Move/Zoom functionality

This commit is contained in:
Jakub Melka 2019-02-02 18:10:00 +01:00
parent d4087eae1a
commit 4d7eaf7587
6 changed files with 408 additions and 16 deletions

View File

@ -27,7 +27,7 @@ namespace pdf
PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) :
QObject(parent),
m_document(nullptr),
m_pageLayoutMode(PageLayout::SinglePage),
m_pageLayoutMode(PageLayout::OneColumn),
m_verticalSpacingMM(5.0),
m_horizontalSpacingMM(1.0)
{
@ -353,6 +353,10 @@ void PDFDrawWidgetProxy::init(PDFWidget* widget)
m_horizontalScrollbar = widget->getHorizontalScrollbar();
m_verticalScrollbar = widget->getVerticalScrollbar();
connect(m_horizontalScrollbar, &QScrollBar::valueChanged, this, &PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged);
connect(m_verticalScrollbar, &QScrollBar::valueChanged, this, &PDFDrawWidgetProxy::onVerticalScrollbarValueChanged);
connect(this, &PDFDrawWidgetProxy::drawSpaceChanged, m_widget, QOverload<void>::of(&PDFDrawWidget::update));
// We must update the draw space - widget has been set
update();
}
@ -426,8 +430,9 @@ void PDFDrawWidgetProxy::update()
m_horizontalScrollbar->setMinimum(0);
m_horizontalScrollbar->setMaximum(horizontalDifference);
m_horizontalOffset = qBound<PDFInteger>(0, m_horizontalOffset, horizontalDifference);
m_horizontalScrollbar->setValue(m_horizontalOffset);
m_horizontalOffsetRange = Range<PDFInteger>(-horizontalDifference, 0);
m_horizontalOffset = m_horizontalOffsetRange.bound(m_horizontalOffset);
m_horizontalScrollbar->setValue(-m_horizontalOffset);
}
else
{
@ -435,6 +440,7 @@ void PDFDrawWidgetProxy::update()
// We set the offset to the half of available empty space.
m_horizontalScrollbar->setVisible(false);
m_horizontalOffset = -horizontalDifference / 2;
m_horizontalOffsetRange = Range<PDFInteger>(m_horizontalOffset);
}
// Vertical scrollbar - has two meanings, in block mode, it switches between blocks,
@ -465,6 +471,12 @@ void PDFDrawWidgetProxy::update()
if (verticalDifference < 0)
{
m_verticalOffset = -verticalDifference / 2;
m_verticalOffsetRange = Range<PDFInteger>(m_verticalOffset);
}
else
{
m_verticalOffsetRange = Range<PDFInteger>(-verticalDifference, 0);
m_verticalOffset = m_verticalOffsetRange.bound(m_verticalOffset);
}
}
else
@ -489,13 +501,15 @@ void PDFDrawWidgetProxy::update()
m_verticalScrollbar->setSingleStep(singleStep);
}
m_verticalOffset = qBound<PDFInteger>(0, m_verticalOffset, verticalDifference);
m_verticalScrollbar->setValue(m_verticalOffset);
m_verticalOffsetRange = Range<PDFInteger>(-verticalDifference, 0);
m_verticalOffset = m_verticalOffsetRange.bound(m_verticalOffset);
m_verticalScrollbar->setValue(-m_verticalOffset);
}
else
{
m_verticalScrollbar->setVisible(false);
m_verticalOffset = -verticalDifference / 2;
m_verticalOffsetRange = Range<PDFInteger>(m_verticalOffset);
}
}
@ -517,10 +531,105 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
{
// Clear the page space by white color
painter->fillRect(placedRect, Qt::white);
QFont font = m_widget->font();
font.setPixelSize(placedRect.height() * 0.75);
painter->setFont(font);
painter->drawText(placedRect, Qt::AlignCenter, QString::number(item.pageIndex + 1));
}
}
}
void PDFDrawWidgetProxy::performOperation(Operation operation)
{
switch (operation)
{
case NavigateDocumentStart:
{
if (m_verticalScrollbar->isVisible())
{
m_verticalScrollbar->setValue(0);
}
break;
}
case NavigateDocumentEnd:
{
if (m_verticalScrollbar->isVisible())
{
m_verticalScrollbar->setValue(m_verticalScrollbar->maximum());
}
break;
}
case NavigateNextPage:
{
if (m_verticalScrollbar->isVisible())
{
m_verticalScrollbar->setValue(m_verticalScrollbar->value() + m_verticalScrollbar->pageStep());
}
break;
}
case NavigatePreviousPage:
{
if (m_verticalScrollbar->isVisible())
{
m_verticalScrollbar->setValue(m_verticalScrollbar->value() - m_verticalScrollbar->pageStep());
}
break;
}
case NavigateNextStep:
{
if (m_verticalScrollbar->isVisible())
{
m_verticalScrollbar->setValue(m_verticalScrollbar->value() + m_verticalScrollbar->singleStep());
}
break;
}
case NavigatePreviousStep:
{
if (m_verticalScrollbar->isVisible())
{
m_verticalScrollbar->setValue(m_verticalScrollbar->value() - m_verticalScrollbar->singleStep());
}
break;
}
default:
{
Q_ASSERT(false);
break;
}
}
}
void PDFDrawWidgetProxy::scrollByPixels(QPoint offset)
{
setHorizontalOffset(m_horizontalOffset + offset.x());
setVerticalOffset(m_verticalOffset + offset.y());
}
void PDFDrawWidgetProxy::zoom(PDFReal zoom)
{
const PDFReal clampedZoom = qBound(MIN_ZOOM, zoom, MAX_ZOOM);
if (m_zoom != clampedZoom)
{
const PDFReal oldHorizontalOffsetMM = m_horizontalOffset * m_pixelToDeviceSpaceUnit;
const PDFReal oldVerticalOffsetMM = m_verticalOffset * m_pixelToDeviceSpaceUnit;
m_zoom = clampedZoom;
update();
// Try to restore offsets, so we are in the same place
setHorizontalOffset(oldHorizontalOffsetMM * m_deviceSpaceUnitToPixel);
setVerticalOffset(oldVerticalOffsetMM * m_deviceSpaceUnitToPixel);
}
}
QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const
{
Q_ASSERT(rect.isValid());
@ -550,4 +659,78 @@ bool PDFDrawWidgetProxy::isBlockMode() const
return false;
}
void PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged(int value)
{
if (!m_updateDisabled && !m_horizontalScrollbar->isHidden())
{
setHorizontalOffset(-value);
}
}
void PDFDrawWidgetProxy::onVerticalScrollbarValueChanged(int value)
{
if (!m_updateDisabled && !m_verticalScrollbar->isHidden())
{
if (isBlockMode())
{
setBlockIndex(value);
}
else
{
setVerticalOffset(-value);
}
}
}
void PDFDrawWidgetProxy::setHorizontalOffset(int value)
{
const PDFInteger horizontalOffset = m_horizontalOffsetRange.bound(value);
if (m_horizontalOffset != horizontalOffset)
{
m_horizontalOffset = horizontalOffset;
updateHorizontalScrollbarFromOffset();
emit drawSpaceChanged();
}
}
void PDFDrawWidgetProxy::setVerticalOffset(int value)
{
const PDFInteger verticalOffset = m_verticalOffsetRange.bound(value);
if (m_verticalOffset != verticalOffset)
{
m_verticalOffset = verticalOffset;
updateVerticalScrollbarFromOffset();
emit drawSpaceChanged();
}
}
void PDFDrawWidgetProxy::setBlockIndex(int index)
{
if (m_currentBlock != index)
{
m_currentBlock = static_cast<size_t>(index);
update();
}
}
void PDFDrawWidgetProxy::updateHorizontalScrollbarFromOffset()
{
if (!m_horizontalScrollbar->isHidden())
{
PDFBoolGuard guard(m_updateDisabled);
m_horizontalScrollbar->setValue(-m_horizontalOffset);
}
}
void PDFDrawWidgetProxy::updateVerticalScrollbarFromOffset()
{
if (!m_verticalScrollbar->isHidden() && !isBlockMode())
{
PDFBoolGuard guard(m_updateDisabled);
m_verticalScrollbar->setValue(-m_verticalOffset);
}
}
} // namespace pdf

