Signature plugin: Element manipulation

This commit is contained in:
Jakub Melka 2022-03-04 19:32:33 +01:00
parent f34f10ebb7
commit fe84cdb3f2
4 changed files with 524 additions and 12 deletions

View File

@ -17,11 +17,14 @@
#include "pdfpagecontentelements.h"
#include "pdfpainterutils.h"
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include <QPainter>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QSvgRenderer>
#include <QApplication>
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<PDFInteger>& 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<PDFInteger>& 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<PDFInteger>& ids)
{
update(ids, Select);
}
void PDFPageContentElementManipulator::selectNew(PDFInteger id)
{
update(id, Select | Clear);
}
void PDFPageContentElementManipulator::selectNew(const std::set<PDFInteger>& ids)
{
update(ids, Select | Clear);
}
void PDFPageContentElementManipulator::deselect(PDFInteger id)
{
update(id, Deselect);
}
void PDFPageContentElementManipulator::deselect(const std::set<PDFInteger>& 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

View File

@ -25,10 +25,13 @@
#include <QCursor>
#include <QPainterPath>
#include <set>
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<QSvgRenderer> 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<PDFInteger>& ids, SelectionModes modes);
void select(PDFInteger id);
void select(const std::set<PDFInteger>& ids);
void selectNew(PDFInteger id);
void selectNew(const std::set<PDFInteger>& ids);
void deselect(PDFInteger id);
void deselect(const std::set<PDFInteger>& ids);
void deselectAll();
bool isManipulationInProgress() const { return m_isManipulationInProgress; }
void manipulateDeleteSelection();
signals:
void selectionChanged();
void stateChanged();
private:
void stopManipulation();
PDFPageContentScene* m_scene;
std::set<PDFInteger> m_selection;
bool m_isManipulationInProgress;
std::vector<std::unique_ptr<PDFPageContentElement>> m_manipulatedElements;
std::map<PDFInteger, uint> 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<PDFInteger>& selection);
// IDrawWidgetInputInterface interface
public:
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
@ -244,20 +317,55 @@ public:
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& 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<PDFInteger> 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<std::unique_ptr<PDFPageContentElement>> m_elements;
std::optional<QCursor> m_cursor;
PDFPageContentElementManipulator m_manipulator;
MouseGrabInfo m_mouseGrabInfo;
};
} // namespace pdf
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFPageContentElementManipulator::SelectionModes)
#endif // PDFPAGECONTENTELEMENTS_H

View File

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

View File

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