2023-01-26 17:31:16 +00:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
|
|
|
|
#include "pdfviewer.h"
|
|
|
|
#include "zoomselector.h"
|
|
|
|
#include "hoverwatcher.h"
|
|
|
|
|
|
|
|
#include <QApplication>
|
2023-06-20 13:36:15 +00:00
|
|
|
#include <QEvent>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
2023-01-26 17:31:16 +00:00
|
|
|
#include <QPdfBookmarkModel>
|
|
|
|
#include <QPdfDocument>
|
|
|
|
#include <QPdfPageNavigator>
|
2023-06-20 13:36:15 +00:00
|
|
|
#include <QPdfView>
|
2023-03-20 06:57:09 +00:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6,6,0)
|
2023-01-26 17:31:16 +00:00
|
|
|
#include <QPdfPageSelector>
|
2023-03-20 06:57:09 +00:00
|
|
|
#endif
|
2023-06-20 13:36:15 +00:00
|
|
|
|
2023-01-26 17:31:16 +00:00
|
|
|
#include <QtMath>
|
2023-07-04 11:44:41 +00:00
|
|
|
#include <QDir>
|
2023-06-20 13:36:15 +00:00
|
|
|
#include <QStandardPaths>
|
|
|
|
|
|
|
|
#include <QListView>
|
2023-01-26 17:31:16 +00:00
|
|
|
#include <QListWidget>
|
2023-06-20 13:36:15 +00:00
|
|
|
#include <QMainWindow>
|
2023-01-26 17:31:16 +00:00
|
|
|
#include <QScrollBar>
|
|
|
|
#include <QScroller>
|
2023-06-20 13:36:15 +00:00
|
|
|
#include <QSpinBox>
|
|
|
|
#include <QToolBar>
|
|
|
|
#include <QTreeView>
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
|
2023-01-26 17:31:16 +00:00
|
|
|
#include <QPrinter>
|
|
|
|
#include <QPainter>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
Q_LOGGING_CATEGORY(lcExample, "qt.examples.pdfviewer")
|
|
|
|
|
2023-06-20 13:36:15 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2023-06-17 18:04:55 +00:00
|
|
|
PdfViewer::PdfViewer()
|
|
|
|
{
|
|
|
|
connect(this, &AbstractViewer::uiInitialized, this, &PdfViewer::initPdfViewer);
|
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
void PdfViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
|
2023-01-26 17:31:16 +00:00
|
|
|
{
|
2023-03-23 15:25:43 +00:00
|
|
|
AbstractViewer::init(file, new QPdfView(parent), mainWindow);
|
2025-04-17 12:46:19 +00:00
|
|
|
setTranslationBaseName("pdfviewer"_L1);
|
2023-03-23 15:25:43 +00:00
|
|
|
m_document = new QPdfDocument(this);
|
2023-01-26 17:31:16 +00:00
|
|
|
m_pdfView = qobject_cast<QPdfView *>(widget());
|
|
|
|
}
|
2023-03-23 15:25:43 +00:00
|
|
|
|
2023-06-17 18:04:55 +00:00
|
|
|
void PdfViewer::cleanup()
|
2023-01-26 17:31:16 +00:00
|
|
|
{
|
2023-06-17 18:04:55 +00:00
|
|
|
delete m_pageSelector;
|
|
|
|
m_pageSelector = nullptr;
|
|
|
|
delete m_zoomSelector;
|
|
|
|
m_zoomSelector = nullptr;
|
2023-01-26 17:31:16 +00:00
|
|
|
delete m_pages;
|
2023-06-17 18:04:55 +00:00
|
|
|
m_pages = nullptr;
|
2023-01-26 17:31:16 +00:00
|
|
|
delete m_bookmarks;
|
2023-06-17 18:04:55 +00:00
|
|
|
m_bookmarks = nullptr;
|
2023-01-26 17:31:16 +00:00
|
|
|
delete m_document;
|
2023-06-17 18:04:55 +00:00
|
|
|
m_document = nullptr;
|
|
|
|
AbstractViewer::cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
PdfViewer::~PdfViewer()
|
|
|
|
{
|
|
|
|
PdfViewer::cleanup();
|
2023-01-26 17:31:16 +00:00
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
QStringList PdfViewer::supportedMimeTypes() const
|
|
|
|
{
|
2023-06-20 13:36:15 +00:00
|
|
|
return {"application/pdf"_L1};
|
2023-03-23 15:25:43 +00:00
|
|
|
}
|
|
|
|
|
2023-01-26 17:31:16 +00:00
|
|
|
void PdfViewer::initPdfViewer()
|
|
|
|
{
|
2023-06-20 13:36:15 +00:00
|
|
|
m_toolBar = addToolBar(tr("PDF"));
|
2023-01-26 17:31:16 +00:00
|
|
|
m_zoomSelector = new ZoomSelector(m_toolBar);
|
|
|
|
|
2023-03-20 06:57:09 +00:00
|
|
|
auto *nav = m_pdfView->pageNavigator();
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6,6,0)
|
|
|
|
m_pageSelector = new QPdfPageSelector(m_toolBar);
|
2023-01-26 17:31:16 +00:00
|
|
|
m_toolBar->insertWidget(m_uiAssets.forward, m_pageSelector);
|
2023-09-12 13:14:26 +00:00
|
|
|
m_pageSelector->setDocument(m_document);
|
|
|
|
connect(m_pageSelector, &QPdfPageSelector::currentPageChanged,
|
|
|
|
this, &PdfViewer::pageSelected);
|
|
|
|
connect(m_pageSelector, &QPdfPageSelector::currentPageChanged,
|
|
|
|
this, &PdfViewer::pageSelected);
|
|
|
|
connect(nav, &QPdfPageNavigator::currentPageChanged,
|
|
|
|
m_pageSelector, &QPdfPageSelector::setCurrentPage);
|
2023-03-20 06:57:09 +00:00
|
|
|
#endif
|
|
|
|
|
2023-10-19 09:50:59 +00:00
|
|
|
connect(m_pdfView->pageNavigator(), &QPdfPageNavigator::backAvailableChanged,
|
|
|
|
m_uiAssets.back, &QAction::setEnabled);
|
2023-01-26 17:31:16 +00:00
|
|
|
m_actionBack = m_uiAssets.back;
|
|
|
|
m_actionForward = m_uiAssets.forward;
|
2023-10-19 09:50:59 +00:00
|
|
|
m_connections.append(connect(m_uiAssets.back, &QAction::triggered,
|
|
|
|
this, &PdfViewer::onActionBackTriggered));
|
|
|
|
m_connections.append(connect(m_uiAssets.forward, &QAction::triggered,
|
|
|
|
this, &PdfViewer::onActionForwardTriggered));
|
2023-01-26 17:31:16 +00:00
|
|
|
|
|
|
|
m_toolBar->addSeparator();
|
|
|
|
m_toolBar->addWidget(m_zoomSelector);
|
|
|
|
|
2024-11-25 21:14:38 +00:00
|
|
|
QIcon icon = QIcon::fromTheme(QIcon::ThemeIcon::ZoomIn, QIcon(":/demos/documentviewer/images/zoom-in.png"_L1));
|
documentviewer: Add retranslate() method to all plugins
Previously, only the imageviewer plugin had a retranslate() method,
which meant that when switching languages, UI texts in other plugins
(jsonviewer, txtviewer, pdfviewer) would not be updated.
This change adds retranslate() methods to all plugins that contain
translatable UI text:
- jsonviewer: Updates menu titles, toolbar titles, actions, tab titles,
and bookmark tooltips
- txtviewer: Updates menu titles, toolbar titles, and action texts/tooltips
- pdfviewer: Updates toolbar title, action texts/tooltips, and tab titles
Each plugin now stores references to UI elements that need retranslation
and implements the retranslate() method to update all translatable text
when the language is changed.
Fixes: QTBUG-138344
Pick-to: 6.10
Change-Id: I9d294694a42c2c48e03b72e28e9c0f4015cd5c80
Reviewed-by: Masoud Jami <masoud.jami@qt.io>
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
2025-07-09 11:40:55 +00:00
|
|
|
m_actionZoomIn = m_toolBar->addAction(icon, tr("Zoom in"), QKeySequence::ZoomIn);
|
|
|
|
connect(m_actionZoomIn, &QAction::triggered, this, &PdfViewer::onActionZoomInTriggered);
|
2023-01-26 17:31:16 +00:00
|
|
|
|
2025-07-03 15:33:46 +00:00
|
|
|
icon = QIcon::fromTheme(QIcon::ThemeIcon::ZoomOut,
|
|
|
|
QIcon(":/demos/documentviewer/images/zoom-out.png"_L1));
|
documentviewer: Add retranslate() method to all plugins
Previously, only the imageviewer plugin had a retranslate() method,
which meant that when switching languages, UI texts in other plugins
(jsonviewer, txtviewer, pdfviewer) would not be updated.
This change adds retranslate() methods to all plugins that contain
translatable UI text:
- jsonviewer: Updates menu titles, toolbar titles, actions, tab titles,
and bookmark tooltips
- txtviewer: Updates menu titles, toolbar titles, and action texts/tooltips
- pdfviewer: Updates toolbar title, action texts/tooltips, and tab titles
Each plugin now stores references to UI elements that need retranslation
and implements the retranslate() method to update all translatable text
when the language is changed.
Fixes: QTBUG-138344
Pick-to: 6.10
Change-Id: I9d294694a42c2c48e03b72e28e9c0f4015cd5c80
Reviewed-by: Masoud Jami <masoud.jami@qt.io>
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
2025-07-09 11:40:55 +00:00
|
|
|
m_actionZoomOut = m_toolBar->addAction(icon, tr("Zoom out"), QKeySequence::ZoomOut);
|
|
|
|
connect(m_actionZoomOut, &QAction::triggered, this, &PdfViewer::onActionZoomOutTriggered);
|
2023-01-26 17:31:16 +00:00
|
|
|
|
|
|
|
connect(nav, &QPdfPageNavigator::backAvailableChanged, m_actionBack, &QAction::setEnabled);
|
|
|
|
connect(nav, &QPdfPageNavigator::forwardAvailableChanged, m_actionForward, &QAction::setEnabled);
|
|
|
|
|
|
|
|
connect(m_zoomSelector, &ZoomSelector::zoomModeChanged, m_pdfView, &QPdfView::setZoomMode);
|
|
|
|
connect(m_zoomSelector, &ZoomSelector::zoomFactorChanged, m_pdfView, &QPdfView::setZoomFactor);
|
|
|
|
m_zoomSelector->reset();
|
|
|
|
|
|
|
|
QPdfBookmarkModel *bookmarkModel = new QPdfBookmarkModel(this);
|
|
|
|
bookmarkModel->setDocument(m_document);
|
|
|
|
m_uiAssets.tabs->clear();
|
|
|
|
m_bookmarks = new QTreeView(m_uiAssets.tabs);
|
|
|
|
connect(m_bookmarks, &QAbstractItemView::activated, this, &PdfViewer::bookmarkSelected);
|
|
|
|
m_bookmarks->setModel(bookmarkModel);
|
|
|
|
m_pdfView->setDocument(m_document);
|
|
|
|
m_pdfView->setPageMode(QPdfView::PageMode::MultiPage);
|
|
|
|
|
|
|
|
openPdfFile();
|
|
|
|
if (!m_document->pageCount())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pages = new QListView(m_uiAssets.tabs);
|
|
|
|
m_pages->setModel(m_document->pageModel());
|
|
|
|
connect(m_pages->selectionModel(), &QItemSelectionModel::currentRowChanged, m_pages, [&]
|
|
|
|
(const QModelIndex ¤t, const QModelIndex &previous){
|
|
|
|
if (previous == current)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto *nav = m_pdfView->pageNavigator();
|
|
|
|
const int &row = current.row();
|
|
|
|
if (nav->currentPage() == row)
|
|
|
|
return;
|
|
|
|
|
|
|
|
nav->jump(row, QPointF(), nav->currentZoom());
|
|
|
|
});
|
|
|
|
|
|
|
|
connect(m_pdfView->pageNavigator(), &QPdfPageNavigator::currentPageChanged, m_pages, [&](int page){
|
|
|
|
if (m_pages->currentIndex().row() == page)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pages->setCurrentIndex(m_pages->model()->index(page, 0));
|
|
|
|
});
|
|
|
|
|
2023-06-20 13:36:15 +00:00
|
|
|
m_uiAssets.tabs->addTab(m_pages, tr("Pages"));
|
|
|
|
m_uiAssets.tabs->addTab(m_bookmarks, tr("Bookmarks"));
|
2023-01-26 17:31:16 +00:00
|
|
|
QScroller::grabGesture(m_pdfView->viewport(), QScroller::ScrollerGestureType::LeftMouseButtonGesture);
|
|
|
|
HoverWatcher::watcher(m_pdfView->viewport());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::openPdfFile()
|
|
|
|
{
|
|
|
|
disablePrinting();
|
|
|
|
|
2023-07-04 11:40:19 +00:00
|
|
|
if (m_file->open(QIODevice::ReadOnly))
|
2023-01-26 17:31:16 +00:00
|
|
|
m_document->load(m_file.get());
|
|
|
|
|
|
|
|
const auto documentTitle = m_document->metaData(QPdfDocument::MetaDataField::Title).toString();
|
2023-07-04 11:44:41 +00:00
|
|
|
statusMessage(documentTitle.isEmpty() ? "PDF Viewer"_L1 : documentTitle);
|
2023-01-26 17:31:16 +00:00
|
|
|
pageSelected(0);
|
|
|
|
|
2023-07-04 11:44:41 +00:00
|
|
|
statusMessage(tr("Opened PDF file %1")
|
|
|
|
.arg(QDir::toNativeSeparators(m_file->fileName())));
|
2023-01-26 17:31:16 +00:00
|
|
|
qCDebug(lcExample) << "Opened file" << m_file->fileName();
|
|
|
|
|
|
|
|
maybeEnablePrinting();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PdfViewer::hasContent() const
|
|
|
|
{
|
|
|
|
return m_document ? m_document->pageCount() > 0 : false;
|
|
|
|
}
|
|
|
|
|
2023-03-23 15:25:43 +00:00
|
|
|
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
|
2023-01-26 17:31:16 +00:00
|
|
|
void PdfViewer::printDocument(QPrinter *printer) const
|
|
|
|
{
|
|
|
|
if (!hasContent())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QPainter painter;
|
|
|
|
painter.begin(printer);
|
|
|
|
const QRect pageRect = printer->pageRect(QPrinter::Unit::DevicePixel).toRect();
|
|
|
|
const QSize pageSize = pageRect.size();
|
|
|
|
for (int i = 0; i < m_document->pageCount(); ++i) {
|
|
|
|
if (i > 0)
|
|
|
|
printer->newPage();
|
|
|
|
const QImage &page = m_document->render(i, pageSize);
|
|
|
|
painter.drawImage(pageRect, page);
|
|
|
|
}
|
|
|
|
painter.end();
|
|
|
|
}
|
2023-03-23 15:25:43 +00:00
|
|
|
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
|
2023-01-26 17:31:16 +00:00
|
|
|
|
|
|
|
void PdfViewer::bookmarkSelected(const QModelIndex &index)
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const int page = index.data(int(QPdfBookmarkModel::Role::Page)).toInt();
|
|
|
|
const qreal zoomLevel = index.data(int(QPdfBookmarkModel::Role::Level)).toReal();
|
|
|
|
m_pdfView->pageNavigator()->jump(page, {}, zoomLevel);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::pageSelected(int page)
|
|
|
|
{
|
|
|
|
auto nav = m_pdfView->pageNavigator();
|
|
|
|
nav->jump(page, {}, nav->currentZoom());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::onActionZoomInTriggered()
|
|
|
|
{
|
|
|
|
m_pdfView->setZoomFactor(m_pdfView->zoomFactor() * zoomMultiplier);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::onActionZoomOutTriggered()
|
|
|
|
{
|
|
|
|
m_pdfView->setZoomFactor(m_pdfView->zoomFactor() / zoomMultiplier);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::onActionPreviousPageTriggered()
|
|
|
|
{
|
|
|
|
auto nav = m_pdfView->pageNavigator();
|
|
|
|
nav->jump(nav->currentPage() - 1, {}, nav->currentZoom());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::onActionNextPageTriggered()
|
|
|
|
{
|
|
|
|
auto nav = m_pdfView->pageNavigator();
|
|
|
|
nav->jump(nav->currentPage() + 1, {}, nav->currentZoom());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::onActionBackTriggered()
|
|
|
|
{
|
|
|
|
m_pdfView->pageNavigator()->back();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PdfViewer::onActionForwardTriggered()
|
|
|
|
{
|
|
|
|
m_pdfView->pageNavigator()->forward();
|
|
|
|
}
|
documentviewer: Add retranslate() method to all plugins
Previously, only the imageviewer plugin had a retranslate() method,
which meant that when switching languages, UI texts in other plugins
(jsonviewer, txtviewer, pdfviewer) would not be updated.
This change adds retranslate() methods to all plugins that contain
translatable UI text:
- jsonviewer: Updates menu titles, toolbar titles, actions, tab titles,
and bookmark tooltips
- txtviewer: Updates menu titles, toolbar titles, and action texts/tooltips
- pdfviewer: Updates toolbar title, action texts/tooltips, and tab titles
Each plugin now stores references to UI elements that need retranslation
and implements the retranslate() method to update all translatable text
when the language is changed.
Fixes: QTBUG-138344
Pick-to: 6.10
Change-Id: I9d294694a42c2c48e03b72e28e9c0f4015cd5c80
Reviewed-by: Masoud Jami <masoud.jami@qt.io>
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
2025-07-09 11:40:55 +00:00
|
|
|
|
|
|
|
void PdfViewer::retranslate()
|
|
|
|
{
|
|
|
|
if (m_toolBar)
|
|
|
|
m_toolBar->setWindowTitle(tr("PDF"));
|
|
|
|
if (m_actionZoomIn)
|
|
|
|
m_actionZoomIn->setText(tr("Zoom in"));
|
|
|
|
if (m_actionZoomOut) {
|
|
|
|
m_actionZoomOut->setText(tr("Zoom out"));
|
|
|
|
}
|
|
|
|
if (m_pages && m_bookmarks && m_uiAssets.tabs) {
|
|
|
|
int pagesIndex = m_uiAssets.tabs->indexOf(m_pages);
|
|
|
|
if (pagesIndex >= 0)
|
|
|
|
m_uiAssets.tabs->setTabText(pagesIndex, tr("Pages"));
|
|
|
|
int bookmarksIndex = m_uiAssets.tabs->indexOf(m_bookmarks);
|
|
|
|
if (bookmarksIndex >= 0)
|
|
|
|
m_uiAssets.tabs->setTabText(bookmarksIndex, tr("Bookmarks"));
|
|
|
|
}
|
|
|
|
}
|