Tool for text selection (first part)

This commit is contained in:
Jakub Melka
2020-01-25 17:36:25 +01:00
parent 7ad4c46124
commit 95f6135482
15 changed files with 911 additions and 14 deletions

View File

@@ -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

View File

@@ -784,6 +784,31 @@ std::vector<PDFInteger> 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;

View File

@@ -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<PDFInteger> 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;

View File

@@ -18,6 +18,7 @@
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include "pdfcompiler.h"
#include "pdfwidgettool.h"
#include <QPainter>
#include <QGridLayout>
@@ -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<BaseWidget>::PDFDrawWidgetBase(PDFWidget* widget, QWidget* par
m_mouseOperation(MouseOperation::None)
{
this->setFocusPolicy(Qt::StrongFocus);
this->setMouseTracking(true);
}
template<typename BaseWidget>
@@ -216,10 +219,21 @@ void PDFDrawWidgetBase<BaseWidget>::performMouseOperation(QPoint currentMousePos
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::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<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation> keyToOperations[] =
@@ -241,24 +255,52 @@ void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
}
}
}
updateCursor();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::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<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::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<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
case MouseOperation::Translate:
{
m_mouseOperation = MouseOperation::None;
unsetCursor();
break;
}
@@ -277,19 +318,83 @@ void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
Q_ASSERT(false);
}
updateCursor();
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::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<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::updateCursor()
{
std::optional<QCursor> 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<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::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();

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -25,6 +25,9 @@
#include <QLineEdit>
#include <QGridLayout>
#include <QPushButton>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
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<PDFFindTextTool*>(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<QCursor>& PDFToolManager::getCursor() const
{
if (PDFWidgetTool* tool = getActiveTool())
{
return tool->getCursor();
}
static const std::optional<QCursor> dummy;
return dummy;
}
void PDFToolManager::onToolActionTriggered(bool checked)
{
PDFWidgetTool* tool = m_actionsToTools.at(qobject_cast<QAction*>(sender()));
if (checked)
{
setActiveTool(tool);
}
else
{
tool->setActive(false);
}
}
} // namespace pdf

View File

@@ -22,6 +22,7 @@
#include "pdftextlayout.h"
#include <QDialog>
#include <QCursor>
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<QCursor>& 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<PDFWidgetTool*> m_toolStack;
std::optional<QCursor> 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<pdf::PDFTextSelection> 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<QCursor>& getCursor() const;
private:
void onToolActionTriggered(bool checked);
std::set<PDFWidgetTool*> m_tools;
std::array<PDFWidgetTool*, ToolEnd> m_predefinedTools;
std::map<QAction*, PDFWidgetTool*> m_actionsToTools;
};
} // namespace pdf