making clangd happy and making script to compile/build with run.sh
@@ -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}"
|
||||
)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 990 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1010 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1006 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 678 B |
|
After Width: | Height: | Size: 931 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 952 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 940 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||