diff --git a/PdfForQtLib/PdfForQtLib.pro b/PdfForQtLib/PdfForQtLib.pro index 8ad52ad..6f1da5f 100644 --- a/PdfForQtLib/PdfForQtLib.pro +++ b/PdfForQtLib/PdfForQtLib.pro @@ -227,3 +227,11 @@ qt_libraries.files = $$[QT_INSTALL_BINS]/Qt?Widgets$${SUFFIX}.dll \ $$[QT_INSTALL_BINS]/Qt?Svg$${SUFFIX}.dll qt_libraries.path = $$DESTDIR/install INSTALLS += qt_libraries + +qt_plugin_platform.files = $$[QT_INSTALL_PLUGINS]/platforms/qwindows$${SUFFIX}.dll +qt_plugin_platform.path = $$DESTDIR/install/platforms +INSTALLS += qt_plugin_platform + +qt_plugin_iconengine.files = $$[QT_INSTALL_PLUGINS]/iconengines/qsvgicon$${SUFFIX}.dll +qt_plugin_iconengine.path = $$DESTDIR/install/iconengines +INSTALLS += qt_plugin_iconengine diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp index 9e89ef2..7526171 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp @@ -784,6 +784,31 @@ std::vector PDFDrawWidgetProxy::getPagesIntersectingRect(QRect rect) return pages; } +PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const +{ + // Iterate trough pages, place them and test, if they intersects with rectangle + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + if (placedRect.contains(point)) + { + if (pagePoint) + { + const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); + QMatrix matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, placedRect).inverted(); + *pagePoint = matrix.map(point); + } + + return item.pageIndex; + } + } + + return -1; +} + QRect PDFDrawWidgetProxy::getPagesIntersectingRectBoundingBox(QRect rect) const { QRect resultRect; diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.h b/PdfForQtLib/sources/pdfdrawspacecontroller.h index 215b951..16ca140 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.h +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.h @@ -35,7 +35,6 @@ namespace pdf class PDFProgress; class PDFWidget; class IDrawWidget; -class PDFWidgetTool; class PDFCMSManager; class PDFTextLayoutGetter; class PDFAsynchronousPageCompiler; @@ -269,6 +268,13 @@ public: /// \param rect Rectangle to test std::vector getPagesIntersectingRect(QRect rect) const; + /// Returns page, under which is point. If no page is under the point, + /// then -1 is returned. Point is in widget coordinates. If \p pagePoint + /// is not nullptr, then point in page coordinate space is set here. + /// \param point Point + /// \param pagePoint Point in page coordinate system + PDFInteger getPageUnderPoint(QPoint point, QPointF* pagePoint) const; + /// Returns bounding box of pages, which are intersecting rectangle (even partially) /// \param rect Rectangle to test QRect getPagesIntersectingRectBoundingBox(QRect rect) const; diff --git a/PdfForQtLib/sources/pdfdrawwidget.cpp b/PdfForQtLib/sources/pdfdrawwidget.cpp index 8dfd62d..a710c3e 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.cpp +++ b/PdfForQtLib/sources/pdfdrawwidget.cpp @@ -18,6 +18,7 @@ #include "pdfdrawwidget.h" #include "pdfdrawspacecontroller.h" #include "pdfcompiler.h" +#include "pdfwidgettool.h" #include #include @@ -31,6 +32,7 @@ namespace pdf PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent) : QWidget(parent), m_cmsManager(cmsManager), + m_toolManager(nullptr), m_drawWidget(nullptr), m_horizontalScrollBar(nullptr), m_verticalScrollBar(nullptr), @@ -177,6 +179,7 @@ PDFDrawWidgetBase::PDFDrawWidgetBase(PDFWidget* widget, QWidget* par m_mouseOperation(MouseOperation::None) { this->setFocusPolicy(Qt::StrongFocus); + this->setMouseTracking(true); } template @@ -216,10 +219,21 @@ void PDFDrawWidgetBase::performMouseOperation(QPoint currentMousePos template void PDFDrawWidgetBase::keyPressEvent(QKeyEvent* event) { - QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); event->ignore(); + // Try to pass event to tool manager + if (PDFToolManager* toolManager = getPDFWidget()->getToolManager()) + { + toolManager->keyPressEvent(this, event); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + // Vertical navigation + QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); if (verticalScrollbar->isVisible()) { constexpr std::pair keyToOperations[] = @@ -241,24 +255,52 @@ void PDFDrawWidgetBase::keyPressEvent(QKeyEvent* event) } } } + + updateCursor(); } template void PDFDrawWidgetBase::mousePressEvent(QMouseEvent* event) { + event->ignore(); + + // Try to pass event to tool manager + if (PDFToolManager* toolManager = getPDFWidget()->getToolManager()) + { + toolManager->mousePressEvent(this, event); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + if (event->button() == Qt::LeftButton) { m_mouseOperation = MouseOperation::Translate; m_lastMousePosition = event->pos(); - setCursor(Qt::ClosedHandCursor); } + updateCursor(); event->accept(); } template void PDFDrawWidgetBase::mouseReleaseEvent(QMouseEvent* event) { + event->ignore(); + + // Try to pass event to tool manager + if (PDFToolManager* toolManager = getPDFWidget()->getToolManager()) + { + toolManager->mouseReleaseEvent(this, event); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + performMouseOperation(event->pos()); switch (m_mouseOperation) @@ -269,7 +311,6 @@ void PDFDrawWidgetBase::mouseReleaseEvent(QMouseEvent* event) case MouseOperation::Translate: { m_mouseOperation = MouseOperation::None; - unsetCursor(); break; } @@ -277,19 +318,83 @@ void PDFDrawWidgetBase::mouseReleaseEvent(QMouseEvent* event) Q_ASSERT(false); } + updateCursor(); event->accept(); } template void PDFDrawWidgetBase::mouseMoveEvent(QMouseEvent* event) { + event->ignore(); + + // Try to pass event to tool manager + if (PDFToolManager* toolManager = getPDFWidget()->getToolManager()) + { + toolManager->mouseMoveEvent(this, event); + if (event->isAccepted()) + { + updateCursor(); + return; + } + } + performMouseOperation(event->pos()); + updateCursor(); event->accept(); } +template +void PDFDrawWidgetBase::updateCursor() +{ + std::optional cursor; + if (PDFToolManager* toolManager = m_widget->getToolManager()) + { + cursor = toolManager->getCursor(); + } + + if (!cursor) + { + switch (m_mouseOperation) + { + case MouseOperation::None: + cursor = QCursor(Qt::OpenHandCursor); + break; + + case MouseOperation::Translate: + cursor = QCursor(Qt::ClosedHandCursor); + break; + + default: + Q_ASSERT(false); + break; + } + } + + if (cursor) + { + this->setCursor(*cursor); + } + else + { + this->unsetCursor(); + } +} + template void PDFDrawWidgetBase::wheelEvent(QWheelEvent* event) { + event->ignore(); + + // Try to pass event to tool manager + if (PDFToolManager* toolManager = getPDFWidget()->getToolManager()) + { + toolManager->wheelEvent(this, event); + if (event->isAccepted()) + { + return; + } + } + Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy(); diff --git a/PdfForQtLib/sources/pdfdrawwidget.h b/PdfForQtLib/sources/pdfdrawwidget.h index fd60634..3df4130 100644 --- a/PdfForQtLib/sources/pdfdrawwidget.h +++ b/PdfForQtLib/sources/pdfdrawwidget.h @@ -29,6 +29,7 @@ namespace pdf { class PDFDocument; class PDFCMSManager; +class PDFToolManager; class PDFDrawWidget; class PDFDrawWidgetProxy; @@ -77,6 +78,7 @@ public: void updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit); const PDFCMSManager* getCMSManager() const { return m_cmsManager; } + PDFToolManager* getToolManager() const { return m_toolManager; } IDrawWidget* getDrawWidget() const { return m_drawWidget; } QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; } QScrollBar* getVerticalScrollbar() const { return m_verticalScrollBar; } @@ -84,6 +86,8 @@ public: const PageRenderingErrors* getPageRenderingErrors() const { return &m_pageRenderingErrors; } int getPageRenderingErrorCount() const; + void setToolManager(PDFToolManager* toolManager) { m_toolManager = toolManager; } + signals: void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount); @@ -95,6 +99,7 @@ private: IDrawWidget* createDrawWidget(RendererEngine rendererEngine, int samplesCount); const PDFCMSManager* m_cmsManager; + PDFToolManager* m_toolManager; IDrawWidget* m_drawWidget; QScrollBar* m_horizontalScrollBar; QScrollBar* m_verticalScrollBar; @@ -125,6 +130,8 @@ protected: PDFWidget* getPDFWidget() const { return m_widget; } private: + void updateCursor(); + enum class MouseOperation { None, diff --git a/PdfForQtLib/sources/pdftextlayout.cpp b/PdfForQtLib/sources/pdftextlayout.cpp index b6960f5..1e22c2c 100644 --- a/PdfForQtLib/sources/pdftextlayout.cpp +++ b/PdfForQtLib/sources/pdftextlayout.cpp @@ -349,6 +349,19 @@ qint64 PDFTextLayout::getMemoryConsumptionEstimate() const return estimate; } +bool PDFTextLayout::isHoveringOverTextBlock(const QPointF& point) const +{ + for (const PDFTextBlock& block : m_blocks) + { + if (block.getBoundingBox().contains(point)) + { + return true; + } + } + + return false; +} + QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout) { stream >> layout.m_characters; diff --git a/PdfForQtLib/sources/pdftextlayout.h b/PdfForQtLib/sources/pdftextlayout.h index f7451ae..3691417 100644 --- a/PdfForQtLib/sources/pdftextlayout.h +++ b/PdfForQtLib/sources/pdftextlayout.h @@ -200,6 +200,9 @@ struct PDFTextSelectionColoredItem } + bool operator==(const PDFTextSelectionColoredItem&) const = default; + bool operator!=(const PDFTextSelectionColoredItem&) const = default; + inline bool operator<(const PDFTextSelectionColoredItem& other) const { return std::tie(start, end) < std::tie(other.start, other.end); } PDFCharacterPointer start; @@ -217,6 +220,9 @@ public: using iterator = PDFTextSelectionColoredItems::const_iterator; + bool operator==(const PDFTextSelection&) const = default; + bool operator!=(const PDFTextSelection&) const = default; + /// Adds text selection items to selection /// \param items Items /// \param color Color for items (must include alpha channel) @@ -232,6 +238,9 @@ public: /// Returns iterator to end of page range iterator end(PDFInteger pageIndex) const; + /// Returns true, if text selection is empty + bool isEmpty() const { return m_items.empty(); } + private: PDFTextSelectionColoredItems m_items; }; @@ -327,6 +336,9 @@ public: /// Returns recognized text blocks const PDFTextBlocks& getTextBlocks() const { return m_blocks; } + /// Returns true, if given point is pointing to some text block + bool isHoveringOverTextBlock(const QPointF& point) const; + friend QDataStream& operator<<(QDataStream& stream, const PDFTextLayout& layout); friend QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout); diff --git a/PdfForQtLib/sources/pdfwidgettool.cpp b/PdfForQtLib/sources/pdfwidgettool.cpp index 2797ae7..0446add 100644 --- a/PdfForQtLib/sources/pdfwidgettool.cpp +++ b/PdfForQtLib/sources/pdfwidgettool.cpp @@ -25,6 +25,9 @@ #include #include #include +#include +#include +#include namespace pdf { @@ -33,6 +36,17 @@ PDFWidgetTool::PDFWidgetTool(PDFDrawWidgetProxy* proxy, QObject* parent) : BaseClass(parent), m_active(false), m_document(nullptr), + m_action(nullptr), + m_proxy(proxy) +{ + +} + +PDFWidgetTool::PDFWidgetTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) : + BaseClass(parent), + m_active(false), + m_document(nullptr), + m_action(action), m_proxy(proxy) { @@ -62,6 +76,7 @@ void PDFWidgetTool::setDocument(const PDFDocument* document) // We must turn off the tool, if we are changing the document setActive(false); m_document = document; + updateActions(); } } @@ -81,17 +96,57 @@ void PDFWidgetTool::setActive(bool active) } setActiveImpl(active); + updateActions(); m_proxy->repaintNeeded(); emit toolActivityChanged(active); } } +void PDFWidgetTool::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetTool::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + void PDFWidgetTool::setActiveImpl(bool active) { Q_UNUSED(active); } +void PDFWidgetTool::updateActions() +{ + if (m_action) + { + m_action->setChecked(isActive()); + m_action->setEnabled(m_document); + } +} + PDFFindTextTool::PDFFindTextTool(PDFDrawWidgetProxy* proxy, QAction* prevAction, QAction* nextAction, QObject* parent, QWidget* parentDialog) : BaseClass(proxy, parent), m_prevAction(prevAction), @@ -143,7 +198,7 @@ void PDFFindTextTool::setActiveImpl(bool active) getProxy()->getTextLayoutCompiler()->makeTextLayout(); // Create dialog - m_dialog = new QDialog(m_parentDialog, Qt::Tool); + m_dialog = new QDialog(m_parentDialog, Qt::Popup | Qt::CustomizeWindowHint | Qt::WindowTitleHint); m_dialog->setWindowTitle(tr("Find")); QGridLayout* layout = new QGridLayout(m_dialog); @@ -164,6 +219,9 @@ void PDFFindTextTool::setActiveImpl(bool active) m_previousButton->setDefault(false); m_nextButton->setDefault(false); + m_previousButton->setShortcut(m_prevAction->shortcut()); + m_nextButton->setShortcut(m_nextAction->shortcut()); + connect(m_previousButton, &QPushButton::clicked, m_prevAction, &QAction::trigger); connect(m_nextButton, &QPushButton::clicked, m_nextAction, &QAction::trigger); connect(m_findTextEdit, &QLineEdit::editingFinished, this, &PDFFindTextTool::onSearchText); @@ -184,6 +242,8 @@ void PDFFindTextTool::setActiveImpl(bool active) m_dialog->show(); m_dialog->move(topRightParent - QPoint(m_dialog->width() * 1.1, 0)); + m_dialog->setFocus(); + m_findTextEdit->setFocus(); connect(m_dialog, &QDialog::rejected, this, [this] { setActive(false); }); } else @@ -329,6 +389,8 @@ void PDFFindTextTool::performSearch() void PDFFindTextTool::updateActions() { + BaseClass::updateActions(); + const bool isActive = this->isActive(); const bool hasResults = !m_findResults.empty(); const bool enablePrevious = isActive && hasResults; @@ -384,15 +446,161 @@ PDFTextSelection PDFFindTextTool::getTextSelectionImpl() const return result; } -PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, QAction* findPreviousAction, QAction* findNextAction, QObject* parent, QWidget* parentDialog) : +PDFSelectTextTool::PDFSelectTextTool(PDFDrawWidgetProxy* proxy, QAction* action, QAction* selectAllAction, QAction* deselectAction, QObject* parent) : + BaseClass(proxy, action, parent), + m_selectAllAction(selectAllAction), + m_deselectAction(deselectAction), + m_isCursorOverText(false) +{ + updateActions(); +} + +void PDFSelectTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1) + { + m_selectionInfo.pageIndex = pageIndex; + m_selectionInfo.selectionStartPoint = pagePoint; + event->accept(); + } + else + { + m_selectionInfo = SelectionInfo(); + } + + setSelection(pdf::PDFTextSelection()); + updateCursor(); + } +} + +void PDFSelectTextTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + if (m_selectionInfo.pageIndex != -1) + { + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + + if (m_selectionInfo.pageIndex == pageIndex) + { + // Jakub Melka: handle the selection + } + else + { + setSelection(pdf::PDFTextSelection()); + } + + m_selectionInfo = SelectionInfo(); + event->accept(); + updateCursor(); + } + } +} + +void PDFSelectTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + PDFTextLayout textLayout = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); + m_isCursorOverText = textLayout.isHoveringOverTextBlock(pagePoint); + + if (event->button() == Qt::LeftButton) + { + if (m_selectionInfo.pageIndex != -1) + { + if (m_selectionInfo.pageIndex == pageIndex) + { + // Jakub Melka: handle the selection + } + else + { + setSelection(pdf::PDFTextSelection()); + } + + event->accept(); + } + } + + updateCursor(); +} + +void PDFSelectTextTool::setActiveImpl(bool active) +{ + if (active) + { + pdf::PDFAsynchronousTextLayoutCompiler* compiler = getProxy()->getTextLayoutCompiler(); + if (!compiler->isTextLayoutReady()) + { + compiler->makeTextLayout(); + } + } + else + { + // Just clear the text selection + setSelection(PDFTextSelection()); + } +} + +void PDFSelectTextTool::updateActions() +{ + BaseClass::updateActions(); + + m_selectAllAction->setEnabled(isActive()); + m_deselectAction->setEnabled(isActive() && !m_textSelection.isEmpty()); +} + +void PDFSelectTextTool::updateCursor() +{ + if (isActive()) + { + if (m_isCursorOverText) + { + setCursor(QCursor(Qt::IBeamCursor)); + } + else + { + setCursor(QCursor(Qt::ArrowCursor)); + } + } +} + +void PDFSelectTextTool::setSelection(PDFTextSelection&& textSelection) +{ + if (m_textSelection != textSelection) + { + m_textSelection = qMove(textSelection); + getProxy()->repaintNeeded(); + updateActions(); + } +} + +PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObject* parent, QWidget* parentDialog) : BaseClass(parent), m_predefinedTools() { - m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, findPreviousAction, findNextAction, this, parentDialog); + m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, actions.findPrevAction, actions.findNextAction, this, parentDialog); + m_predefinedTools[SelectTextTool] = new PDFSelectTextTool(proxy, actions.selectTextToolAction, actions.selectAllAction, actions.deselectAction, this); for (PDFWidgetTool* tool : m_predefinedTools) { m_tools.insert(tool); + + if (QAction* action = tool->getAction()) + { + m_actionsToTools[action] = tool; + connect(action, &QAction::triggered, this, &PDFToolManager::onToolActionTriggered); + } } } @@ -404,6 +612,22 @@ void PDFToolManager::setDocument(const PDFDocument* document) } } +void PDFToolManager::setActiveTool(PDFWidgetTool* tool) +{ + PDFWidgetTool* activeTool = getActiveTool(); + if (activeTool && activeTool != tool) + { + activeTool->setActive(false); + } + + Q_ASSERT(!getActiveTool()); + + if (tool) + { + tool->setActive(true); + } +} + PDFWidgetTool* PDFToolManager::getActiveTool() const { for (PDFWidgetTool* tool : m_tools) @@ -422,4 +646,88 @@ PDFFindTextTool* PDFToolManager::getFindTextTool() const return qobject_cast(m_predefinedTools[FindTextTool]); } +void PDFToolManager::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + event->ignore(); + + // Escape key cancels current tool + PDFWidgetTool* activeTool = getActiveTool(); + if (event->key() == Qt::Key_Escape && activeTool) + { + activeTool->setActive(false); + event->accept(); + return; + } + + if (activeTool) + { + activeTool->keyPressEvent(widget, event); + } +} + +void PDFToolManager::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->mousePressEvent(widget, event); + } +} + +void PDFToolManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->mouseReleaseEvent(widget, event); + } +} + +void PDFToolManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->mouseMoveEvent(widget, event); + } +} + +void PDFToolManager::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->wheelEvent(widget, event); + } +} + +const std::optional& PDFToolManager::getCursor() const +{ + if (PDFWidgetTool* tool = getActiveTool()) + { + return tool->getCursor(); + } + + static const std::optional dummy; + return dummy; +} + +void PDFToolManager::onToolActionTriggered(bool checked) +{ + PDFWidgetTool* tool = m_actionsToTools.at(qobject_cast(sender())); + if (checked) + { + setActiveTool(tool); + } + else + { + tool->setActive(false); + } +} + + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfwidgettool.h b/PdfForQtLib/sources/pdfwidgettool.h index 2bd3381..b0822eb 100644 --- a/PdfForQtLib/sources/pdfwidgettool.h +++ b/PdfForQtLib/sources/pdfwidgettool.h @@ -22,6 +22,7 @@ #include "pdftextlayout.h" #include +#include class QCheckBox; @@ -40,6 +41,7 @@ private: public: explicit PDFWidgetTool(PDFDrawWidgetProxy* proxy, QObject* parent); + explicit PDFWidgetTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent); virtual ~PDFWidgetTool(); virtual void drawPage(QPainter* painter, @@ -61,24 +63,62 @@ public: /// Returns true, if tool is active bool isActive() const { return m_active; } + /// Returns action for activating/deactivating this tool + QAction* getAction() const { return m_action; } + + /// Handles key press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event); + + /// Handles mouse press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse release event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse move event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse wheel event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void wheelEvent(QWidget* widget, QWheelEvent* event); + + /// Returns actual cursor defined by the tool. Cursor can be undefined, + /// in this case, optional will be set to nullopt. + const std::optional& getCursor() const { return m_cursor; } + signals: void toolActivityChanged(bool active); protected: virtual void setActiveImpl(bool active); + virtual void updateActions(); PDFDrawWidgetProxy* getProxy() const { return m_proxy; } + inline void setCursor(QCursor cursor) { m_cursor = qMove(cursor); } + inline void unsetCursor() { m_cursor = std::nullopt; } + private: bool m_active; const PDFDocument* m_document; + QAction* m_action; PDFDrawWidgetProxy* m_proxy; std::vector m_toolStack; + std::optional m_cursor; }; /// Simple tool for find text in PDF document. It is much simpler than advanced /// search and can't search using regular expressions. -class PDFFORQTLIBSHARED_EXPORT PDFFindTextTool : public PDFWidgetTool +class PDFFindTextTool : public PDFWidgetTool { Q_OBJECT @@ -102,6 +142,7 @@ public: protected: virtual void setActiveImpl(bool active) override; + virtual void updateActions() override; private: void onSearchText(); @@ -109,7 +150,6 @@ private: void onActionNext(); void performSearch(); - void updateActions(); void updateResultsUI(); void updateTitle(); void clearResults(); @@ -142,6 +182,45 @@ private: mutable pdf::PDFCachedItem m_textSelection; }; +/// Tool for selection of text in document +class PDFSelectTextTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + /// Construct new text selection tool + /// \param proxy Draw widget proxy + /// \param parent Parent object + explicit PDFSelectTextTool(PDFDrawWidgetProxy* proxy, QAction* action, QAction* selectAllAction,QAction* deselectAction, QObject* parent); + + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + +protected: + virtual void setActiveImpl(bool active) override; + virtual void updateActions() override; + +private: + void updateCursor(); + void setSelection(pdf::PDFTextSelection&& textSelection); + + struct SelectionInfo + { + PDFInteger pageIndex = -1; + QPointF selectionStartPoint; + }; + + QAction* m_selectAllAction; + QAction* m_deselectAction; + pdf::PDFTextSelection m_textSelection; + SelectionInfo m_selectionInfo; + bool m_isCursorOverText; +}; + /// Manager used for managing tools, their activity, availability /// and other settings. It also defines a predefined set of tools, /// available for various purposes (text searching, magnifier tool etc.) @@ -153,13 +232,21 @@ private: using BaseClass = QObject; public: + struct Actions + { + QAction* findPrevAction = nullptr; ///< Action for navigating to previous result + QAction* findNextAction = nullptr; ///< Action for navigating to next result + QAction* selectTextToolAction = nullptr; + QAction* selectAllAction = nullptr; + QAction* deselectAction = nullptr; + }; + /// Construct new text search tool /// \param proxy Draw widget proxy - /// \param prevAction Action for navigating to previous result - /// \param nextAction Action for navigating to next result + /// \param actions Actions /// \param parent Parent object /// \param parentDialog Paret dialog for tool dialog - explicit PDFToolManager(PDFDrawWidgetProxy* proxy, QAction* findPreviousAction, QAction* findNextAction, QObject* parent, QWidget* parentDialog); + explicit PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObject* parent, QWidget* parentDialog); /// Sets document /// \param document Document @@ -168,9 +255,13 @@ public: enum PredefinedTools { FindTextTool, + SelectTextTool, ToolEnd }; + /// Sets active tool + void setActiveTool(PDFWidgetTool* tool); + /// Returns first active tool from tool set. If no tool is active, /// then nullptr is returned. PDFWidgetTool* getActiveTool() const; @@ -178,9 +269,41 @@ public: /// Returns find text tool PDFFindTextTool* getFindTextTool() const; + /// Handles key press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void keyPressEvent(QWidget* widget, QKeyEvent* event); + + /// Handles mouse press event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void mousePressEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse release event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void mouseReleaseEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse move event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void mouseMoveEvent(QWidget* widget, QMouseEvent* event); + + /// Handles mouse wheel event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + void wheelEvent(QWidget* widget, QWheelEvent* event); + + /// Returns actual cursor defined by the tool. Cursor can be undefined, + /// in this case, optional will be set to nullopt. + const std::optional& getCursor() const; + private: + void onToolActionTriggered(bool checked); + std::set m_tools; std::array m_predefinedTools; + std::map m_actionsToTools; }; } // namespace pdf diff --git a/PdfForQtViewer/pdfforqtviewer.qrc b/PdfForQtViewer/pdfforqtviewer.qrc index 6d8a44d..79263c2 100644 --- a/PdfForQtViewer/pdfforqtviewer.qrc +++ b/PdfForQtViewer/pdfforqtviewer.qrc @@ -31,5 +31,6 @@ resources/find-advanced.svg resources/find-next.svg resources/find-previous.svg + resources/select-text.svg diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 17ed13b..70971b0 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -35,6 +35,7 @@ #include "pdfutils.h" #include "pdfsendmail.h" #include "pdfexecutionpolicy.h" +#include "pdfwidgetutils.h" #include #include @@ -85,6 +86,10 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : { ui->setupUi(this); + // Initialize toolbar icon size + QSize iconSize = PDFWidgetUtils::scaleDPI(this, QSize(24, 24)); + ui->mainToolBar->setIconSize(iconSize); + // Initialize task bar progress m_progressTaskbarIndicator = m_taskbarButton->progress(); @@ -97,6 +102,8 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : ui->actionFind->setShortcut(QKeySequence::Find); ui->actionFindPrevious->setShortcut(QKeySequence::FindPrevious); ui->actionFindNext->setShortcut(QKeySequence::FindNext); + ui->actionSelectTextAll->setShortcut(QKeySequence::SelectAll); + ui->actionDeselectText->setShortcut(QKeySequence::Deselect); connect(ui->actionOpen, &QAction::triggered, this, &PDFViewerMainWindow::onActionOpenTriggered); connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered); @@ -160,6 +167,10 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : ui->mainToolBar->addAction(ui->actionFitPage); ui->mainToolBar->addAction(ui->actionFitWidth); ui->mainToolBar->addAction(ui->actionFitHeight); + ui->mainToolBar->addSeparator(); + + // Tools + ui->mainToolBar->addAction(ui->actionSelectText); connect(ui->actionZoom_In, &QAction::triggered, this, [this] { m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::ZoomIn); }); connect(ui->actionZoom_Out, &QAction::triggered, this, [this] { m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::ZoomOut); }); @@ -212,7 +223,14 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : m_sidebarDockWidget->toggleViewAction()->setObjectName("actionSidebar"); // Initialize tools - m_toolManager = new pdf::PDFToolManager(m_pdfWidget->getDrawWidgetProxy(), ui->actionFindPrevious, ui->actionFindNext, this, this); + pdf::PDFToolManager::Actions actions; + actions.findPrevAction = ui->actionFindPrevious; + actions.findNextAction = ui->actionFindNext; + actions.selectTextToolAction = ui->actionSelectText; + actions.selectAllAction = ui->actionSelectTextAll; + actions.deselectAction = ui->actionDeselectText; + m_toolManager = new pdf::PDFToolManager(m_pdfWidget->getDrawWidgetProxy(), actions, this, this); + m_pdfWidget->setToolManager(m_toolManager); connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFViewerMainWindow::onDrawSpaceChanged); connect(m_pdfWidget->getDrawWidgetProxy(), &pdf::PDFDrawWidgetProxy::pageLayoutChanged, this, &PDFViewerMainWindow::onPageLayoutChanged); @@ -223,7 +241,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : connect(m_progress, &pdf::PDFProgress::progressFinished, this, &PDFViewerMainWindow::onProgressFinished); connect(&m_futureWatcher, &QFutureWatcher::finished, this, &PDFViewerMainWindow::onDocumentReadingFinished); connect(this, &PDFViewerMainWindow::queryPasswordRequest, this, &PDFViewerMainWindow::onQueryPasswordRequest, Qt::BlockingQueuedConnection); - connect(ui->actionFind, &QAction::triggered, this, [this] { m_toolManager->getFindTextTool()->setActive(true); }); + connect(ui->actionFind, &QAction::triggered, this, [this] { m_toolManager->setActiveTool(m_toolManager->getFindTextTool()); }); readActionSettings(); updatePageLayoutActions(); diff --git a/PdfForQtViewer/pdfviewermainwindow.ui b/PdfForQtViewer/pdfviewermainwindow.ui index 91a3860..ebfb30c 100644 --- a/PdfForQtViewer/pdfviewermainwindow.ui +++ b/PdfForQtViewer/pdfviewermainwindow.ui @@ -103,6 +103,11 @@ + + + + + @@ -376,6 +381,28 @@ Find Next + + + true + + + + :/resources/select-text.svg:/resources/select-text.svg + + + Select text + + + + + Select All + + + + + Deselect + + diff --git a/PdfForQtViewer/pdfwidgetutils.cpp b/PdfForQtViewer/pdfwidgetutils.cpp index 2eae984..5e90a91 100644 --- a/PdfForQtViewer/pdfwidgetutils.cpp +++ b/PdfForQtViewer/pdfwidgetutils.cpp @@ -63,4 +63,17 @@ void PDFWidgetUtils::scaleWidget(QWidget* widget, QSize unscaledSize) widget->resize(width, height); } +QSize PDFWidgetUtils::scaleDPI(QWidget* widget, QSize unscaledSize) +{ + const double logicalDPI_x = widget->logicalDpiX(); + const double logicalDPI_y = widget->logicalDpiY(); + const double defaultDPI_x = qt_default_dpi_x(); + const double defaultDPI_y = qt_default_dpi_y(); + + const int width = (logicalDPI_x / defaultDPI_x) * unscaledSize.width(); + const int height = (logicalDPI_y / defaultDPI_y) * unscaledSize.height(); + + return QSize(width, height); +} + } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfwidgetutils.h b/PdfForQtViewer/pdfwidgetutils.h index 25611fa..b413e16 100644 --- a/PdfForQtViewer/pdfwidgetutils.h +++ b/PdfForQtViewer/pdfwidgetutils.h @@ -40,6 +40,11 @@ public: /// \param widget Widget to be scaled /// \param unscaledSize Unscaled size of the widget static void scaleWidget(QWidget* widget, QSize unscaledSize); + + /// Scales size based on DPI + /// \param widget Widget, from which we get DPI + /// \param unscaledSize Unscaled size + static QSize scaleDPI(QWidget* widget, QSize unscaledSize); }; } // namespace pdfviewer diff --git a/PdfForQtViewer/resources/select-text.svg b/PdfForQtViewer/resources/select-text.svg new file mode 100644 index 0000000..c9f5961 --- /dev/null +++ b/PdfForQtViewer/resources/select-text.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + abc + + + + + +