View File

@ -18,6 +18,7 @@
#ifndef PDFDRAWSPACECONTROLLER_H
#define PDFDRAWSPACECONTROLLER_H
#include "pdfglobal.h"
#include "pdfdocument.h"
#include <QRectF>
@ -115,7 +116,7 @@ private:
/// This is a proxy class to draw space controller using widget. We have two spaces, pixel space
/// (on the controlled widget) and device space (device is draw space controller).
class PDFDrawWidgetProxy : public QObject
class PDFFORQTLIBSHARED_EXPORT PDFDrawWidgetProxy : public QObject
{
Q_OBJECT
@ -138,6 +139,36 @@ public:
/// \param rect Rectangle in which the content is painted
void draw(QPainter* painter, QRect rect);
enum Operation
{
NavigateDocumentStart,
NavigateDocumentEnd,
NavigateNextPage,
NavigatePreviousPage,
NavigateNextStep,
NavigatePreviousStep
};
/// Performs the desired operation (for example navigation).
/// \param operation Operation to be performed
void performOperation(Operation operation);
/// Scrolls by pixels, if it is possible. If it is not possible to scroll,
/// then nothing happens.
/// \param offset Offset in pixels
void scrollByPixels(QPoint offset);
/// Sets the zoom. Tries to preserve current offsets (so the current visible
/// area will be visible after the zoom).
/// \param zoom New zoom
void zoom(PDFReal zoom);
/// Returns current zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom,
/// and each 1 cm of widget area corresponds to 0.5 cm of the device space area.
PDFReal getZoom() const { return m_zoom; }
static constexpr PDFReal ZOOM_STEP = 1.2;
signals:
void drawSpaceChanged();
@ -181,6 +212,29 @@ private:
/// or continuous mode (single block with continuous list of separated pages).
bool isBlockMode() const;
void onHorizontalScrollbarValueChanged(int value);
void onVerticalScrollbarValueChanged(int value);
void setHorizontalOffset(int value);
void setVerticalOffset(int value);
void setBlockIndex(int index);
void updateHorizontalScrollbarFromOffset();
void updateVerticalScrollbarFromOffset();
template<typename T>
struct Range
{
constexpr inline Range() : min(T()), max(T()) { }
constexpr inline Range(T value) : min(value), max(value) { }
constexpr inline Range(T min, T max) : min(min), max(max) { }
T min;
T max;
constexpr inline T bound(T value) { return qBound(min, value, max); }
};
/// Flag, disables the update
bool m_updateDisabled;
@ -205,10 +259,16 @@ private:
/// with this vertical offset)
PDFInteger m_verticalOffset;
/// Range of vertical offset
Range<PDFInteger> m_verticalOffsetRange;
/// Actual horizontal offset of the draw space area in the widget (so block will be drawn
/// with this horizontal offset)
PDFInteger m_horizontalOffset;
/// Range for horizontal offset
Range<PDFInteger> m_horizontalOffsetRange;
/// Draw space controller
PDFDrawSpaceController* m_controller;

View File

@ -21,6 +21,7 @@
#include <QPainter>
#include <QGridLayout>
#include <QKeyEvent>
#include <QApplication>
namespace pdf
{
@ -62,7 +63,8 @@ void PDFWidget::setDocument(const PDFDocument* document)
PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) :
QWidget(parent),
m_widget(widget)
m_widget(widget),
m_mouseOperation(MouseOperation::None)
{
setFocusPolicy(Qt::StrongFocus);
}
@ -99,21 +101,125 @@ void PDFDrawWidget::keyPressEvent(QKeyEvent* event)
// Vertical navigation
if (verticalScrollbar->isVisible())
{
if (event->matches(QKeySequence::MoveToStartOfDocument))
constexpr std::pair<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation> keyToOperations[] =
{
verticalScrollbar->setValue(0);
}
else if (event->matches(QKeySequence::MoveToEndOfDocument))
{ QKeySequence::MoveToStartOfDocument, PDFDrawWidgetProxy::NavigateDocumentStart },
{ QKeySequence::MoveToEndOfDocument, PDFDrawWidgetProxy::NavigateDocumentEnd },
{ QKeySequence::MoveToNextPage, PDFDrawWidgetProxy::NavigateNextPage },
{ QKeySequence::MoveToPreviousPage, PDFDrawWidgetProxy::NavigatePreviousPage },
{ QKeySequence::MoveToNextLine, PDFDrawWidgetProxy::NavigateNextStep },
{ QKeySequence::MoveToPreviousLine, PDFDrawWidgetProxy::NavigatePreviousStep }
};
for (const std::pair<QKeySequence::StandardKey, PDFDrawWidgetProxy::Operation>& keyToOperation : keyToOperations)
{
verticalScrollbar->setValue(verticalScrollbar->maximum());
}
else if (event->matches(QKeySequence::MoveToNextPage))
{
verticalScrollbar->setValue(verticalScrollbar->value() + verticalScrollbar->pageStep());
if (event->matches(keyToOperation.first))
{
m_widget->getDrawWidgetProxy()->performOperation(keyToOperation.second);
}
}
}
event->accept();
}
void PDFDrawWidget::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
m_mouseOperation = MouseOperation::Translate;
m_lastMousePosition = event->pos();
setCursor(Qt::ClosedHandCursor);
}
event->accept();
}
void PDFDrawWidget::mouseReleaseEvent(QMouseEvent* event)
{
performMouseOperation(event->pos());
switch (m_mouseOperation)
{
case MouseOperation::None:
break;
case MouseOperation::Translate:
{
m_mouseOperation = MouseOperation::None;
unsetCursor();
break;
}
default:
Q_ASSERT(false);
}
event->accept();
}
void PDFDrawWidget::mouseMoveEvent(QMouseEvent* event)
{
performMouseOperation(event->pos());
event->accept();
}
void PDFDrawWidget::wheelEvent(QWheelEvent* event)
{
Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers();
if (keyboardModifiers.testFlag(Qt::ControlModifier))
{
// Zoom in/Zoom out
const int angleDeltaY = event->angleDelta().y();
const PDFReal zoom = m_widget->getDrawWidgetProxy()->getZoom();
const PDFReal zoomStep = std::pow(PDFDrawWidgetProxy::ZOOM_STEP, static_cast<PDFReal>(angleDeltaY) / static_cast<PDFReal>(QWheelEvent::DefaultDeltasPerStep));
const PDFReal newZoom = zoom * zoomStep;
m_widget->getDrawWidgetProxy()->zoom(newZoom);
}
else
{
// Move Up/Down. Angle is negative, if wheel is scrolled down. First we try to scroll by pixel delta.
// Otherwise we compute scroll using angle.
QPoint scrollByPixels = event->pixelDelta();
if (scrollByPixels.isNull())
{
const QPoint angleDelta = event->angleDelta();
const bool shiftModifier = keyboardModifiers.testFlag(Qt::ShiftModifier);
const int stepVertical = shiftModifier ? m_widget->getVerticalScrollbar()->pageStep() : m_widget->getVerticalScrollbar()->singleStep();
const int stepHorizontal = shiftModifier ? m_widget->getHorizontalScrollbar()->pageStep() : m_widget->getHorizontalScrollbar()->singleStep();
const int scrollVertical = stepVertical * static_cast<PDFReal>(angleDelta.y()) / static_cast<PDFReal>(QWheelEvent::DefaultDeltasPerStep);
const int scrollHorizontal = stepHorizontal * static_cast<PDFReal>(angleDelta.x()) / static_cast<PDFReal>(QWheelEvent::DefaultDeltasPerStep);
scrollByPixels = QPoint(scrollHorizontal, scrollVertical);
}
m_widget->getDrawWidgetProxy()->scrollByPixels(scrollByPixels);
}
event->accept();
}
void PDFDrawWidget::performMouseOperation(QPoint currentMousePosition)
{
switch (m_mouseOperation)
{
case MouseOperation::None:
// No operation performed
break;
case MouseOperation::Translate:
{
QPoint difference = currentMousePosition - m_lastMousePosition;
m_widget->getDrawWidgetProxy()->scrollByPixels(difference);
m_lastMousePosition = currentMousePosition;
break;
}
default:
Q_ASSERT(false);
}
}
} // namespace pdf

