From a085a54b0027a6b889d428b35eae778a95e3a6bc Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Mon, 25 Dec 2023 14:39:47 +0100 Subject: [PATCH 1/2] Issue #134: Add search bar for actions --- Pdf4QtViewer/CMakeLists.txt | 2 + Pdf4QtViewer/pdfactioncombobox.cpp | 191 +++++++++++++++++++++++ Pdf4QtViewer/pdfactioncombobox.h | 64 ++++++++ Pdf4QtViewer/pdfprogramcontroller.cpp | 22 +++ Pdf4QtViewer/pdfprogramcontroller.h | 4 + Pdf4QtViewer/pdfviewermainwindow.cpp | 5 + Pdf4QtViewer/pdfviewermainwindowlite.cpp | 4 + 7 files changed, 292 insertions(+) create mode 100644 Pdf4QtViewer/pdfactioncombobox.cpp create mode 100644 Pdf4QtViewer/pdfactioncombobox.h diff --git a/Pdf4QtViewer/CMakeLists.txt b/Pdf4QtViewer/CMakeLists.txt index e15e348..e470d80 100644 --- a/Pdf4QtViewer/CMakeLists.txt +++ b/Pdf4QtViewer/CMakeLists.txt @@ -73,6 +73,8 @@ add_library(Pdf4QtViewer SHARED pdfbookmarkmanager.cpp pdfbookmarkui.h pdfbookmarkui.cpp + pdfactioncombobox.h + pdfactioncombobox.cpp ) add_compile_definitions(QT_INSTALL_DIRECTORY="${QT6_INSTALL_PREFIX}") diff --git a/Pdf4QtViewer/pdfactioncombobox.cpp b/Pdf4QtViewer/pdfactioncombobox.cpp new file mode 100644 index 0000000..b94fc9b --- /dev/null +++ b/Pdf4QtViewer/pdfactioncombobox.cpp @@ -0,0 +1,191 @@ +// Copyright (C) 2023 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfactioncombobox.h" +#include "pdfwidgetutils.h" + +#include +#include +#include +#include + +namespace pdfviewer +{ + +PDFActionComboBox::PDFActionComboBox(QWidget* parent) : + BaseClass(parent), + m_model(nullptr) +{ + setEditable(true); + lineEdit()->setPlaceholderText(tr("Find action...")); + lineEdit()->setClearButtonEnabled(true); + setMinimumWidth(pdf::PDFWidgetUtils::scaleDPI_x(this, DEFAULT_WIDTH)); + + m_model = new QStandardItemModel(this); + m_proxyModel = new QSortFilterProxyModel(this); + + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_proxyModel->setDynamicSortFilter(true); + m_proxyModel->setFilterKeyColumn(0); + m_proxyModel->setFilterRole(Qt::DisplayRole); + m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_proxyModel->setSortLocaleAware(true); + m_proxyModel->setSortRole(Qt::DisplayRole); + m_proxyModel->setSourceModel(m_model); + + setFocusPolicy(Qt::StrongFocus); + setCompleter(nullptr); + setInsertPolicy(QComboBox::NoInsert); +/* + completer()->setCompletionMode(QCompleter::PopupCompletion); + completer()->setCompletionColumn(-1); + completer()->setFilterMode(Qt::MatchContains | Qt::MatchWildcard); + completer()->setCaseSensitivity(Qt::CaseInsensitive); + completer()->setModelSorting(QCompleter::UnsortedModel);*/ + + connect(this, &PDFActionComboBox::activated, this, &PDFActionComboBox::onActionActivated); + connect(this, &PDFActionComboBox::editTextChanged, this, &PDFActionComboBox::onEditTextChanged, Qt::QueuedConnection); + + setModel(m_proxyModel); +} + +QSize PDFActionComboBox::sizeHint() const +{ + QSize sizeHint = BaseClass::sizeHint(); + sizeHint.setWidth(pdf::PDFWidgetUtils::scaleDPI_x(this, DEFAULT_WIDTH)); + return sizeHint; +} + +QSize PDFActionComboBox::minimumSizeHint() const +{ + QSize sizeHint = BaseClass::minimumSizeHint(); + sizeHint.setWidth(pdf::PDFWidgetUtils::scaleDPI_x(this, DEFAULT_WIDTH)); + return sizeHint; +} + +void PDFActionComboBox::addQuickFindAction(QAction* action) +{ + if (std::find(m_actions.begin(), m_actions.end(), action) == m_actions.end()) + { + m_actions.push_back(action); + connect(action, &QAction::changed, this, &PDFActionComboBox::onActionChanged); + updateAction(action); + } +} + +void PDFActionComboBox::onActionChanged() +{ + QAction* action = qobject_cast(sender()); + updateAction(action); +} + +void PDFActionComboBox::onActionActivated(int index) +{ + QVariant actionData = itemData(index, Qt::UserRole); + QAction* action = actionData.value(); + + lineEdit()->clear(); + setCurrentIndex(-1); + + if (action && action->isEnabled()) + { + action->trigger(); + } +} + +void PDFActionComboBox::onEditTextChanged(const QString& text) +{ + if (text.isEmpty()) + { + m_proxyModel->setFilterFixedString(QString()); + } + else if (text.contains(QChar('*')) || text.contains(QChar('?'))) + { + m_proxyModel->setFilterWildcard(text); + } + else + { + m_proxyModel->setFilterFixedString(text); + } + + if (!text.isEmpty()) + { + showPopup(); + } + else + { + hidePopup(); + } + + lineEdit()->setFocus(); + lineEdit()->setCursorPosition(text.size()); +} + +void PDFActionComboBox::updateAction(QAction* action) +{ + if (!action) + { + return; + } + + int actionIndex = findAction(action); + if (action->isEnabled()) + { + if (actionIndex == -1) + { + QStandardItem* item = new QStandardItem(action->icon(), action->text()); + item->setData(QVariant::fromValue(action), Qt::UserRole); + m_model->appendRow(item); + } + else + { + QStandardItem* item = m_model->item(actionIndex); + item->setIcon(action->icon()); + item->setText(action->text()); + } + } + else + { + // Remove action from the model + if (actionIndex != -1) + { + m_model->removeRow(actionIndex); + } + } + + setCurrentIndex(-1); +} + +int PDFActionComboBox::findAction(QAction* action) +{ + const int rowCount = m_model->rowCount(); + + for (int i = 0; i < rowCount; ++i) + { + QModelIndex index = m_model->index(i, 0); + QAction* currentAction = index.data(Qt::UserRole).value(); + + if (currentAction == action) + { + return i; + } + } + + return -1; +} + +} // namespace pdfviewer diff --git a/Pdf4QtViewer/pdfactioncombobox.h b/Pdf4QtViewer/pdfactioncombobox.h new file mode 100644 index 0000000..32709c7 --- /dev/null +++ b/Pdf4QtViewer/pdfactioncombobox.h @@ -0,0 +1,64 @@ +// Copyright (C) 2023 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFACTIONCOMBOBOX_H +#define PDFACTIONCOMBOBOX_H + +#include "pdfwidgetsglobal.h" + +#include +#include + +class QStandardItemModel; +class QSortFilterProxyModel; + +namespace pdfviewer +{ + +class PDFActionComboBox : public QComboBox +{ + Q_OBJECT + +private: + using BaseClass = QComboBox; + +public: + PDFActionComboBox(QWidget* parent); + + virtual QSize sizeHint() const override; + virtual QSize minimumSizeHint() const override; + + void addQuickFindAction(QAction* action); + +private: + static constexpr int DEFAULT_WIDTH = 220; + + void onActionChanged(); + void onActionActivated(int index); + void onEditTextChanged(const QString& text); + + void updateAction(QAction* action); + int findAction(QAction* action); + + std::vector m_actions; + QStandardItemModel* m_model; + QSortFilterProxyModel* m_proxyModel; +}; + +} // namespace pdfviewer + +#endif // PDFACTIONCOMBOBOX_H diff --git a/Pdf4QtViewer/pdfprogramcontroller.cpp b/Pdf4QtViewer/pdfprogramcontroller.cpp index fdd8059..b6bfd96 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.cpp +++ b/Pdf4QtViewer/pdfprogramcontroller.cpp @@ -43,6 +43,7 @@ #include "pdfencryptionsettingsdialog.h" #include "pdfwidgetannotation.h" #include "pdfwidgetformmanager.h" +#include "pdfactioncombobox.h" #include #include @@ -56,6 +57,8 @@ #include #include #include +#include +#include #include "pdfdbgheap.h" @@ -366,6 +369,7 @@ PDFProgramController::PDFProgramController(QObject* parent) : m_annotationManager(nullptr), m_formManager(nullptr), m_bookmarkManager(nullptr), + m_actionComboBox(nullptr), m_isBusy(false), m_isFactorySettingsBeingRestored(false), m_progress(nullptr) @@ -673,6 +677,24 @@ void PDFProgramController::initialize(Features features, } } +void PDFProgramController::initActionComboBox(PDFActionComboBox* comboBox) +{ + m_actionComboBox = comboBox; + + if (m_actionComboBox) + { + bool updatesEnabled = m_actionComboBox->updatesEnabled(); + m_actionComboBox->setUpdatesEnabled(false); + + for (QAction* action : m_actionManager->getActions()) + { + m_actionComboBox->addQuickFindAction(action); + } + + m_actionComboBox->setUpdatesEnabled(updatesEnabled); + } +} + void PDFProgramController::finishInitialization() { readSettings(Settings(WindowSettings | ActionSettings)); diff --git a/Pdf4QtViewer/pdfprogramcontroller.h b/Pdf4QtViewer/pdfprogramcontroller.h index 76507c5..fb382d1 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.h +++ b/Pdf4QtViewer/pdfprogramcontroller.h @@ -35,6 +35,7 @@ #include class QMainWindow; +class QComboBox; class QToolBar; namespace pdf @@ -53,6 +54,7 @@ class PDFViewerSettings; class PDFUndoRedoManager; class PDFRecentFileManager; class PDFTextToSpeech; +class PDFActionComboBox; class IMainWindow { @@ -291,6 +293,7 @@ public: IMainWindow* mainWindowInterface, PDFActionManager* actionManager, pdf::PDFProgress* progress); + void initActionComboBox(PDFActionComboBox* comboBox); void finishInitialization(); void writeSettings(); void resetSettings(); @@ -440,6 +443,7 @@ private: pdf::PDFWidgetAnnotationManager* m_annotationManager; pdf::PDFWidgetFormManager* m_formManager; PDFBookmarkManager* m_bookmarkManager; + PDFActionComboBox* m_actionComboBox; PDFFileInfo m_fileInfo; QFileSystemWatcher m_fileWatcher; diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index e4fbcc1..9634b97 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -41,6 +41,7 @@ #include "pdfsignaturehandler.h" #include "pdfadvancedtools.h" #include "pdfwidgetutils.h" +#include "pdfactioncombobox.h" #include #include @@ -310,6 +311,9 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : connect(m_progress, &pdf::PDFProgress::progressStep, this, &PDFViewerMainWindow::onProgressStep); connect(m_progress, &pdf::PDFProgress::progressFinished, this, &PDFViewerMainWindow::onProgressFinished); + PDFActionComboBox* actionComboBox = new PDFActionComboBox(this); + menuBar()->setCornerWidget(actionComboBox); + m_programController->finishInitialization(); updateDeveloperMenu(); @@ -319,6 +323,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : } m_actionManager->styleActions(); + m_programController->initActionComboBox(actionComboBox); } PDFViewerMainWindow::~PDFViewerMainWindow() diff --git a/Pdf4QtViewer/pdfviewermainwindowlite.cpp b/Pdf4QtViewer/pdfviewermainwindowlite.cpp index 0eb6576..385318d 100644 --- a/Pdf4QtViewer/pdfviewermainwindowlite.cpp +++ b/Pdf4QtViewer/pdfviewermainwindowlite.cpp @@ -41,6 +41,7 @@ #include "pdfsignaturehandler.h" #include "pdfadvancedtools.h" #include "pdfwidgetutils.h" +#include "pdfactioncombobox.h" #include #include @@ -228,6 +229,8 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : connect(m_progress, &pdf::PDFProgress::progressStep, this, &PDFViewerMainWindowLite::onProgressStep); connect(m_progress, &pdf::PDFProgress::progressFinished, this, &PDFViewerMainWindowLite::onProgressFinished); + PDFActionComboBox* actionComboBox = new PDFActionComboBox(this); + menuBar()->setCornerWidget(actionComboBox); m_programController->finishInitialization(); if (pdf::PDFToolManager* toolManager = m_programController->getToolManager()) @@ -236,6 +239,7 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : } m_actionManager->styleActions(); + m_programController->initActionComboBox(actionComboBox); } PDFViewerMainWindowLite::~PDFViewerMainWindowLite() From 7cb6b5bb3a0f3aaa86c1f45b5f24240788a5a955 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Tue, 26 Dec 2023 18:44:28 +0100 Subject: [PATCH 2/2] Issue #134: Bugfixing --- Pdf4QtViewer/pdfactioncombobox.cpp | 140 ++++++++++++++++------------- Pdf4QtViewer/pdfactioncombobox.h | 10 +-- RELEASES.txt | 1 + 3 files changed, 86 insertions(+), 65 deletions(-) diff --git a/Pdf4QtViewer/pdfactioncombobox.cpp b/Pdf4QtViewer/pdfactioncombobox.cpp index b94fc9b..e3cc285 100644 --- a/Pdf4QtViewer/pdfactioncombobox.cpp +++ b/Pdf4QtViewer/pdfactioncombobox.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include namespace pdfviewer { @@ -30,37 +32,26 @@ PDFActionComboBox::PDFActionComboBox(QWidget* parent) : BaseClass(parent), m_model(nullptr) { - setEditable(true); - lineEdit()->setPlaceholderText(tr("Find action...")); - lineEdit()->setClearButtonEnabled(true); + setPlaceholderText(tr("Find action...")); + setClearButtonEnabled(true); setMinimumWidth(pdf::PDFWidgetUtils::scaleDPI_x(this, DEFAULT_WIDTH)); m_model = new QStandardItemModel(this); - m_proxyModel = new QSortFilterProxyModel(this); - - m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_proxyModel->setDynamicSortFilter(true); - m_proxyModel->setFilterKeyColumn(0); - m_proxyModel->setFilterRole(Qt::DisplayRole); - m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_proxyModel->setSortLocaleAware(true); - m_proxyModel->setSortRole(Qt::DisplayRole); - m_proxyModel->setSourceModel(m_model); + QCompleter* completer = new QCompleter(m_model, this); setFocusPolicy(Qt::StrongFocus); - setCompleter(nullptr); - setInsertPolicy(QComboBox::NoInsert); -/* - completer()->setCompletionMode(QCompleter::PopupCompletion); - completer()->setCompletionColumn(-1); - completer()->setFilterMode(Qt::MatchContains | Qt::MatchWildcard); - completer()->setCaseSensitivity(Qt::CaseInsensitive); - completer()->setModelSorting(QCompleter::UnsortedModel);*/ + setCompleter(completer); - connect(this, &PDFActionComboBox::activated, this, &PDFActionComboBox::onActionActivated); - connect(this, &PDFActionComboBox::editTextChanged, this, &PDFActionComboBox::onEditTextChanged, Qt::QueuedConnection); + completer->setCompletionMode(QCompleter::PopupCompletion); + completer->setCompletionColumn(0); + completer->setCompletionRole(Qt::DisplayRole); + completer->setFilterMode(Qt::MatchContains); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); + completer->setWrapAround(false); + completer->setMaxVisibleItems(20); - setModel(m_proxyModel); + connect(this, &QLineEdit::editingFinished, this, &PDFActionComboBox::performExecuteAction, Qt::QueuedConnection); } QSize PDFActionComboBox::sizeHint() const @@ -87,54 +78,85 @@ void PDFActionComboBox::addQuickFindAction(QAction* action) } } +bool PDFActionComboBox::event(QEvent* event) +{ + if (event->type() == QEvent::ShortcutOverride) + { + QKeyEvent* keyEvent = dynamic_cast(event); + switch (keyEvent->key()) + { + case Qt::Key_Down: + case Qt::Key_Up: + event->accept(); + return true; + } + } + + if (event->type() == QEvent::KeyPress) + { + QKeyEvent* keyEvent = dynamic_cast(event); + + // Redirect up and down arrows to the completer + switch (keyEvent->key()) + { + case Qt::Key_Down: + case Qt::Key_Up: + { + if (completer()) + { + if (completer()->popup()->isVisible()) + { + QCoreApplication::sendEvent(completer()->popup(), event); + } + else + { + completer()->complete(); + } + } + return true; + } + + case Qt::Key_Enter: + case Qt::Key_Return: + clearFocus(); + return true; + + default: + break; + } + } + + return BaseClass::event(event); +} + void PDFActionComboBox::onActionChanged() { QAction* action = qobject_cast(sender()); updateAction(action); } -void PDFActionComboBox::onActionActivated(int index) +void PDFActionComboBox::performExecuteAction() { - QVariant actionData = itemData(index, Qt::UserRole); - QAction* action = actionData.value(); + QString text = this->text(); - lineEdit()->clear(); - setCurrentIndex(-1); + QAction* action = nullptr; + for (QAction* currentAction : m_actions) + { + if (currentAction->text() == text) + { + action = currentAction; + } + } - if (action && action->isEnabled()) + clear(); + completer()->setCompletionPrefix(QString()); + + if (action) { action->trigger(); } } -void PDFActionComboBox::onEditTextChanged(const QString& text) -{ - if (text.isEmpty()) - { - m_proxyModel->setFilterFixedString(QString()); - } - else if (text.contains(QChar('*')) || text.contains(QChar('?'))) - { - m_proxyModel->setFilterWildcard(text); - } - else - { - m_proxyModel->setFilterFixedString(text); - } - - if (!text.isEmpty()) - { - showPopup(); - } - else - { - hidePopup(); - } - - lineEdit()->setFocus(); - lineEdit()->setCursorPosition(text.size()); -} - void PDFActionComboBox::updateAction(QAction* action) { if (!action) @@ -166,8 +188,6 @@ void PDFActionComboBox::updateAction(QAction* action) m_model->removeRow(actionIndex); } } - - setCurrentIndex(-1); } int PDFActionComboBox::findAction(QAction* action) diff --git a/Pdf4QtViewer/pdfactioncombobox.h b/Pdf4QtViewer/pdfactioncombobox.h index 32709c7..7c3a545 100644 --- a/Pdf4QtViewer/pdfactioncombobox.h +++ b/Pdf4QtViewer/pdfactioncombobox.h @@ -22,6 +22,7 @@ #include #include +#include class QStandardItemModel; class QSortFilterProxyModel; @@ -29,18 +30,19 @@ class QSortFilterProxyModel; namespace pdfviewer { -class PDFActionComboBox : public QComboBox +class PDFActionComboBox : public QLineEdit { Q_OBJECT private: - using BaseClass = QComboBox; + using BaseClass = QLineEdit; public: PDFActionComboBox(QWidget* parent); virtual QSize sizeHint() const override; virtual QSize minimumSizeHint() const override; + virtual bool event(QEvent* event) override; void addQuickFindAction(QAction* action); @@ -48,15 +50,13 @@ private: static constexpr int DEFAULT_WIDTH = 220; void onActionChanged(); - void onActionActivated(int index); - void onEditTextChanged(const QString& text); + void performExecuteAction(); void updateAction(QAction* action); int findAction(QAction* action); std::vector m_actions; QStandardItemModel* m_model; - QSortFilterProxyModel* m_proxyModel; }; } // namespace pdfviewer diff --git a/RELEASES.txt b/RELEASES.txt index 08eb77f..d970570 100644 --- a/RELEASES.txt +++ b/RELEASES.txt @@ -1,4 +1,5 @@ CURRENT: + - Issue #134: Add search bar for actions - Issue #129: Cannot compile with lcms 2.16 - Issue #128: Create list of markup annotations - Issue #126: Remove include from main headers