diff options
Diffstat (limited to 'examples/pdfwidgets')
27 files changed, 1114 insertions, 0 deletions
diff --git a/examples/pdfwidgets/CMakeLists.txt b/examples/pdfwidgets/CMakeLists.txt new file mode 100644 index 000000000..dbde7f1c2 --- /dev/null +++ b/examples/pdfwidgets/CMakeLists.txt @@ -0,0 +1 @@ +qt_internal_add_example(pdfviewer) diff --git a/examples/pdfwidgets/pdfviewer/CMakeLists.txt b/examples/pdfwidgets/pdfviewer/CMakeLists.txt new file mode 100644 index 000000000..4ace81618 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(pdfviewer LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/pdfwidgets/pdfviewer") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets PdfWidgets) + +qt_add_executable(pdfviewerwidgets + main.cpp + mainwindow.cpp mainwindow.h mainwindow.ui + searchresultdelegate.cpp searchresultdelegate.h + zoomselector.cpp zoomselector.h +) + +set_target_properties(pdfviewerwidgets PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(pdfviewerwidgets PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets + Qt::PdfWidgets +) + +# Resources: +set(resources_resource_files + "images/zoom-fit-width.svgz" + "images/zoom-in.svgz" + "images/go-previous-view-page.svgz" + "images/zoom-original.svgz" + "images/go-previous-view.svgz" + "images/go-next-view-page.svgz" + "images/zoom-fit-best.svgz" + "images/zoom-out.svgz" + "images/zoom-previous.svgz" + "images/document-open.svgz" + "images/go-next-view.svgz" + "images/go-down-search.svgz" + "images/go-up-search.svgz" +) + +qt_add_resources(pdfviewerwidgets "resources" + PREFIX + "/icons" + FILES + ${resources_resource_files} +) + +install(TARGETS pdfviewerwidgets + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc b/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc new file mode 100644 index 000000000..b56958a9f --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc @@ -0,0 +1,87 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example pdfviewer + \meta installpath pdfwidgets + \ingroup qtpdf-examples + \examplecategory {User Interface Components} + + \title PDF Viewer Widget Example + \brief A widget-based PDF viewer that allows scrolling through the pages. + + \image pdfviewer.png + + \e {PDF Viewer} demonstrates how to use the QPdfView class to render + PDF documents and the QPdfPageNavigator class to navigate them. + + Qt Creator and the integrated Qt Designer were used to create the example + UI and to connect it to the code. This affects the code, which might be + somewhat different to what you would typically write by hand. + For more information about using Qt Designer, see \l{Qt Designer Manual} + and \l{Qt Creator: Creating a Qt Widget Based Application}. + + \include examples-run.qdocinc + + \section1 Creating the Main Window + + The MainWindow class inherits the QMainWindow class: + + \quotefromfile pdfviewer/mainwindow.h + \skipto public + \printuntil ~MainWindow() + + The class declares public and private slots that match the actions of the + selectors: + + \printuntil on_actionForward_triggered() + + The actual layout of the main window is specified in a \c{.ui} file. The + widgets and actions are available at runtime in the \c ui member variable. + + \printuntil Ui:: + + The \c m_zoomSelector variable holds the zoom selector and the + \c m_pageSelector holds the page selector. The \c m_document + variable is an instance of the QPdfDocument class that contains + the PDF document. + + \printuntil } + + The actual setup of the different objects is done in the MainWindow + constructor: + + \quotefromfile pdfviewer/mainwindow.cpp + \skipto MainWindow + \printuntil { + + The constructor first calls \c setupUi() to construct most of the main window + according to the UI file. The zoom and page selectors need to be added to + the toolbar via \l QToolBar::insertWidget(), because that cannot be done + in a UI file: + + \printuntil insertWidget(ui->actionForward, m_pageSelector); + + We connect relevant signals to the page selector spinbox and the browser-style + back and forward buttons: + + \printuntil forwardAvailableChanged + + We connect the \c zoomModeChanged and \c zoomFactor changed signals of the + PDF view to the functions that reset the zoom selector: + + \printuntil reset() + + We then load the PDF document to the viewer: + + \dots + \skipto pdfView + \printuntil ; + + Finally, we connect the \c zoomFactorChanged signal to the function that + sets the value of the zoom selector: + + \printuntil } + + \section1 Files and Attributions +*/ diff --git a/examples/pdfwidgets/pdfviewer/images/document-open.svgz b/examples/pdfwidgets/pdfviewer/images/document-open.svgz Binary files differnew file mode 100644 index 000000000..5ddde5981 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/document-open.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/go-down-search.svgz b/examples/pdfwidgets/pdfviewer/images/go-down-search.svgz Binary files differnew file mode 100644 index 000000000..f845473e7 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-down-search.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/go-next-view-page.svgz b/examples/pdfwidgets/pdfviewer/images/go-next-view-page.svgz Binary files differnew file mode 100644 index 000000000..e7f7bece4 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-next-view-page.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/go-next-view.svgz b/examples/pdfwidgets/pdfviewer/images/go-next-view.svgz Binary files differnew file mode 100644 index 000000000..2f86541cc --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-next-view.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/go-previous-view-page.svgz b/examples/pdfwidgets/pdfviewer/images/go-previous-view-page.svgz Binary files differnew file mode 100644 index 000000000..f4df6df40 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-previous-view-page.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/go-previous-view.svgz b/examples/pdfwidgets/pdfviewer/images/go-previous-view.svgz Binary files differnew file mode 100644 index 000000000..688f55ffe --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-previous-view.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/go-up-search.svgz b/examples/pdfwidgets/pdfviewer/images/go-up-search.svgz Binary files differnew file mode 100644 index 000000000..6378721fa --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-up-search.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-fit-best.svgz b/examples/pdfwidgets/pdfviewer/images/zoom-fit-best.svgz Binary files differnew file mode 100644 index 000000000..0cb1b3074 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-fit-best.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-fit-width.svgz b/examples/pdfwidgets/pdfviewer/images/zoom-fit-width.svgz Binary files differnew file mode 100644 index 000000000..a467f6eae --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-fit-width.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-in.svgz b/examples/pdfwidgets/pdfviewer/images/zoom-in.svgz Binary files differnew file mode 100644 index 000000000..21d6c2ab2 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-in.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-original.svgz b/examples/pdfwidgets/pdfviewer/images/zoom-original.svgz Binary files differnew file mode 100644 index 000000000..afa79db19 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-original.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-out.svgz b/examples/pdfwidgets/pdfviewer/images/zoom-out.svgz Binary files differnew file mode 100644 index 000000000..b6db7a144 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-out.svgz diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-previous.svgz b/examples/pdfwidgets/pdfviewer/images/zoom-previous.svgz Binary files differnew file mode 100644 index 000000000..ad4c29215 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-previous.svgz diff --git a/examples/pdfwidgets/pdfviewer/main.cpp b/examples/pdfwidgets/pdfviewer/main.cpp new file mode 100644 index 000000000..6b890e0ac --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/main.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include <QApplication> +#include <QCommandLineParser> +#include <QCommandLineOption> +#include <QUrl> + +int main(int argc, char *argv[]) +{ + QCoreApplication::setApplicationName("Qt PDF Viewer"); + QCoreApplication::setOrganizationName("QtProject"); + + QApplication a(argc, argv); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("file", "The file to open."); + parser.process(a); + + MainWindow w; + w.show(); + if (!parser.positionalArguments().isEmpty()) + w.open(QUrl::fromLocalFile(parser.positionalArguments().constFirst())); + + return a.exec(); +} diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.cpp b/examples/pdfwidgets/pdfviewer/mainwindow.cpp new file mode 100644 index 000000000..ce19359fc --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/mainwindow.cpp @@ -0,0 +1,225 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "searchresultdelegate.h" +#include "zoomselector.h" + +#include <QFileDialog> +#include <QLineEdit> +#include <QMessageBox> +#include <QPdfBookmarkModel> +#include <QPdfDocument> +#include <QPdfPageNavigator> +#include <QPdfPageSelector> +#include <QPdfSearchModel> +#include <QShortcut> +#include <QStandardPaths> +#include <QtMath> + +const qreal zoomMultiplier = qSqrt(2.0); + +Q_LOGGING_CATEGORY(lcExample, "qt.examples.pdfviewer") + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_zoomSelector(new ZoomSelector(this)) + , m_pageSelector(new QPdfPageSelector(this)) + , m_searchModel(new QPdfSearchModel(this)) + , m_searchField(new QLineEdit(this)) + , m_document(new QPdfDocument(this)) +{ + ui->setupUi(this); + + m_zoomSelector->setMaximumWidth(150); + ui->mainToolBar->insertWidget(ui->actionZoom_In, m_zoomSelector); + + ui->mainToolBar->insertWidget(ui->actionForward, m_pageSelector); + connect(m_pageSelector, &QPdfPageSelector::currentPageChanged, this, &MainWindow::pageSelected); + m_pageSelector->setDocument(m_document); + auto nav = ui->pdfView->pageNavigator(); + connect(nav, &QPdfPageNavigator::currentPageChanged, m_pageSelector, &QPdfPageSelector::setCurrentPage); + connect(nav, &QPdfPageNavigator::backAvailableChanged, ui->actionBack, &QAction::setEnabled); + connect(nav, &QPdfPageNavigator::forwardAvailableChanged, ui->actionForward, &QAction::setEnabled); + + connect(m_zoomSelector, &ZoomSelector::zoomModeChanged, ui->pdfView, &QPdfView::setZoomMode); + connect(m_zoomSelector, &ZoomSelector::zoomFactorChanged, ui->pdfView, &QPdfView::setZoomFactor); + m_zoomSelector->reset(); + + QPdfBookmarkModel *bookmarkModel = new QPdfBookmarkModel(this); + bookmarkModel->setDocument(m_document); + + ui->bookmarkView->setModel(bookmarkModel); + connect(ui->bookmarkView, &QAbstractItemView::activated, this, &MainWindow::bookmarkSelected); + + ui->thumbnailsView->setModel(m_document->pageModel()); + + m_searchModel->setDocument(m_document); + ui->pdfView->setSearchModel(m_searchModel); + ui->searchToolBar->insertWidget(ui->actionFindPrevious, m_searchField); + connect(new QShortcut(QKeySequence::Find, this), &QShortcut::activated, this, [this]() { + m_searchField->setFocus(Qt::ShortcutFocusReason); + }); + m_searchField->setPlaceholderText(tr("Find in document")); + m_searchField->setMaximumWidth(400); + connect(m_searchField, &QLineEdit::textEdited, this, [this](const QString &text) { + m_searchModel->setSearchString(text); + ui->tabWidget->setCurrentWidget(ui->searchResultsTab); + }); + ui->searchResultsView->setModel(m_searchModel); + ui->searchResultsView->setItemDelegate(new SearchResultDelegate(this)); + connect(ui->searchResultsView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &MainWindow::searchResultSelected); + + ui->pdfView->setDocument(m_document); + + connect(ui->pdfView, &QPdfView::zoomFactorChanged, + m_zoomSelector, &ZoomSelector::setZoomFactor); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::open(const QUrl &docLocation) +{ + if (docLocation.isLocalFile()) { + m_document->load(docLocation.toLocalFile()); + pageSelected(0); + } else { + const QString message = tr("%1 is not a valid local file").arg(docLocation.toString()); + qCDebug(lcExample).noquote() << message; + QMessageBox::critical(this, tr("Failed to open"), message); + } + qCDebug(lcExample) << docLocation; +} + +void MainWindow::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(); + ui->pdfView->pageNavigator()->jump(page, {}, zoomLevel); +} + +void MainWindow::pageSelected(int page) +{ + auto nav = ui->pdfView->pageNavigator(); + nav->jump(page, {}, nav->currentZoom()); + const auto documentTitle = m_document->metaData(QPdfDocument::MetaDataField::Title).toString(); + setWindowTitle(!documentTitle.isEmpty() ? documentTitle : QStringLiteral("PDF Viewer")); + setWindowTitle(tr("%1: page %2 (%3 of %4)") + .arg(documentTitle.isEmpty() ? u"PDF Viewer"_qs : documentTitle, + m_pageSelector->currentPageLabel(), QString::number(page + 1), QString::number(m_document->pageCount()))); +} + +void MainWindow::searchResultSelected(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(previous); + if (!current.isValid()) + return; + + const int page = current.data(int(QPdfSearchModel::Role::Page)).toInt(); + const QPointF location = current.data(int(QPdfSearchModel::Role::Location)).toPointF(); + ui->pdfView->pageNavigator()->jump(page, location); + ui->pdfView->setCurrentSearchResultIndex(current.row()); +} + +void MainWindow::on_actionOpen_triggered() +{ + if (m_fileDialog == nullptr) { + m_fileDialog = new QFileDialog(this, tr("Choose a PDF"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); + m_fileDialog->setAcceptMode(QFileDialog::AcceptOpen); + m_fileDialog->setMimeTypeFilters({"application/pdf"}); + } + + if (m_fileDialog->exec() == QDialog::Accepted) { + const QUrl toOpen = m_fileDialog->selectedUrls().constFirst(); + if (toOpen.isValid()) + open(toOpen); + } +} + +void MainWindow::on_actionQuit_triggered() +{ + QApplication::quit(); +} + +void MainWindow::on_actionAbout_triggered() +{ + QMessageBox::about(this, tr("About PdfViewer"), + tr("An example using QPdfDocument")); +} + +void MainWindow::on_actionAbout_Qt_triggered() +{ + QMessageBox::aboutQt(this); +} + +void MainWindow::on_actionZoom_In_triggered() +{ + ui->pdfView->setZoomFactor(ui->pdfView->zoomFactor() * zoomMultiplier); +} + +void MainWindow::on_actionZoom_Out_triggered() +{ + ui->pdfView->setZoomFactor(ui->pdfView->zoomFactor() / zoomMultiplier); +} + +void MainWindow::on_actionPrevious_Page_triggered() +{ + auto nav = ui->pdfView->pageNavigator(); + nav->jump(nav->currentPage() - 1, {}, nav->currentZoom()); +} + +void MainWindow::on_actionNext_Page_triggered() +{ + auto nav = ui->pdfView->pageNavigator(); + nav->jump(nav->currentPage() + 1, {}, nav->currentZoom()); +} + +void MainWindow::on_thumbnailsView_activated(const QModelIndex &index) +{ + auto nav = ui->pdfView->pageNavigator(); + nav->jump(index.row(), {}, nav->currentZoom()); +} + +void MainWindow::on_actionContinuous_triggered() +{ + ui->pdfView->setPageMode(ui->actionContinuous->isChecked() ? + QPdfView::PageMode::MultiPage : + QPdfView::PageMode::SinglePage); +} + +void MainWindow::on_actionBack_triggered() +{ + ui->pdfView->pageNavigator()->back(); +} + +void MainWindow::on_actionForward_triggered() +{ + ui->pdfView->pageNavigator()->forward(); +} + +void MainWindow::on_actionFindNext_triggered() +{ + int next = ui->searchResultsView->currentIndex().row() + 1; + if (next >= m_searchModel->rowCount({})) + next = 0; + ui->searchResultsView->setCurrentIndex(m_searchModel->index(next)); +} + +void MainWindow::on_actionFindPrevious_triggered() +{ + int prev = ui->searchResultsView->currentIndex().row() - 1; + if (prev < 0) + prev = m_searchModel->rowCount({}) - 1; + ui->searchResultsView->setCurrentIndex(m_searchModel->index(prev)); +} diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.h b/examples/pdfwidgets/pdfviewer/mainwindow.h new file mode 100644 index 000000000..7f5a5dbca --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/mainwindow.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QLoggingCategory> +#include <QMainWindow> + +Q_DECLARE_LOGGING_CATEGORY(lcExample) + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} + +class QFileDialog; +class QLineEdit; +class QPdfDocument; +class QPdfPageSelector; +class QPdfSearchModel; +class QPdfView; +class QSpinBox; +QT_END_NAMESPACE + +class ZoomSelector; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +public slots: + void open(const QUrl &docLocation); + +private slots: + void bookmarkSelected(const QModelIndex &index); + void pageSelected(int page); + void searchResultSelected(const QModelIndex ¤t, const QModelIndex &previous); + + // action handlers + void on_actionOpen_triggered(); + void on_actionQuit_triggered(); + void on_actionAbout_triggered(); + void on_actionAbout_Qt_triggered(); + void on_actionZoom_In_triggered(); + void on_actionZoom_Out_triggered(); + void on_actionPrevious_Page_triggered(); + void on_actionNext_Page_triggered(); + void on_thumbnailsView_activated(const QModelIndex &index); + void on_actionContinuous_triggered(); + void on_actionBack_triggered(); + void on_actionForward_triggered(); + void on_actionFindNext_triggered(); + void on_actionFindPrevious_triggered(); + +private: + Ui::MainWindow *ui; + ZoomSelector *m_zoomSelector; + QPdfPageSelector *m_pageSelector; + QPdfSearchModel *m_searchModel; + QLineEdit *m_searchField; + QFileDialog *m_fileDialog = nullptr; + + QPdfDocument *m_document; +}; + +#endif // MAINWINDOW_H diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.ui b/examples/pdfwidgets/pdfviewer/mainwindow.ui new file mode 100644 index 000000000..881df2e1b --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/mainwindow.ui @@ -0,0 +1,438 @@ +<?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>700</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>PDF Viewer</string> + </property> + <property name="unifiedTitleAndToolBarOnMac"> + <bool>true</bool> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTabWidget" name="tabWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="tabPosition"> + <enum>QTabWidget::West</enum> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="documentMode"> + <bool>false</bool> + </property> + <widget class="QWidget" name="bookmarkTab"> + <attribute name="title"> + <string>Bookmarks</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QTreeView" name="bookmarkView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="headerHidden"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="pagesTab"> + <attribute name="title"> + <string>Pages</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QListView" name="thumbnailsView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="iconSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="movement"> + <enum>QListView::Static</enum> + </property> + <property name="resizeMode"> + <enum>QListView::Adjust</enum> + </property> + <property name="viewMode"> + <enum>QListView::IconMode</enum> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="searchResultsTab"> + <attribute name="title"> + <string>Search Results</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QListView" name="searchResultsView"> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <widget class="QPdfView" name="pdfView" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>700</width> + <height>23</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionOpen"/> + <addaction name="actionQuit"/> + </widget> + <widget class="QMenu" name="menuHelp"> + <property name="title"> + <string>Help</string> + </property> + <addaction name="actionAbout"/> + <addaction name="actionAbout_Qt"/> + </widget> + <widget class="QMenu" name="menuView"> + <property name="title"> + <string>View</string> + </property> + <addaction name="actionZoom_In"/> + <addaction name="actionZoom_Out"/> + <addaction name="actionPrevious_Page"/> + <addaction name="actionNext_Page"/> + <addaction name="separator"/> + <addaction name="actionContinuous"/> + </widget> + <addaction name="menuFile"/> + <addaction name="menuView"/> + <addaction name="menuHelp"/> + </widget> + <widget class="QToolBar" name="mainToolBar"> + <property name="movable"> + <bool>false</bool> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionOpen"/> + <addaction name="separator"/> + <addaction name="actionZoom_Out"/> + <addaction name="actionZoom_In"/> + <addaction name="separator"/> + <addaction name="actionBack"/> + <addaction name="actionForward"/> + </widget> + <widget class="QStatusBar" name="statusBar"/> + <widget class="QToolBar" name="searchToolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionFindPrevious"/> + <addaction name="actionFindNext"/> + </widget> + <action name="actionOpen"> + <property name="icon"> + <iconset theme="document-open" resource="resources.qrc"> + <normaloff>:/icons/images/document-open.svgz</normaloff>:/icons/images/document-open.svgz</iconset> + </property> + <property name="text"> + <string>Open...</string> + </property> + <property name="shortcut"> + <string>Ctrl+O</string> + </property> + </action> + <action name="actionQuit"> + <property name="icon"> + <iconset theme="application-exit"/> + </property> + <property name="text"> + <string>Quit</string> + </property> + <property name="shortcut"> + <string>Ctrl+Q</string> + </property> + </action> + <action name="actionAbout"> + <property name="icon"> + <iconset theme="help-about"/> + </property> + <property name="text"> + <string>About</string> + </property> + </action> + <action name="actionAbout_Qt"> + <property name="text"> + <string>About Qt</string> + </property> + </action> + <action name="actionZoom_In"> + <property name="icon"> + <iconset theme="zoom-in" resource="resources.qrc"> + <normaloff>:/icons/images/zoom-in.svgz</normaloff>:/icons/images/zoom-in.svgz</iconset> + </property> + <property name="text"> + <string>Zoom In</string> + </property> + <property name="shortcut"> + <string>Ctrl+=</string> + </property> + </action> + <action name="actionZoom_Out"> + <property name="icon"> + <iconset theme="zoom-out" resource="resources.qrc"> + <normaloff>:/icons/images/zoom-out.svgz</normaloff>:/icons/images/zoom-out.svgz</iconset> + </property> + <property name="text"> + <string>Zoom Out</string> + </property> + <property name="shortcut"> + <string>Ctrl+-</string> + </property> + </action> + <action name="actionPrevious_Page"> + <property name="icon"> + <iconset theme="go-previous-view-page" resource="resources.qrc"> + <normaloff>:/icons/images/go-previous-view-page.svgz</normaloff>:/icons/images/go-previous-view-page.svgz</iconset> + </property> + <property name="text"> + <string>Previous Page</string> + </property> + <property name="shortcut"> + <string>PgUp</string> + </property> + </action> + <action name="actionNext_Page"> + <property name="icon"> + <iconset theme="go-next-view-page" resource="resources.qrc"> + <normaloff>:/icons/images/go-next-view-page.svgz</normaloff>:/icons/images/go-next-view-page.svgz</iconset> + </property> + <property name="text"> + <string>Next Page</string> + </property> + <property name="shortcut"> + <string>PgDown</string> + </property> + </action> + <action name="actionContinuous"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Continuous</string> + </property> + </action> + <action name="actionBack"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/images/go-previous-view.svgz</normaloff>:/icons/images/go-previous-view.svgz</iconset> + </property> + <property name="text"> + <string>Back</string> + </property> + <property name="toolTip"> + <string>back to previous view</string> + </property> + </action> + <action name="actionForward"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/images/go-next-view.svgz</normaloff>:/icons/images/go-next-view.svgz</iconset> + </property> + <property name="text"> + <string>Forward</string> + </property> + <property name="toolTip"> + <string>forward to next view</string> + </property> + </action> + <action name="actionFindNext"> + <property name="icon"> + <iconset theme="go-down" resource=".rcc/resources.qrc"> + <normaloff>:/icons/images/go-down-search.svgz</normaloff>:/icons/images/go-down-search.svgz</iconset> + </property> + <property name="text"> + <string>Find Next</string> + </property> + <property name="toolTip"> + <string>Find the next occurrence of the phrase</string> + </property> + <property name="shortcut"> + <string>F3</string> + </property> + </action> + <action name="actionFindPrevious"> + <property name="icon"> + <iconset theme="go-up" resource=".rcc/resources.qrc"> + <normaloff>:/icons/images/go-up-search.svgz</normaloff>:/icons/images/go-up-search.svgz</iconset> + </property> + <property name="text"> + <string>Find Previous</string> + </property> + <property name="toolTip"> + <string>Find the previous occurrence of the phrase</string> + </property> + <property name="shortcut"> + <string>Shift+F3</string> + </property> + </action> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QPdfView</class> + <extends>QWidget</extends> + <header location="global">qpdfview.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources> + <include location="resources.qrc"/> + </resources> + <connections/> +</ui> diff --git a/examples/pdfwidgets/pdfviewer/pdfviewer.pro b/examples/pdfwidgets/pdfviewer/pdfviewer.pro new file mode 100644 index 000000000..5e48edeb9 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/pdfviewer.pro @@ -0,0 +1,23 @@ +TEMPLATE = app +TARGET = pdfviewer +QT += core gui widgets pdfwidgets + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + searchresultdelegate.cpp \ + zoomselector.cpp + +HEADERS += \ + mainwindow.h \ + searchresultdelegate.h \ + zoomselector.h + +FORMS += \ + mainwindow.ui + +RESOURCES += \ + resources.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/pdfwidgets/pdfviewer +INSTALLS += target diff --git a/examples/pdfwidgets/pdfviewer/resources.qrc b/examples/pdfwidgets/pdfviewer/resources.qrc new file mode 100644 index 000000000..ea408b825 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/resources.qrc @@ -0,0 +1,13 @@ +<RCC> + <qresource prefix="/icons"> + <file>images/document-open.svgz</file> + <file>images/go-down-search.svgz</file> + <file>images/go-next-view.svgz</file> + <file>images/go-previous-view.svgz</file> + <file>images/go-next-view-page.svgz</file> + <file>images/go-previous-view-page.svgz</file> + <file>images/go-up-search.svgz</file> + <file>images/zoom-in.svgz</file> + <file>images/zoom-out.svgz</file> + </qresource> +</RCC> diff --git a/examples/pdfwidgets/pdfviewer/searchresultdelegate.cpp b/examples/pdfwidgets/pdfviewer/searchresultdelegate.cpp new file mode 100644 index 000000000..87efd916d --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/searchresultdelegate.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QFontMetrics> +#include <QPainter> +#include <QPdfSearchModel> + +#include "searchresultdelegate.h" + +SearchResultDelegate::SearchResultDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +void SearchResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + const QString displayText = index.data().toString(); + const auto boldBegin = displayText.indexOf(u"<b>", 0, Qt::CaseInsensitive) + 3; + const auto boldEnd = displayText.indexOf(u"</b>", boldBegin, Qt::CaseInsensitive); + if (boldBegin >= 3 && boldEnd > boldBegin) { + const QString pageLabel = tr("Page %1: ").arg(index.data(int(QPdfSearchModel::Role::Page)).toInt()); + const QString boldText = displayText.mid(boldBegin, boldEnd - boldBegin); + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + const QFont defaultFont = painter->font(); + QFontMetrics fm = painter->fontMetrics(); + auto pageLabelWidth = fm.horizontalAdvance(pageLabel); + const int yOffset = (option.rect.height() - fm.height()) / 2 + fm.ascent(); + painter->drawText(0, option.rect.y() + yOffset, pageLabel); + QFont boldFont = defaultFont; + boldFont.setBold(true); + auto boldWidth = QFontMetrics(boldFont).horizontalAdvance(boldText); + auto prefixSuffixWidth = (option.rect.width() - pageLabelWidth - boldWidth) / 2; + painter->setFont(boldFont); + painter->drawText(pageLabelWidth + prefixSuffixWidth, option.rect.y() + yOffset, boldText); + painter->setFont(defaultFont); + const QString suffix = fm.elidedText(displayText.mid(boldEnd + 4), Qt::ElideRight, prefixSuffixWidth); + painter->drawText(pageLabelWidth + prefixSuffixWidth + boldWidth, option.rect.y() + yOffset, suffix); + const QString prefix = fm.elidedText(displayText.left(boldBegin - 3), Qt::ElideLeft, prefixSuffixWidth); + painter->drawText(pageLabelWidth + prefixSuffixWidth - fm.horizontalAdvance(prefix), + option.rect.y() + yOffset, prefix); + } else { + QStyledItemDelegate::paint(painter, option, index); + } +} diff --git a/examples/pdfwidgets/pdfviewer/searchresultdelegate.h b/examples/pdfwidgets/pdfviewer/searchresultdelegate.h new file mode 100644 index 000000000..dcf886111 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/searchresultdelegate.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SEARCHRESULTDELEGATE_H +#define SEARCHRESULTDELEGATE_H + +#include <QStyledItemDelegate> + +//! [0] +class SearchResultDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + SearchResultDelegate(QObject *parent = nullptr); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +#endif // SEARCHRESULTDELEGATE_H diff --git a/examples/pdfwidgets/pdfviewer/zoomselector.cpp b/examples/pdfwidgets/pdfviewer/zoomselector.cpp new file mode 100644 index 000000000..0b314129a --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/zoomselector.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "zoomselector.h" + +#include <QLineEdit> + +ZoomSelector::ZoomSelector(QWidget *parent) + : QComboBox(parent) +{ + setEditable(true); + + addItem(QLatin1String("Fit Width")); + addItem(QLatin1String("Fit Page")); + addItem(QLatin1String("12%")); + addItem(QLatin1String("25%")); + addItem(QLatin1String("33%")); + addItem(QLatin1String("50%")); + addItem(QLatin1String("66%")); + addItem(QLatin1String("75%")); + addItem(QLatin1String("100%")); + addItem(QLatin1String("125%")); + addItem(QLatin1String("150%")); + addItem(QLatin1String("200%")); + addItem(QLatin1String("400%")); + + connect(this, &QComboBox::currentTextChanged, + this, &ZoomSelector::onCurrentTextChanged); + + connect(lineEdit(), &QLineEdit::editingFinished, + this, [this](){onCurrentTextChanged(lineEdit()->text()); }); +} + +void ZoomSelector::setZoomFactor(qreal zoomFactor) +{ + setCurrentText(QString::number(qRound(zoomFactor * 100)) + QLatin1String("%")); +} + +void ZoomSelector::reset() +{ + setCurrentIndex(8); // 100% +} + +void ZoomSelector::onCurrentTextChanged(const QString &text) +{ + if (text == QLatin1String("Fit Width")) { + emit zoomModeChanged(QPdfView::ZoomMode::FitToWidth); + } else if (text == QLatin1String("Fit Page")) { + emit zoomModeChanged(QPdfView::ZoomMode::FitInView); + } else { + qreal factor = 1.0; + + QString withoutPercent(text); + withoutPercent.remove(QLatin1Char('%')); + + bool ok = false; + const int zoomLevel = withoutPercent.toInt(&ok); + if (ok) + factor = zoomLevel / 100.0; + + emit zoomModeChanged(QPdfView::ZoomMode::Custom); + emit zoomFactorChanged(factor); + } +} diff --git a/examples/pdfwidgets/pdfviewer/zoomselector.h b/examples/pdfwidgets/pdfviewer/zoomselector.h new file mode 100644 index 000000000..7c35bdab7 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/zoomselector.h @@ -0,0 +1,30 @@ +// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef ZOOMSELECTOR_H +#define ZOOMSELECTOR_H + +#include <QComboBox> +#include <QPdfView> + +class ZoomSelector : public QComboBox +{ + Q_OBJECT + +public: + explicit ZoomSelector(QWidget *parent = nullptr); + +public slots: + void setZoomFactor(qreal zoomFactor); + + void reset(); + +signals: + void zoomModeChanged(QPdfView::ZoomMode zoomMode); + void zoomFactorChanged(qreal zoomFactor); + +private slots: + void onCurrentTextChanged(const QString &text); +}; + +#endif // ZOOMSELECTOR_H diff --git a/examples/pdfwidgets/pdfwidgets.pro b/examples/pdfwidgets/pdfwidgets.pro new file mode 100644 index 000000000..a12dc5b86 --- /dev/null +++ b/examples/pdfwidgets/pdfwidgets.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += pdfviewer |