From fe84cdb3f27c9b26aba9a8cd20b210658a290146 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 4 Mar 2022 19:32:33 +0100 Subject: [PATCH] Signature plugin: Element manipulation --- Pdf4QtLib/sources/pdfpagecontentelements.cpp | 418 ++++++++++++++++++- Pdf4QtLib/sources/pdfpagecontentelements.h | 116 ++++- Pdf4QtViewer/pdfviewermainwindow.cpp | 1 + Pdf4QtViewer/pdfviewermainwindowlite.cpp | 1 + 4 files changed, 524 insertions(+), 12 deletions(-) diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp index 0d40673..80ca7c0 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -17,11 +17,14 @@ #include "pdfpagecontentelements.h" #include "pdfpainterutils.h" +#include "pdfdrawwidget.h" +#include "pdfdrawspacecontroller.h" #include #include #include #include +#include namespace pdf { @@ -36,6 +39,16 @@ void PDFPageContentElement::setPageIndex(PDFInteger newPageIndex) m_pageIndex = newPageIndex; } +PDFInteger PDFPageContentElement::getElementId() const +{ + return m_elementId; +} + +void PDFPageContentElement::setElementId(PDFInteger newElementId) +{ + m_elementId = newElementId; +} + const QPen& PDFPageContentStyledElement::getPen() const { return m_pen; @@ -59,6 +72,7 @@ void PDFPageContentStyledElement::setBrush(const QBrush& newBrush) PDFPageContentElementRectangle* PDFPageContentElementRectangle::clone() const { PDFPageContentElementRectangle* copy = new PDFPageContentElementRectangle(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -123,7 +137,9 @@ void PDFPageContentElementRectangle::drawPage(QPainter* painter, PDFPageContentScene::PDFPageContentScene(QObject* parent) : QObject(parent), - m_isActive(false) + m_firstFreeId(1), + m_isActive(false), + m_manipulator(this, nullptr) { } @@ -135,10 +151,22 @@ PDFPageContentScene::~PDFPageContentScene() void PDFPageContentScene::addElement(PDFPageContentElement* element) { + element->setElementId(m_firstFreeId++); m_elements.emplace_back(element); emit sceneChanged(); } +PDFPageContentElement* PDFPageContentScene::getElementById(PDFInteger id) const +{ + auto it = std::find_if(m_elements.cbegin(), m_elements.cend(), [id](const auto& element) { return element->getElementId() == id; }); + if (it != m_elements.cend()) + { + return it->get(); + } + + return nullptr; +} + void PDFPageContentScene::clear() { if (!m_elements.empty()) @@ -168,32 +196,127 @@ void PDFPageContentScene::keyReleaseEvent(QWidget* widget, QKeyEvent* event) void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - event->ignore(); + if (!isActive()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid() || isMouseGrabbed()) + { + // We to handle selecting/deselecting the active + // item. After that, accept the item. + if (info.isValid() && event->button() == Qt::LeftButton) + { + info.widgetMouseStartPos = event->pos(); + info.timer.start(); + + if (!m_manipulator.isManipulationInProgress()) + { + Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); + const bool isCtrl = keyboardModifiers.testFlag(Qt::CTRL); + const bool isShift = keyboardModifiers.testFlag(Qt::SHIFT); + + if (isCtrl && !isShift) + { + m_manipulator.select(info.hoveredElementIds); + } + else if (!isCtrl && isShift) + { + m_manipulator.deselect(info.hoveredElementIds); + } + else + { + m_manipulator.selectNew(info.hoveredElementIds); + } + } + + event->accept(); + } + + grabMouse(info, event); + } } void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) { Q_UNUSED(widget); - event->ignore(); + + if (!isActive()) + { + return; + } + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } } void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) { Q_UNUSED(widget); - event->ignore(); + + if (!isActive()) + { + return; + } + + if (isMouseGrabbed()) + { + if (event->button() == Qt::LeftButton) + { + event->accept(); + m_manipulator.finishManipulation(); + } + + ungrabMouse(info, event); + } } void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - event->ignore(); + if (!isActive()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + + + } + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } } void PDFPageContentScene::wheelEvent(QWidget* widget, QWheelEvent* event) { Q_UNUSED(widget); - event->ignore(); + + if (!isActive()) + { + return; + } + + // We will accept mouse wheel events, if we are grabbing the mouse. + // We do not want to zoom in/zoom out while grabbing. + if (isMouseGrabbed()) + { + event->accept(); + } } QString PDFPageContentScene::getTooltip() const @@ -234,6 +357,80 @@ void PDFPageContentScene::drawPage(QPainter* painter, } } +PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidget* widget, QPoint point) +{ + MouseEventInfo result; + + Q_ASSERT(isActive()); + + if (isMouseGrabbed()) + { + result = m_mouseGrabInfo.info; + result.widgetMouseCurrentPos = point; + return result; + } + + PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); + for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) + { + + } + + return result; +} + +void PDFPageContentScene::grabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_ASSERT(isActive()); + + if (event->type() == QEvent::MouseButtonDblClick) + { + // Double clicks doesn't grab the mouse + return; + } + + Q_ASSERT(event->type() == QEvent::MouseButtonPress); + + if (isMouseGrabbed()) + { + // If mouse is already grabbed, then when new mouse button is pressed, + // we just increase nesting level and accept the mouse event. We are + // accepting all mouse events, if mouse is grabbed. + ++m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + } + else if (event->isAccepted()) + { + // Event is accepted and we are not grabbing the mouse. We must start + // grabbing the mouse. + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0); + ++m_mouseGrabInfo.mouseGrabNesting; + m_mouseGrabInfo.info = info; + } +} + +void PDFPageContentScene::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_UNUSED(info); + Q_ASSERT(isActive()); + Q_ASSERT(event->type() == QEvent::MouseButtonRelease); + + if (isMouseGrabbed()) + { + // Mouse is being grabbed, decrease nesting level. We must also accept + // mouse release event, because mouse is being grabbed. + --m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + + if (!isMouseGrabbed()) + { + m_mouseGrabInfo.info = MouseEventInfo(); + } + } + + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); +} + bool PDFPageContentScene::isActive() const { return m_isActive; @@ -244,6 +441,25 @@ void PDFPageContentScene::setActive(bool newIsActive) if (m_isActive != newIsActive) { m_isActive = newIsActive; + + if (!newIsActive) + { + m_mouseGrabInfo = MouseGrabInfo(); + m_manipulator.reset(); + } + + emit sceneChanged(); + } +} + +void PDFPageContentScene::removeElementsById(const std::set& selection) +{ + const size_t oldSize = m_elements.size(); + m_elements.erase(std::remove_if(m_elements.begin(), m_elements.end(), [&selection](const auto& element){ return selection.count(element->getElementId()); }), m_elements.end()); + const size_t newSize = m_elements.size(); + + if (newSize < oldSize) + { emit sceneChanged(); } } @@ -251,6 +467,7 @@ void PDFPageContentScene::setActive(bool newIsActive) PDFPageContentElementLine* PDFPageContentElementLine::clone() const { PDFPageContentElementLine* copy = new PDFPageContentElementLine(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -328,6 +545,7 @@ PDFPageContentSvgElement::~PDFPageContentSvgElement() PDFPageContentSvgElement* PDFPageContentSvgElement::clone() const { PDFPageContentSvgElement* copy = new PDFPageContentSvgElement(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setRectangle(getRectangle()); copy->setContent(getContent()); @@ -400,6 +618,7 @@ void PDFPageContentSvgElement::setRectangle(const QRectF& newRectangle) PDFPageContentElementDot* PDFPageContentElementDot::clone() const { PDFPageContentElementDot* copy = new PDFPageContentElementDot(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -444,6 +663,7 @@ void PDFPageContentElementDot::setPoint(QPointF newPoint) PDFPageContentElementFreehandCurve* PDFPageContentElementFreehandCurve::clone() const { PDFPageContentElementFreehandCurve* copy = new PDFPageContentElementFreehandCurve(); + copy->setElementId(getElementId()); copy->setPageIndex(getPageIndex()); copy->setPen(getPen()); copy->setBrush(getBrush()); @@ -502,4 +722,186 @@ void PDFPageContentElementFreehandCurve::clear() m_curve = QPainterPath(); } +PDFPageContentElementManipulator::PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent) : + QObject(parent), + m_scene(scene), + m_isManipulationInProgress(false) +{ + +} + +void PDFPageContentElementManipulator::reset() +{ + stopManipulation(); + deselectAll(); +} + +void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes modes) +{ + bool modified = false; + + if (modes.testFlag(Clear)) + { + modified = !m_selection.empty(); + m_selection.clear(); + } + + // Is id valid? + if (id > 0) + { + if (modes.testFlag(Select)) + { + if (!isSelected(id)) + { + modified = true; + m_selection.insert(id); + } + } + + if (modes.testFlag(Deselect)) + { + if (isSelected(id)) + { + modified = true; + m_selection.erase(id); + } + } + + if (modes.testFlag(Toggle)) + { + if (isSelected(id)) + { + m_selection.erase(id); + } + else + { + m_selection.insert(id); + } + + // When toggle is performed, selection is always changed + modified = true; + } + } + + if (modified) + { + emit selectionChanged(); + } +} + +void PDFPageContentElementManipulator::update(const std::set& ids, SelectionModes modes) +{ + bool modified = false; + + if (modes.testFlag(Clear)) + { + modified = !m_selection.empty(); + m_selection.clear(); + } + + // Is id valid? + if (!ids.empty()) + { + if (modes.testFlag(Select)) + { + for (auto id : ids) + { + if (!isSelected(id)) + { + modified = true; + m_selection.insert(id); + } + } + } + + if (modes.testFlag(Deselect)) + { + for (auto id : ids) + { + if (isSelected(id)) + { + modified = true; + m_selection.erase(id); + } + } + } + + if (modes.testFlag(Toggle)) + { + for (auto id : ids) + { + if (isSelected(id)) + { + m_selection.erase(id); + } + else + { + m_selection.insert(id); + } + + // When toggle is performed, selection is always changed + modified = true; + } + } + } + + if (modified) + { + emit selectionChanged(); + } +} + +void PDFPageContentElementManipulator::select(PDFInteger id) +{ + update(id, Select); +} + +void PDFPageContentElementManipulator::select(const std::set& ids) +{ + update(ids, Select); +} + +void PDFPageContentElementManipulator::selectNew(PDFInteger id) +{ + update(id, Select | Clear); +} + +void PDFPageContentElementManipulator::selectNew(const std::set& ids) +{ + update(ids, Select | Clear); +} + +void PDFPageContentElementManipulator::deselect(PDFInteger id) +{ + update(id, Deselect); +} + +void PDFPageContentElementManipulator::deselect(const std::set& ids) +{ + update(ids, Deselect); +} + +void PDFPageContentElementManipulator::deselectAll() +{ + update(-1, Clear); +} + +void PDFPageContentElementManipulator::manipulateDeleteSelection() +{ + stopManipulation(); + m_scene->removeElementsById(m_selection); + deselectAll(); +} + +void PDFPageContentElementManipulator::stopManipulation() +{ + if (m_isManipulationInProgress) + { + m_isManipulationInProgress = false; + m_manipulatedElements.clear(); + m_manipulationModes.clear(); + emit stateChanged(); + } +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h index 567f43b..745772e 100644 --- a/Pdf4QtLib/sources/pdfpagecontentelements.h +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -25,10 +25,13 @@ #include #include +#include + class QSvgRenderer; namespace pdf { +class PDFPageContentScene; class PDF4QTLIBSHARED_EXPORT PDFPageContentElement { @@ -48,7 +51,11 @@ public: PDFInteger getPageIndex() const; void setPageIndex(PDFInteger newPageIndex); + PDFInteger getElementId() const; + void setElementId(PDFInteger newElementId); + protected: + PDFInteger m_elementId = -1; PDFInteger m_pageIndex = -1; }; @@ -201,6 +208,61 @@ private: std::unique_ptr m_renderer; }; +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementManipulator : public QObject +{ + Q_OBJECT + +public: + PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent); + + enum SelectionMode + { + NoUpdate = 0x0000, + Clear = 0x0001, ///< Clears current selection + Select = 0x0002, ///< Selects item + Deselect = 0x0004, ///< Deselects item + Toggle = 0x0008, ///< Toggles selection of the item + }; + Q_DECLARE_FLAGS(SelectionModes, SelectionMode) + + /// Returns true, if element with given id is selected + /// \param id Element id + bool isSelected(PDFInteger id) const { return m_selection.count(id); } + + /// Returns true, if selection is empty + bool isSelectionEmpty() const { return m_selection.empty(); } + + /// Clear all selection, stop manipulation + void reset(); + + void update(PDFInteger id, SelectionModes modes); + void update(const std::set& ids, SelectionModes modes); + void select(PDFInteger id); + void select(const std::set& ids); + void selectNew(PDFInteger id); + void selectNew(const std::set& ids); + void deselect(PDFInteger id); + void deselect(const std::set& ids); + void deselectAll(); + + bool isManipulationInProgress() const { return m_isManipulationInProgress; } + + void manipulateDeleteSelection(); + +signals: + void selectionChanged(); + void stateChanged(); + +private: + void stopManipulation(); + + PDFPageContentScene* m_scene; + std::set m_selection; + bool m_isManipulationInProgress; + std::vector> m_manipulatedElements; + std::map m_manipulationModes; +}; + class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, public IDocumentDrawInterface, public IDrawWidgetInputInterface @@ -216,12 +278,23 @@ public: /// \param element Element void addElement(PDFPageContentElement* element); + /// Returns element by its id (identifier number) + /// \param id Element id + PDFPageContentElement* getElementById(PDFInteger id) const; + /// Clear whole scene - remove all page content elements void clear(); /// Returns true, if scene is empty bool isEmpty() const { return m_elements.empty(); } + bool isActive() const; + void setActive(bool newIsActive); + + /// Removes elements specified in selection + /// \param selection Items to be removed + void removeElementsById(const std::set& selection); + // IDrawWidgetInputInterface interface public: virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; @@ -244,20 +317,55 @@ public: PDFTextLayoutGetter& layoutGetter, const QMatrix& pagePointToDevicePointMatrix, QList& errors) const override; - - bool isActive() const; - void setActive(bool newIsActive); - signals: /// This signal is emitted when scene has changed (including graphics) void sceneChanged(); private: + + struct MouseEventInfo + { + std::set hoveredElementIds; + QPoint widgetMouseStartPos; + QPoint widgetMouseCurrentPos; + QElapsedTimer timer; + + bool isValid() const { return !hoveredElementIds.empty(); } + }; + + MouseEventInfo getMouseEventInfo(QWidget* widget, QPoint point); + + struct MouseGrabInfo + { + MouseEventInfo info; + int mouseGrabNesting = 0; + + bool isMouseGrabbed() const { return mouseGrabNesting > 0; } + }; + + bool isMouseGrabbed() const { return m_mouseGrabInfo.isMouseGrabbed(); } + + /// Grabs mouse input, if mouse is already grabbed, or if event + /// is accepted. + /// \param info Mouse event info + /// \param event Mouse event + void grabMouse(const MouseEventInfo& info, QMouseEvent* event); + + /// Release mouse input + /// \param info Mouse event info + /// \param event Mouse event + void ungrabMouse(const MouseEventInfo& info, QMouseEvent* event); + + PDFInteger m_firstFreeId; bool m_isActive; std::vector> m_elements; std::optional m_cursor; + PDFPageContentElementManipulator m_manipulator; + MouseGrabInfo m_mouseGrabInfo; }; } // namespace pdf +Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFPageContentElementManipulator::SelectionModes) + #endif // PDFPAGECONTENTELEMENTS_H diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index 905a4fd..2a63550 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -104,6 +104,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : // Initialize toolbar icon size adjustToolbar(ui->mainToolBar); + ui->mainToolBar->setWindowTitle(tr("Standard")); // Initialize task bar progress #ifdef Q_OS_WIN diff --git a/Pdf4QtViewer/pdfviewermainwindowlite.cpp b/Pdf4QtViewer/pdfviewermainwindowlite.cpp index 68d0b8f..fb1638c 100644 --- a/Pdf4QtViewer/pdfviewermainwindowlite.cpp +++ b/Pdf4QtViewer/pdfviewermainwindowlite.cpp @@ -102,6 +102,7 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : // Initialize toolbar icon size adjustToolbar(ui->mainToolBar); + ui->mainToolBar->setWindowTitle(tr("Standard")); // Initialize task bar progress #ifdef Q_OS_WIN