making clangd happy and making script to compile/build with run.sh

This commit is contained in:
talksik
2024-02-07 13:12:51 -08:00
commit 45c681e7be
79 changed files with 4235 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.16)
project(documentviewer LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
set(CMAKE_AUTORCC ON)
qt_standard_project_setup()
qt_add_library(abstractviewer STATIC
abstractviewer.cpp abstractviewer.h
)
qt_add_executable(documentviewer
main.cpp
mainwindow.cpp mainwindow.h mainwindow.ui
viewerfactory.cpp viewerfactory.h
recentfiles.cpp recentfiles.h
recentfilemenu.cpp recentfilemenu.h
viewerinterfaces.h
documentviewer.qrc
)
set_target_properties(documentviewer PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(documentviewer PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
abstractviewer
)
target_link_libraries(abstractviewer PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
set(plugin_targets
jsonviewer
txtviewer
)
if(TARGET pdfviewer)
list(APPEND plugin_targets pdfviewer)
endif()
if(QT6_IS_SHARED_LIBS_BUILD)
add_dependencies(documentviewer ${plugin_targets})
else()
target_link_libraries(documentviewer PRIVATE ${plugin_targets})
endif()
if(TARGET Qt6::PrintSupport)
target_link_libraries(documentviewer PRIVATE Qt6::PrintSupport)
target_link_libraries(abstractviewer 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}"
)
+281
View File
@@ -0,0 +1,281 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "abstractviewer.h"
#include <QApplication>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QScrollArea>
#include <QStatusBar>
#include <QTabWidget>
#include <QToolBar>
#include <QAction>
#include <QFile>
#include <QSettings>
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrinter>
#include <QPrintDialog>
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
using namespace Qt::StringLiterals;
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;
}
AbstractViewer::~AbstractViewer()
{
AbstractViewer::cleanup();
}
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() ? ": "_L1 : "/"_L1 + type + ": "_L1) + message;
emit showMessage(msg, timeout);
}
QToolBar *AbstractViewer::addToolBar(const QString &title)
{
auto *bar = mainWindow()->addToolBar(title);
QString name = title;
name.remove(u' ');
bar->setObjectName(name);
m_toolBars.append(bar);
return bar;
}
QMenu *AbstractViewer::addMenu(const QString &title)
{
QMenu *menu = new QMenu(title, menuBar());
menu->setObjectName(title);
menuBar()->insertMenu(m_uiAssets.help, menu);
m_menus.append(menu);
return menu;
}
void AbstractViewer::cleanup()
{
// delete all objects created by the viewer which need to be displayed
// and therefore parented on MainWindow
if (m_file)
m_file.reset();
qDeleteAll(m_menus);
m_menus.clear();
qDeleteAll(m_toolBars);
m_toolBars.clear();
for (const auto &connection : m_connections)
QObject::disconnect(connection);
m_connections.clear();
}
QMenu *AbstractViewer::fileMenu()
{
static constexpr QLatin1StringView name = QLatin1StringView("qtFileMenu");
static QMenu *fileMenu = nullptr;
if (fileMenu)
return fileMenu;
const QList<QMenu *> menus = mainWindow()->findChildren<QMenu *>();
for (auto *menu : menus) {
if (menu->objectName() == name) {
fileMenu = menu;
return fileMenu;
}
}
fileMenu = addMenu(tr("&File"));
fileMenu->setObjectName(name);
return fileMenu;
}
void AbstractViewer::print()
{
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
static const QString type = tr("Printing");
if (!hasContent()) {
statusMessage(tr("No content to print."), type);
return;
}
QPrinter printer(QPrinter::HighResolution);
QPrintDialog dlg(&printer, mainWindow());
dlg.setWindowTitle(tr("Print Document"));
if (dlg.exec() == QDialog::Accepted) {
printDocument(&printer);
} else {
statusMessage(tr("Printing canceled!"), type);
return;
}
const QPrinter::PrinterState state = printer.printerState();
QString message = viewerName() + " :"_L1;
switch (state) {
case QPrinter::PrinterState::Aborted:
message += tr("Printing aborted.");
break;
case QPrinter::PrinterState::Active:
message += tr("Printing active.");
break;
case QPrinter::PrinterState::Idle:
message += tr("Printing completed.");
break;
case QPrinter::PrinterState::Error:
message += tr("Printing error.");
break;
}
statusMessage(message, type);
#else
statusMessage(tr("Printing not supported!"));
#endif
}
/*!
\brief AbstractViewer::setPrintingEnabled
Enables / disables printing.
If printing is not supported or the viewer has no content to display,
\param enabled is overridden with \c false;
The signal printingEnabledChanged is emitted if the status has changed.
*/
void AbstractViewer::maybeSetPrintingEnabled(bool enabled)
{
#ifndef QT_DOCUMENTVIEWER_PRINTSUPPORT
enabled = false;
#else
if (!hasContent())
enabled = false;
#endif
if (enabled == m_printingEnabled)
return;
m_printingEnabled = enabled;
emit printingEnabledChanged(enabled);
}
void AbstractViewer::initViewer(QAction *back, QAction *forward, QAction *help, QTabWidget *tabs)
{
// Viewers need back & forward buttons and a tab widget.
Q_ASSERT(back);
Q_ASSERT(forward);
Q_ASSERT(tabs);
Q_ASSERT(help);
m_uiAssets.back = back;
m_uiAssets.forward = forward;
m_uiAssets.help = help;
m_uiAssets.tabs = tabs;
// Tabs can be populated individually by the viewer, if it supports overview
tabs->clear();
tabs->setVisible(supportsOverview());
emit uiInitialized();
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef ABSTRACTVIEWER_H
#define ABSTRACTVIEWER_H
#include <QObject>
QT_BEGIN_NAMESPACE
class QAction;
class QFile;
class QMainWindow;
class QMenu;
class QMenuBar;
class QPrinter;
class QToolBar;
class QTabWidget;
class QScrollArea;
class QStatusBar;
QT_END_NAMESPACE
class AbstractViewer : public QObject
{
Q_OBJECT
protected:
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;
virtual bool saveDocument();
virtual bool saveDocumentAs();
virtual QString viewerName() const = 0;
virtual bool supportsOverview() const;
virtual QByteArray saveState() const = 0;
virtual bool restoreState(QByteArray &) = 0;
virtual bool hasContent() const;
virtual QStringList supportedMimeTypes() const = 0;
virtual bool isDefaultViewer() const;
virtual void cleanup();
bool isEmpty() const;
bool isPrintingEnabled() const;
AbstractViewer *viewer();
const AbstractViewer *viewer() const;
QList<QAction *> actions() const;
QWidget *widget() const;
QList<QMenu *> menus() const;
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
virtual void printDocument(QPrinter *) const {};
#endif
signals:
void uiInitialized();
void printingEnabledChanged(bool enabled);
void showMessage(const QString &message, int timeout = 8000);
void documentLoaded(const QString &fileName);
public slots:
void print();
protected:
struct UiAssets {
QMainWindow *mainWindow = nullptr;
QAction *back = nullptr;
QAction *forward = nullptr;
QAction *help = nullptr;
QTabWidget *tabs = nullptr;
} m_uiAssets;
void statusMessage(const QString &message, const QString &type = QString(), int timeout = 8000);
QToolBar *addToolBar(const QString &);
QMenu *addMenu(const QString &);
QMenu *fileMenu();
QMainWindow *mainWindow() const;
QStatusBar *statusBar() const;
QMenuBar *menuBar() const;
std::unique_ptr<QFile> m_file;
QList<QAction *> m_actions;
QWidget *m_widget = nullptr;
QList<QMetaObject::Connection> m_connections;
protected slots:
void maybeSetPrintingEnabled(bool enabled);
void maybeEnablePrinting();
void disablePrinting();
private:
QList<QMenu *> m_menus;
QList<QToolBar *> m_toolBars;
bool m_printingEnabled = false;
};
#endif // ABSTRACTVIEWER_H
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="demos/documentviewer">
<file>images/copy@2x.png</file>
<file>images/copy.png</file>
<file>images/cut@2x.png</file>
<file>images/cut.png</file>
<file>images/go-next-view@2x.png</file>
<file>images/go-next-view-page@2x.png</file>
<file>images/go-next-view-page.png</file>
<file>images/go-next-view.png</file>
<file>images/go-previous-view@2x.png</file>
<file>images/go-previous-view-page@2x.png</file>
<file>images/go-previous-view-page.png</file>
<file>images/go-previous-view.png</file>
<file>images/magnifier@2x.png</file>
<file>images/magnifier.png</file>
<file>images/open@2x.png</file>
<file>images/open.png</file>
<file>images/paste@2x.png</file>
<file>images/paste.png</file>
<file>images/print2x.png</file>
<file>images/print.png</file>
<file>images/qt-logo@2x.png</file>
<file>images/qt-logo.png</file>
<file>images/zoom-fit-best@2x.png</file>
<file>images/zoom-fit-best.png</file>
<file>images/zoom-fit-width@2x.png</file>
<file>images/zoom-fit-width.png</file>
<file>images/zoom-in@2x.png</file>
<file>images/zoom-in.png</file>
<file>images/zoom-original@2x.png</file>
<file>images/zoom-original.png</file>
<file>images/zoom-out@2x.png</file>
<file>images/zoom-out.png</file>
<file>images/zoom-previous@2x.png</file>
<file>images/zoom-previous.png</file>
<file>images/document-open-recent.svgz</file>
<file>images/go-next.svgz</file>
<file>images/go-previous.svgz</file>
<file>images/help-about.svgz</file>
</qresource>
</RCC>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+36
View File
@@ -0,0 +1,36 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mainwindow.h"
#include <QApplication>
#include <QCommandLineParser>
using namespace Qt::StringLiterals;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QCoreApplication::setOrganizationName("QtProject"_L1);
QCoreApplication::setApplicationName("DocumentViewer"_L1);
QCoreApplication::setApplicationVersion("1.0"_L1);
QCommandLineParser parser;
parser.setApplicationDescription(QApplication::translate("main",
"A viewer for JSON, PDF and text files"));
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("File"_L1, QApplication::translate("main",
"JSON, PDF or text file to open"));
parser.process(app);
const QStringList &positionalArguments = parser.positionalArguments();
const QString &fileName = (positionalArguments.count() > 0) ? positionalArguments.at(0)
: QString();
MainWindow w;
w.show();
if (!fileName.isEmpty())
w.openFile(fileName);
return app.exec();
}
+190
View File
@@ -0,0 +1,190 @@
// 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 "viewerfactory.h"
#include "abstractviewer.h"
#include "recentfiles.h"
#include "recentfilemenu.h"
#include <QFileDialog>
#include <QToolButton>
#include <QMessageBox>
#include <QDir>
#include <QSettings>
using namespace Qt::StringLiterals;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpenTriggered);
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::onActionAboutTriggered);
connect(ui->actionAboutQt, &QAction::triggered, this, &MainWindow::onActionAboutQtTriggered);
m_recentFiles.reset(new RecentFiles(ui->actionRecent));
connect(m_recentFiles.get(), &RecentFiles::countChanged, this, [&](int count){
ui->actionRecent->setText(tr("%n recent files", nullptr, count));
});
readSettings();
m_factory.reset(new ViewerFactory(ui->viewArea, this));
const QStringList &viewers = m_factory->viewerNames();
const QString msg = tr("Available viewers: %1").arg(viewers.join(", "_L1));
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()
{
saveSettings();
}
void MainWindow::onActionOpenTriggered()
{
QFileDialog fileDialog(this, tr("Open Document"), m_currentDir.absolutePath());
while (fileDialog.exec() == QDialog::Accepted
&& !openFile(fileDialog.selectedFiles().constFirst())) {
}
}
bool MainWindow::openFile(const QString &fileName)
{
QFile *file = new QFile(fileName);
if (!file->exists()) {
statusBar()->showMessage(tr("File %1 could not be opened")
.arg(QDir::toNativeSeparators(fileName)));
delete file;
return false;
}
QFileInfo fileInfo(*file);
m_currentDir = fileInfo.dir();
m_recentFiles->addFile(fileInfo.absoluteFilePath());
// If a viewer is already open, clean it up and save its settings
resetViewer();
m_viewer = m_factory->viewer(file);
if (!m_viewer) {
statusBar()->showMessage(tr("File %1 can't be opened.")
.arg(QDir::toNativeSeparators(fileName)));
return false;
}
ui->actionPrint->setEnabled(m_viewer->hasContent());
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());
return true;
}
void MainWindow::onActionAboutTriggered()
{
const QString viewerNames = m_factory->viewerNames().join(", "_L1);
const QString mimeTypes = m_factory->supportedMimeTypes().join(u'\n');
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%1\n"
"\n\nIt supports the following mime types:\n%2")
.arg(viewerNames, mimeTypes);
if (auto *def = m_factory->defaultViewer())
text += tr("\n\nOther mime types will be displayed with %1.").arg(def->viewerName());
QMessageBox::about(this, tr("About Document Viewer Demo"), text);
}
void MainWindow::onActionAboutQtTriggered()
{
QMessageBox::aboutQt(this);
}
void MainWindow::readSettings()
{
QSettings settings;
// Restore working directory
if (settings.contains(settingsDir))
m_currentDir = QDir(settings.value(settingsDir).toString());
else
m_currentDir = QDir::current();
// Restore QMainWindow state
if (settings.contains(settingsMainWindow)) {
QByteArray mainWindowState = settings.value(settingsMainWindow).toByteArray();
restoreState(mainWindowState);
}
// Restore recent files
m_recentFiles->restoreFromSettings(settings, settingsFiles);
}
void MainWindow::saveSettings() const
{
QSettings settings;
// Save working directory
settings.setValue(settingsDir, m_currentDir.absolutePath());
// Save QMainWindow state
settings.setValue(settingsMainWindow, saveState());
// Save recent files
m_recentFiles->saveSettings(settings, settingsFiles);
settings.sync();
}
void MainWindow::saveViewerSettings() const
{
if (!m_viewer)
return;
QSettings settings;
settings.beginGroup(settingsViewers);
settings.setValue(m_viewer->viewerName(), m_viewer->saveState());
settings.endGroup();
settings.sync();
}
void MainWindow::resetViewer() const
{
if (!m_viewer)
return;
saveViewerSettings();
m_viewer->cleanup();
}
void MainWindow::restoreViewerSettings()
{
if (!m_viewer)
return;
QSettings settings;
settings.beginGroup(settingsViewers);
QByteArray viewerSettings = settings.value(m_viewer->viewerName(), QByteArray()).toByteArray();
settings.endGroup();
if (!viewerSettings.isEmpty())
m_viewer->restoreState(viewerSettings);
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDir>
#include <QStringLiteral>
class AbstractViewer;
class RecentFiles;
class ViewerFactory;
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
bool openFile(const QString &fileName);
private slots:
void onActionOpenTriggered();
void onActionAboutTriggered();
void onActionAboutQtTriggered();
private:
void readSettings();
void saveSettings() const;
void restoreViewerSettings();
void resetViewer() const;
void saveViewerSettings() const;
QDir m_currentDir;
AbstractViewer *m_viewer = nullptr;
std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<RecentFiles> m_recentFiles;
std::unique_ptr<ViewerFactory> m_factory;
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
+272
View File
@@ -0,0 +1,272 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>983</width>
<height>602</height>
</rect>
</property>
<property name="windowTitle">
<string>Document Viewer Demo</string>
</property>
<property name="windowIcon">
<iconset resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>:/demos/documentviewer/images/qt-logo.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="viewArea" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTabWidget" name="tabWidget">
<property name="tabPosition">
<enum>QTabWidget::West</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="bookmarkTab">
<attribute name="title">
<string>Pages</string>
</attribute>
</widget>
<widget class="QWidget" name="pagesTab">
<attribute name="title">
<string>Bookmarks</string>
</attribute>
</widget>
</widget>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>0</height>
</size>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>798</width>
<height>479</height>
</rect>
</property>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>983</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="qtFileMenu">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionRecent"/>
<addaction name="actionPrint"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAboutQt"/>
</widget>
<addaction name="qtFileMenu"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="mainToolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionOpen"/>
<addaction name="actionRecent"/>
<addaction name="actionPrint"/>
<addaction name="separator"/>
<addaction name="actionBack"/>
<addaction name="actionForward"/>
<addaction name="separator"/>
</widget>
<action name="actionOpen">
<property name="icon">
<iconset resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/open.png</normaloff>:/demos/documentviewer/images/open.png</iconset>
</property>
<property name="text">
<string>Open</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionAbout">
<property name="icon">
<iconset theme="help-about" resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/help-about.svgz</normaloff>:/demos/documentviewer/images/help-about.svgz</iconset>
</property>
<property name="text">
<string>about documentviewer</string>
</property>
<property name="toolTip">
<string>Show information about the Document Viewer deomo.</string>
</property>
<property name="shortcut">
<string>Ctrl+H</string>
</property>
</action>
<action name="actionForward">
<property name="icon">
<iconset resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/go-next.svgz</normaloff>:/demos/documentviewer/images/go-next.svgz</iconset>
</property>
<property name="text">
<string>actionForward</string>
</property>
<property name="toolTip">
<string>One step forward</string>
</property>
<property name="shortcut">
<string>Right</string>
</property>
</action>
<action name="actionBack">
<property name="icon">
<iconset resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/go-previous.svgz</normaloff>:/demos/documentviewer/images/go-previous.svgz</iconset>
</property>
<property name="text">
<string>actionBack</string>
</property>
<property name="toolTip">
<string>One step back</string>
</property>
<property name="shortcut">
<string>Left</string>
</property>
</action>
<action name="actionPrint">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-print" resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/print2x.png</normaloff>:/demos/documentviewer/images/print2x.png</iconset>
</property>
<property name="text">
<string>Print</string>
</property>
<property name="toolTip">
<string>Print current file</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
<action name="actionAboutQt">
<property name="icon">
<iconset resource="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>
</property>
<property name="toolTip">
<string>Show Qt license information</string>
</property>
<property name="shortcut">
<string>Ctrl+I</string>
</property>
</action>
<action name="actionRecent">
<property name="icon">
<iconset resource="documentviewer.qrc">
<normaloff>:/demos/documentviewer/images/document-open-recent.svgz</normaloff>:/demos/documentviewer/images/document-open-recent.svgz</iconset>
</property>
<property name="text">
<string>Recently opened...</string>
</property>
<property name="shortcut">
<string>Meta+R</string>
</property>
</action>
<action name="actionQuit">
<property name="icon">
<iconset theme="application-exit">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Quit</string>
</property>
<property name="toolTip">
<string>Quit the application</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
</widget>
<resources>
<include location="documentviewer.qrc"/>
</resources>
<connections>
<connection>
<sender>actionQuit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>491</x>
<y>300</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+36
View File
@@ -0,0 +1,36 @@
// 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>
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());
});
}
}
+28
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 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
+128
View File
@@ -0,0 +1,128 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "recentfiles.h"
#include <QFileInfo>
#include <QSettings>
// Test if file exists and can be opened
static bool testFileAccess(const QString &fileName)
{
return QFileInfo(fileName).isReadable();
}
void RecentFiles::clear()
{
if (isEmpty())
return;
m_files.clear();
emit countChanged(0);
}
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 = m_files.count();
// Clean dangling and duplicate files
for (qsizetype i = 0; i < m_files.size(); ) {
const QString &file = m_files.at(i);
if (!testFileAccess(file)) {
removeFile(file, RemoveReason::Other);
} else if (file == fileName) {
removeFile(file, RemoveReason::Duplicate);
} else {
++i;
}
}
// Cut tail
while (m_files.count() > m_maxFiles)
removeFile((m_files.count() - 1), RemoveReason::Other);
m_files.prepend(fileName);
switch (policy) {
case EmitPolicy::NeverEmit:
return;
case EmitPolicy::EmitWhenChanged:
emit changed();
if (c != m_files.count())
emit countChanged(m_files.count());
return;
}
}
void RecentFiles::addFiles(const QStringList &files)
{
if (files.isEmpty())
return;
if (files.count() == 1) {
addFile(files.at(0));
return;
}
const qsizetype c = m_files.count();
for (const auto &file : files)
addFile(file, EmitPolicy::NeverEmit);
emit changed();
if (m_files.count() != c)
emit countChanged(m_files.count());
}
void RecentFiles::removeFile(qsizetype index, RemoveReason reason)
{
if (index < 0 || index >= m_files.count())
return;
m_files.remove(index);
// No emit for duplicate removal, add emits changed later.
if (reason != RemoveReason::Duplicate)
emit changed();
}
void RecentFiles::saveSettings(QSettings &settings, const QString &key) const
{
settings.beginGroup(key);
settings.setValue(s_maxFiles, maxFiles());
if (!isEmpty()) {
settings.beginWriteArray(s_fileNames, m_files.count());
for (int index = 0; index < m_files.count(); ++index) {
settings.setArrayIndex(index);
settings.setValue(s_file, m_files.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();
setMaxFiles(mxFiles);
m_files.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 (!m_files.isEmpty())
emit changed();
return true;
}
+71
View File
@@ -0,0 +1,71 @@
// 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>
QT_BEGIN_NAMESPACE
class QSettings;
QT_END_NAMESPACE
class RecentFiles : public QObject
{
Q_OBJECT
public:
enum class RemoveReason {
Other,
Duplicate
};
Q_ENUM(RemoveReason)
using QObject::QObject;
// Access to QStringList member functions
const QStringList recentFiles() const {return m_files; }
bool isEmpty() const { return m_files.isEmpty(); }
// Properties
qsizetype maxFiles() const { return m_maxFiles; }
void setMaxFiles(qsizetype maxFiles) { m_maxFiles = maxFiles; }
public slots:
void addFile(const QString &fileName) { addFile(fileName, EmitPolicy::EmitWhenChanged); }
void addFiles(const QStringList &fileNames);
void removeFile(const QString &fileName) { removeFile(m_files.indexOf(fileName)); }
void removeFile(qsizetype index) {removeFile(index, RemoveReason::Other); }
void saveSettings(QSettings &settings, const QString &key) const;
bool restoreFromSettings(QSettings &settings, const QString &key);
void clear();
signals:
void countChanged(int count);
void changed();
private:
// Private removers with reason
void removeFile(qsizetype index, RemoveReason reason);
void removeFile(const QString &fileName, RemoveReason reason) {removeFile(m_files.indexOf(fileName), reason); }
// Private adder with emit policy
enum class EmitPolicy {
EmitWhenChanged,
NeverEmit
};
void addFile(const QString &fileName, EmitPolicy policy);
qsizetype m_maxFiles = 10;
QStringList m_files;
// 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
+189
View File
@@ -0,0 +1,189 @@
// 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>
using namespace Qt::StringLiterals;
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_WINDOWS)
pluginsDir.cd("app"_L1);
#elif defined(Q_OS_DARWIN)
if (pluginsDir.dirName() == "MacOS"_L1) {
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"_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 (m_defaultWarning) {
QMessageBox mbox;
mbox.setIcon(QMessageBox::Warning);
mbox.setText(QObject::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::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());
return mimeTypes;
}
+62
View File
@@ -0,0 +1,62 @@
// 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 <QMap>
QT_BEGIN_NAMESPACE
class QMimeType;
class QWidget;
class QMainWindow;
class QFile;
QT_END_NAMESPACE
class AbstractViewer;
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; }
bool defaultWarning() const { return m_defaultWarning; }
void setDefaultWarning(bool on) { m_defaultWarning = on; }
AbstractViewer *viewer(QFile *file) const;
using ViewerMap = QMap<QString, AbstractViewer *>;
using ViewerList = QList<AbstractViewer *>;
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;
bool m_defaultWarning = true;
void loadViewerPlugins();
void addViewer(QObject *viewerObject);
AbstractViewer *viewer(const QMimeType &type) const;
void unload();
};
#endif // VIEWERFACTORY_H
+35
View File
@@ -0,0 +1,35 @@
// 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>
QT_BEGIN_NAMESPACE
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