View File

@ -69,9 +69,25 @@ protected:
virtual void paintEvent(QPaintEvent* event) override;
virtual void resizeEvent(QResizeEvent* event) override;
virtual void keyPressEvent(QKeyEvent* event) override;
virtual void mousePressEvent(QMouseEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void mouseMoveEvent(QMouseEvent* event) override;
virtual void wheelEvent(QWheelEvent* event) override;
private:
enum class MouseOperation
{
None,
Translate
};
/// Performs the mouse operation (under the current mouse position)
/// \param currentMousePosition Current position of the mouse
void performMouseOperation(QPoint currentMousePosition);
PDFWidget* m_widget;
QPoint m_lastMousePosition;
MouseOperation m_mouseOperation;
};
} // namespace pdf

View File

@ -22,6 +22,7 @@
#include "pdfvisitor.h"
#include "pdfstreamfilters.h"
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include <QSettings>
#include <QFileDialog>
@ -50,6 +51,26 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget *parent) :
connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered);
connect(ui->actionQuit, &QAction::triggered, this, &PDFViewerMainWindow::onActionQuitTriggered);
auto createGoToAction = [this](QMenu* menu, QString text, QKeySequence::StandardKey key, pdf::PDFDrawWidgetProxy::Operation operation)
{
QAction* action = new QAction(text, this);
action->setShortcut(key);
menu->addAction(action);
auto onTriggered = [this, operation]()
{
m_pdfWidget->getDrawWidgetProxy()->performOperation(operation);
};
connect(action, &QAction::triggered, this, onTriggered);
};
createGoToAction(ui->menuGoTo, tr("Go to document start"), QKeySequence::MoveToStartOfDocument, pdf::PDFDrawWidgetProxy::NavigateDocumentStart);
createGoToAction(ui->menuGoTo, tr("Go to document end"), QKeySequence::MoveToEndOfDocument, pdf::PDFDrawWidgetProxy::NavigateDocumentEnd);
createGoToAction(ui->menuGoTo, tr("Go to next page"), QKeySequence::MoveToNextPage, pdf::PDFDrawWidgetProxy::NavigateNextPage);
createGoToAction(ui->menuGoTo, tr("Go to previous page"), QKeySequence::MoveToPreviousPage, pdf::PDFDrawWidgetProxy::NavigatePreviousPage);
createGoToAction(ui->menuGoTo, tr("Go to next line"), QKeySequence::MoveToNextLine, pdf::PDFDrawWidgetProxy::NavigateNextStep);
createGoToAction(ui->menuGoTo, tr("Go to previous line"), QKeySequence::MoveToPreviousLine, pdf::PDFDrawWidgetProxy::NavigatePreviousStep);
m_pdfWidget = new pdf::PDFWidget(this);
setCentralWidget(m_pdfWidget);
setFocusProxy(m_pdfWidget);

View File

@ -31,7 +31,13 @@
<addaction name="actionClose"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuGoTo">
<property name="title">
<string>Go To</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuGoTo"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">