1322 خطوط
35 KiB
C++
1322 خطوط
35 KiB
C++
/*
|
|
|
|
Copyright 2012-2015 Adam Reichold
|
|
|
|
This file is part of qpdfview.
|
|
|
|
qpdfview is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
qpdfview is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "pageitem.h"
|
|
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QtConcurrentRun>
|
|
#include <QFileDialog>
|
|
#include <QGraphicsProxyWidget>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsSceneHoverEvent>
|
|
#include <qmath.h>
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QPainter>
|
|
#include <QStyleOptionGraphicsItem>
|
|
#include <QTimer>
|
|
#include <QToolTip>
|
|
#include <QUrl>
|
|
|
|
#include "settings.h"
|
|
#include "model.h"
|
|
#include "tileitem.h"
|
|
|
|
namespace qpdfview
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
const int largeTilesThreshold = 8;
|
|
const int veryLargeTilesThreshold = 16;
|
|
|
|
const qreal proxyPadding = 2.0;
|
|
|
|
inline bool modifiersAreActive(const QGraphicsSceneMouseEvent* event, Qt::KeyboardModifiers modifiers)
|
|
{
|
|
if(modifiers == Qt::NoModifier)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return event->modifiers() == modifiers || (event->buttons() & modifiers) != 0;
|
|
}
|
|
|
|
inline bool modifiersUseMouseButton(Settings* settings, Qt::MouseButton mouseButton)
|
|
{
|
|
return ((settings->pageItem().copyToClipboardModifiers() | settings->pageItem().addAnnotationModifiers()) & mouseButton) != 0;
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
Settings* PageItem::s_settings = 0;
|
|
|
|
PageItem::PageItem(Model::Page* page, int index, PaintMode paintMode, QGraphicsItem* parent) : QGraphicsObject(parent),
|
|
m_page(page),
|
|
m_size(page->size()),
|
|
m_cropRect(),
|
|
m_index(index),
|
|
m_paintMode(paintMode),
|
|
m_highlights(),
|
|
m_loadInteractiveElements(0),
|
|
m_links(),
|
|
m_annotations(),
|
|
m_formFields(),
|
|
m_rubberBandMode(ModifiersMode),
|
|
m_rubberBand(),
|
|
m_annotationOverlay(),
|
|
m_formFieldOverlay(),
|
|
m_renderParam(),
|
|
m_transform(),
|
|
m_normalizedTransform(),
|
|
m_boundingRect(),
|
|
m_tileItems()
|
|
{
|
|
if(s_settings == 0)
|
|
{
|
|
s_settings = Settings::instance();
|
|
}
|
|
|
|
setAcceptHoverEvents(true);
|
|
|
|
setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, useTiling());
|
|
|
|
if(!useTiling())
|
|
{
|
|
m_tileItems.resize(1);
|
|
m_tileItems.squeeze();
|
|
|
|
m_tileItems.replace(0, new TileItem(this));
|
|
}
|
|
|
|
prepareGeometry();
|
|
}
|
|
|
|
PageItem::~PageItem()
|
|
{
|
|
if(m_loadInteractiveElements != 0)
|
|
{
|
|
m_loadInteractiveElements->waitForFinished();
|
|
|
|
delete m_loadInteractiveElements;
|
|
m_loadInteractiveElements = 0;
|
|
}
|
|
|
|
hideAnnotationOverlay(false);
|
|
hideFormFieldOverlay(false);
|
|
|
|
TileItem::dropCachedPixmaps(this);
|
|
|
|
qDeleteAll(m_links);
|
|
qDeleteAll(m_annotations);
|
|
qDeleteAll(m_formFields);
|
|
|
|
qDeleteAll(m_tileItems);
|
|
}
|
|
|
|
QRectF PageItem::boundingRect() const
|
|
{
|
|
if(m_cropRect.isNull())
|
|
{
|
|
return m_boundingRect;
|
|
}
|
|
|
|
QRectF boundingRect;
|
|
|
|
boundingRect.setLeft(m_boundingRect.left() + m_cropRect.left() * m_boundingRect.width());
|
|
boundingRect.setTop(m_boundingRect.top() + m_cropRect.top() * m_boundingRect.height());
|
|
boundingRect.setWidth(m_cropRect.width() * m_boundingRect.width());
|
|
boundingRect.setHeight(m_cropRect.height() * m_boundingRect.height());
|
|
|
|
return boundingRect;
|
|
}
|
|
|
|
void PageItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget*)
|
|
{
|
|
paintPage(painter, option->exposedRect);
|
|
|
|
paintLinks(painter);
|
|
paintFormFields(painter);
|
|
|
|
paintHighlights(painter);
|
|
paintRubberBand(painter);
|
|
}
|
|
|
|
QSizeF PageItem::displayedSize(const RenderParam& renderParam) const
|
|
{
|
|
const bool rotationChanged = m_renderParam.rotation() != renderParam.rotation();
|
|
|
|
const bool flagsChanged = m_renderParam.flags() != renderParam.flags();
|
|
|
|
const bool useCropRect = !m_cropRect.isNull() && !rotationChanged && !flagsChanged;
|
|
|
|
const qreal cropWidth = useCropRect ? m_cropRect.width() : 1.0;
|
|
const qreal cropHeight = useCropRect ? m_cropRect.height() : 1.0;
|
|
|
|
switch(renderParam.rotation())
|
|
{
|
|
default:
|
|
case RotateBy0:
|
|
case RotateBy180:
|
|
return QSizeF(renderParam.resolutionX() / 72.0 * cropWidth * m_size.width(),
|
|
renderParam.resolutionY() / 72.0 * cropHeight * m_size.height());
|
|
case RotateBy90:
|
|
case RotateBy270:
|
|
return QSizeF(renderParam.resolutionX() / 72.0 * cropHeight * m_size.height(),
|
|
renderParam.resolutionY() / 72.0 * cropWidth * m_size.width());
|
|
}
|
|
}
|
|
|
|
void PageItem::setHighlights(const QList< QRectF >& highlights)
|
|
{
|
|
m_highlights = highlights;
|
|
|
|
update();
|
|
}
|
|
|
|
void PageItem::setRubberBandMode(RubberBandMode rubberBandMode)
|
|
{
|
|
if(m_rubberBandMode != rubberBandMode && rubberBandMode >= 0 && rubberBandMode < NumberOfRubberBandModes)
|
|
{
|
|
m_rubberBandMode = rubberBandMode;
|
|
|
|
if(m_rubberBandMode == ModifiersMode)
|
|
{
|
|
unsetCursor();
|
|
}
|
|
else
|
|
{
|
|
setCursor(Qt::CrossCursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PageItem::setRenderParam(const RenderParam& renderParam)
|
|
{
|
|
if(m_renderParam != renderParam)
|
|
{
|
|
const bool resolutionChanged = m_renderParam.resolutionX() != renderParam.resolutionX()
|
|
|| m_renderParam.resolutionY() != renderParam.resolutionY()
|
|
|| !qFuzzyCompare(m_renderParam.devicePixelRatio(), renderParam.devicePixelRatio())
|
|
|| !qFuzzyCompare(m_renderParam.scaleFactor(), renderParam.scaleFactor());
|
|
|
|
const bool rotationChanged = m_renderParam.rotation() != renderParam.rotation();
|
|
|
|
const RenderFlags changedFlags = m_renderParam.flags() ^ renderParam.flags();
|
|
|
|
refresh(!rotationChanged && changedFlags == 0);
|
|
|
|
m_renderParam = renderParam;
|
|
|
|
if(resolutionChanged || rotationChanged)
|
|
{
|
|
prepareGeometryChange();
|
|
prepareGeometry();
|
|
}
|
|
|
|
if(changedFlags.testFlag(TrimMargins))
|
|
{
|
|
setFlag(QGraphicsItem::ItemClipsToShape, m_renderParam.trimMargins());
|
|
}
|
|
}
|
|
}
|
|
|
|
void PageItem::refresh(bool keepObsoletePixmaps, bool dropCachedPixmaps)
|
|
{
|
|
if(!useTiling())
|
|
{
|
|
m_tileItems.first()->refresh(keepObsoletePixmaps);
|
|
}
|
|
else
|
|
{
|
|
foreach(TileItem* tile, m_tileItems)
|
|
{
|
|
tile->refresh(keepObsoletePixmaps);
|
|
}
|
|
}
|
|
|
|
if(!keepObsoletePixmaps)
|
|
{
|
|
prepareGeometryChange();
|
|
|
|
m_cropRect = QRectF();
|
|
}
|
|
|
|
if(dropCachedPixmaps)
|
|
{
|
|
TileItem::dropCachedPixmaps(this);
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
int PageItem::startRender(bool prefetch)
|
|
{
|
|
int cost = 0;
|
|
|
|
if(!useTiling())
|
|
{
|
|
cost += m_tileItems.first()->startRender(prefetch);
|
|
}
|
|
else
|
|
{
|
|
foreach(TileItem* tile, m_tileItems)
|
|
{
|
|
cost += tile->startRender(prefetch);
|
|
}
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
|
|
void PageItem::cancelRender()
|
|
{
|
|
if(!useTiling())
|
|
{
|
|
m_tileItems.first()->cancelRender();
|
|
}
|
|
else
|
|
{
|
|
foreach(TileItem* tile, m_exposedTileItems)
|
|
{
|
|
tile->cancelRender();
|
|
}
|
|
|
|
m_exposedTileItems.clear();
|
|
}
|
|
}
|
|
|
|
void PageItem::showAnnotationOverlay(Model::Annotation* selectedAnnotation)
|
|
{
|
|
if(s_settings->pageItem().annotationOverlay())
|
|
{
|
|
showOverlay(m_annotationOverlay, SLOT(hideAnnotationOverlay()), m_annotations, selectedAnnotation);
|
|
}
|
|
else
|
|
{
|
|
hideAnnotationOverlay(false);
|
|
|
|
addProxy(m_annotationOverlay, SLOT(hideAnnotationOverlay()), selectedAnnotation);
|
|
m_annotationOverlay.value(selectedAnnotation)->widget()->setFocus();
|
|
}
|
|
}
|
|
|
|
void PageItem::hideAnnotationOverlay(bool deleteLater)
|
|
{
|
|
hideOverlay(m_annotationOverlay, deleteLater);
|
|
}
|
|
|
|
void PageItem::updateAnnotationOverlay()
|
|
{
|
|
updateOverlay(m_annotationOverlay);
|
|
}
|
|
|
|
void PageItem::showFormFieldOverlay(Model::FormField* selectedFormField)
|
|
{
|
|
if(s_settings->pageItem().formFieldOverlay())
|
|
{
|
|
showOverlay(m_formFieldOverlay, SLOT(hideFormFieldOverlay()), m_formFields, selectedFormField);
|
|
}
|
|
else
|
|
{
|
|
hideFormFieldOverlay(false);
|
|
|
|
addProxy(m_formFieldOverlay, SLOT(hideFormFieldOverlay()), selectedFormField);
|
|
m_formFieldOverlay.value(selectedFormField)->widget()->setFocus();
|
|
}
|
|
}
|
|
|
|
void PageItem::updateFormFieldOverlay()
|
|
{
|
|
updateOverlay(m_formFieldOverlay);
|
|
}
|
|
|
|
void PageItem::hideFormFieldOverlay(bool deleteLater)
|
|
{
|
|
hideOverlay(m_formFieldOverlay, deleteLater);
|
|
}
|
|
|
|
void PageItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
|
|
{
|
|
}
|
|
|
|
void PageItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
|
|
{
|
|
if(m_rubberBandMode == ModifiersMode && event->modifiers() == Qt::NoModifier)
|
|
{
|
|
// links
|
|
|
|
foreach(const Model::Link* link, m_links)
|
|
{
|
|
if(m_normalizedTransform.map(link->boundary).contains(event->pos()))
|
|
{
|
|
if(link->page != -1 && (link->urlOrFileName.isNull() || !presentationMode()))
|
|
{
|
|
setCursor(Qt::PointingHandCursor);
|
|
|
|
if(link->urlOrFileName.isNull())
|
|
{
|
|
QToolTip::showText(event->screenPos(), tr("Go to page %1.").arg(link->page));
|
|
}
|
|
else
|
|
{
|
|
QToolTip::showText(event->screenPos(), tr("Go to page %1 of file '%2'.").arg(link->page).arg(link->urlOrFileName));
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if(!link->urlOrFileName.isNull() && !presentationMode())
|
|
{
|
|
setCursor(Qt::PointingHandCursor);
|
|
QToolTip::showText(event->screenPos(), tr("Open '%1'.").arg(link->urlOrFileName));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(presentationMode())
|
|
{
|
|
unsetCursor();
|
|
QToolTip::hideText();
|
|
|
|
return;
|
|
}
|
|
|
|
// annotations
|
|
|
|
foreach(const Model::Annotation* annotation, m_annotations)
|
|
{
|
|
if(m_normalizedTransform.mapRect(annotation->boundary()).contains(event->pos()))
|
|
{
|
|
setCursor(Qt::PointingHandCursor);
|
|
QToolTip::showText(event->screenPos(), annotation->contents());
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// form fields
|
|
|
|
foreach(const Model::FormField* formField, m_formFields)
|
|
{
|
|
if(m_normalizedTransform.mapRect(formField->boundary()).contains(event->pos()))
|
|
{
|
|
setCursor(Qt::PointingHandCursor);
|
|
QToolTip::showText(event->screenPos(), tr("Edit form field '%1'.").arg(formField->name()));
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
unsetCursor();
|
|
QToolTip::hideText();
|
|
}
|
|
}
|
|
|
|
void PageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
|
|
{
|
|
}
|
|
|
|
void PageItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
|
|
{
|
|
const bool leftButtonActive = event->button() == Qt::LeftButton;
|
|
const bool middleButtonActive = event->button() == Qt::MidButton;
|
|
const bool anyButtonActive = leftButtonActive || middleButtonActive;
|
|
|
|
const bool noModifiersActive = event->modifiers() == Qt::NoModifier;
|
|
const bool copyToClipboardModifiersActive = modifiersAreActive(event, s_settings->pageItem().copyToClipboardModifiers());
|
|
const bool addAnnotationModifiersActive = modifiersAreActive(event, s_settings->pageItem().addAnnotationModifiers());
|
|
const bool zoomToSelectionModifiersActive = modifiersAreActive(event, s_settings->pageItem().zoomToSelectionModifiers());
|
|
const bool rubberBandModifiersActive = copyToClipboardModifiersActive || addAnnotationModifiersActive || zoomToSelectionModifiersActive;
|
|
const bool openInSourceEditorModifiersActive = modifiersAreActive(event, s_settings->pageItem().openInSourceEditorModifiers());
|
|
|
|
// rubber band
|
|
|
|
if(rubberBandModifiersActive && leftButtonActive && !presentationMode())
|
|
{
|
|
if(m_rubberBandMode == ModifiersMode)
|
|
{
|
|
setCursor(Qt::CrossCursor);
|
|
|
|
if(copyToClipboardModifiersActive)
|
|
{
|
|
m_rubberBandMode = CopyToClipboardMode;
|
|
}
|
|
else if(addAnnotationModifiersActive)
|
|
{
|
|
m_rubberBandMode = AddAnnotationMode;
|
|
}
|
|
else if(zoomToSelectionModifiersActive)
|
|
{
|
|
m_rubberBandMode = ZoomToSelectionMode;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_rubberBandMode != ModifiersMode)
|
|
{
|
|
m_rubberBand = QRectF(event->pos(), QSizeF());
|
|
|
|
emit rubberBandStarted();
|
|
|
|
update();
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
if(openInSourceEditorModifiersActive && leftButtonActive && !presentationMode())
|
|
{
|
|
emit openInSourceEditor(m_index + 1, sourcePos(event->pos()));
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
if(noModifiersActive && anyButtonActive)
|
|
{
|
|
// links
|
|
|
|
foreach(const Model::Link* link, m_links)
|
|
{
|
|
if(m_normalizedTransform.map(link->boundary).contains(event->pos()))
|
|
{
|
|
unsetCursor();
|
|
|
|
if(link->page != -1 && (link->urlOrFileName.isNull() || !presentationMode()))
|
|
{
|
|
if(link->urlOrFileName.isNull())
|
|
{
|
|
emit linkClicked(middleButtonActive, link->page, link->left, link->top);
|
|
}
|
|
else
|
|
{
|
|
emit linkClicked(middleButtonActive, link->urlOrFileName, link->page);
|
|
}
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
else if(!link->urlOrFileName.isNull() && !presentationMode())
|
|
{
|
|
emit linkClicked(link->urlOrFileName);
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(noModifiersActive && leftButtonActive && !presentationMode())
|
|
{
|
|
// annotations
|
|
|
|
foreach(Model::Annotation* annotation, m_annotations)
|
|
{
|
|
if(m_normalizedTransform.mapRect(annotation->boundary()).contains(event->pos()))
|
|
{
|
|
unsetCursor();
|
|
|
|
showAnnotationOverlay(annotation);
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
hideAnnotationOverlay();
|
|
|
|
// form fields
|
|
|
|
foreach(Model::FormField* formField, m_formFields)
|
|
{
|
|
if(m_normalizedTransform.mapRect(formField->boundary()).contains(event->pos()))
|
|
{
|
|
unsetCursor();
|
|
|
|
showFormFieldOverlay(formField);
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
hideFormFieldOverlay();
|
|
}
|
|
|
|
event->ignore();
|
|
}
|
|
|
|
void PageItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
|
|
{
|
|
if(!m_rubberBand.isNull())
|
|
{
|
|
if(m_boundingRect.contains(event->pos()))
|
|
{
|
|
m_rubberBand.setBottomRight(event->pos());
|
|
|
|
update();
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
event->ignore();
|
|
}
|
|
|
|
void PageItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
|
|
{
|
|
if(!m_rubberBand.isNull())
|
|
{
|
|
unsetCursor();
|
|
|
|
m_rubberBand = m_rubberBand.normalized();
|
|
|
|
if(m_rubberBandMode == CopyToClipboardMode)
|
|
{
|
|
copyToClipboard(event->screenPos());
|
|
}
|
|
else if(m_rubberBandMode == AddAnnotationMode)
|
|
{
|
|
addAnnotation(event->screenPos());
|
|
}
|
|
else if(m_rubberBandMode == ZoomToSelectionMode)
|
|
{
|
|
emit zoomToSelection(m_index + 1, m_normalizedTransform.inverted().mapRect(m_rubberBand));
|
|
}
|
|
|
|
m_rubberBandMode = ModifiersMode;
|
|
m_rubberBand = QRectF();
|
|
|
|
emit rubberBandFinished();
|
|
|
|
update();
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
event->ignore();
|
|
}
|
|
|
|
void PageItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
|
|
{
|
|
if(event->reason() == QGraphicsSceneContextMenuEvent::Mouse && modifiersUseMouseButton(s_settings, Qt::RightButton))
|
|
{
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
if(presentationMode())
|
|
{
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
foreach(Model::Link* link, m_links)
|
|
{
|
|
if(m_normalizedTransform.map(link->boundary).contains(event->pos()))
|
|
{
|
|
unsetCursor();
|
|
|
|
showLinkContextMenu(link, event->screenPos());
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
foreach(Model::Annotation* annotation, m_annotations)
|
|
{
|
|
if(m_normalizedTransform.mapRect(annotation->boundary()).contains(event->pos()))
|
|
{
|
|
unsetCursor();
|
|
|
|
showAnnotationContextMenu(annotation, event->screenPos());
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
event->ignore();
|
|
}
|
|
|
|
void PageItem::on_loadInteractiveElements_finished()
|
|
{
|
|
update();
|
|
}
|
|
|
|
void PageItem::updateCropRect()
|
|
{
|
|
QRectF cropRect;
|
|
|
|
if(!useTiling())
|
|
{
|
|
cropRect = m_tileItems.first()->cropRect();
|
|
}
|
|
else
|
|
{
|
|
foreach(TileItem* tile, m_tileItems)
|
|
{
|
|
const QRect& tileRect = tile->rect();
|
|
const QRectF& tileCropRect = tile->cropRect();
|
|
|
|
if(tileCropRect.isNull())
|
|
{
|
|
cropRect = QRectF();
|
|
break;
|
|
}
|
|
|
|
const qreal left = (tileRect.left() + tileCropRect.left() * tileRect.width()) / m_boundingRect.width();
|
|
const qreal top = (tileRect.top() + tileCropRect.top() * tileRect.height()) / m_boundingRect.height();
|
|
const qreal width = tileCropRect.width() * tileRect.width() / m_boundingRect.width();
|
|
const qreal height = tileCropRect.height() * tileRect.height() / m_boundingRect.height();
|
|
|
|
cropRect = cropRect.united(QRectF(left, top, width, height));
|
|
}
|
|
}
|
|
|
|
if(m_cropRect.isNull() && !cropRect.isNull())
|
|
{
|
|
prepareGeometryChange();
|
|
|
|
m_cropRect = cropRect;
|
|
|
|
emit cropRectChanged();
|
|
}
|
|
}
|
|
|
|
inline bool PageItem::presentationMode() const
|
|
{
|
|
return m_paintMode == PresentationMode;
|
|
}
|
|
|
|
inline bool PageItem::thumbnailMode() const
|
|
{
|
|
return m_paintMode == ThumbnailMode;
|
|
}
|
|
|
|
bool PageItem::useTiling() const
|
|
{
|
|
return m_paintMode != ThumbnailMode && s_settings->pageItem().useTiling();
|
|
}
|
|
|
|
void PageItem::startLoadInteractiveElements()
|
|
{
|
|
if(thumbnailMode() || m_loadInteractiveElements != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_loadInteractiveElements = new QFutureWatcher< void >(this);
|
|
connect(m_loadInteractiveElements, SIGNAL(finished()), SLOT(on_loadInteractiveElements_finished()));
|
|
m_loadInteractiveElements->setFuture(QtConcurrent::run(this, &PageItem::loadInteractiveElements));
|
|
}
|
|
|
|
void PageItem::loadInteractiveElements()
|
|
{
|
|
m_links = m_page->links();
|
|
|
|
if(presentationMode())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PageItem* const parent = this;
|
|
QThread* const parentThread = parent->thread();
|
|
|
|
const QList< Model::Annotation* > annotations = m_page->annotations();
|
|
|
|
foreach(Model::Annotation* annotation, annotations)
|
|
{
|
|
annotation->moveToThread(parentThread);
|
|
connect(annotation, SIGNAL(wasModified()), parent, SIGNAL(wasModified()));
|
|
}
|
|
|
|
m_annotations = annotations;
|
|
|
|
const QList< Model::FormField* > formFields = m_page->formFields();
|
|
|
|
foreach(Model::FormField* formField, formFields)
|
|
{
|
|
formField->moveToThread(parentThread);
|
|
connect(formField, SIGNAL(wasModified()), parent, SIGNAL(wasModified()));
|
|
}
|
|
|
|
m_formFields = formFields;
|
|
}
|
|
|
|
void PageItem::copyToClipboard(QPoint screenPos)
|
|
{
|
|
QMenu menu;
|
|
|
|
QAction* copyTextAction = menu.addAction(tr("Copy &text"));
|
|
QAction* selectTextAction = menu.addAction(tr("&Select text"));
|
|
const QAction* copyImageAction = menu.addAction(tr("Copy &image"));
|
|
const QAction* saveImageToFileAction = menu.addAction(tr("Save image to &file..."));
|
|
|
|
const QString text = m_page->text(m_transform.inverted().mapRect(m_rubberBand));
|
|
|
|
copyTextAction->setVisible(!text.isEmpty());
|
|
selectTextAction->setVisible(!text.isEmpty() && QApplication::clipboard()->supportsSelection());
|
|
|
|
const QAction* action = menu.exec(screenPos);
|
|
|
|
if(action == copyTextAction || action == selectTextAction)
|
|
{
|
|
if(action == copyTextAction)
|
|
{
|
|
QApplication::clipboard()->setText(text);
|
|
}
|
|
else
|
|
{
|
|
QApplication::clipboard()->setText(text, QClipboard::Selection);
|
|
}
|
|
}
|
|
else if(action == copyImageAction || action == saveImageToFileAction)
|
|
{
|
|
const QRect rect = m_rubberBand.translated(-m_boundingRect.topLeft()).toRect();
|
|
const QImage image = m_page->render(m_renderParam.resolutionX() * m_renderParam.scaleFactor(),
|
|
m_renderParam.resolutionY() * m_renderParam.scaleFactor(),
|
|
m_renderParam.rotation(), rect);
|
|
|
|
if(!image.isNull())
|
|
{
|
|
if(action == copyImageAction)
|
|
{
|
|
QApplication::clipboard()->setImage(image);
|
|
}
|
|
else if(action == saveImageToFileAction)
|
|
{
|
|
const QString fileName = QFileDialog::getSaveFileName(0, tr("Save image to file"), QDir::homePath(), "Portable network graphics (*.png)");
|
|
|
|
if(!image.save(fileName, "PNG"))
|
|
{
|
|
QMessageBox::warning(0, tr("Warning"), tr("Could not save image to file '%1'.").arg(fileName));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PageItem::addAnnotation(QPoint screenPos)
|
|
{
|
|
if(m_page->canAddAndRemoveAnnotations())
|
|
{
|
|
QMenu menu;
|
|
|
|
const QAction* addTextAction = menu.addAction(tr("Add &text"));
|
|
const QAction* addHighlightAction = menu.addAction(tr("Add &highlight"));
|
|
|
|
const QAction* action = menu.exec(screenPos);
|
|
|
|
if(action == addTextAction || action == addHighlightAction)
|
|
{
|
|
QRectF boundary = m_normalizedTransform.inverted().mapRect(m_rubberBand);
|
|
|
|
Model::Annotation* annotation = 0;
|
|
|
|
if(action == addTextAction)
|
|
{
|
|
boundary.setWidth(24.0 / m_size.width());
|
|
boundary.setHeight(24.0 / m_size.height());
|
|
|
|
annotation = m_page->addTextAnnotation(boundary, s_settings->pageItem().annotationColor());
|
|
}
|
|
else if(action == addHighlightAction)
|
|
{
|
|
annotation = m_page->addHighlightAnnotation(boundary, s_settings->pageItem().annotationColor());
|
|
}
|
|
|
|
m_annotations.append(annotation);
|
|
connect(annotation, SIGNAL(wasModified()), SIGNAL(wasModified()));
|
|
|
|
refresh(false, true);
|
|
emit wasModified();
|
|
|
|
if(action == addTextAction)
|
|
{
|
|
showAnnotationOverlay(annotation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PageItem::showLinkContextMenu(Model::Link* link, QPoint screenPos)
|
|
{
|
|
if(link->page == -1)
|
|
{
|
|
QMenu menu;
|
|
|
|
const QAction* copyLinkAddressAction = menu.addAction(tr("&Copy link address"));
|
|
QAction* selectLinkAddressAction = menu.addAction(tr("&Select link address"));
|
|
|
|
selectLinkAddressAction->setVisible(QApplication::clipboard()->supportsSelection());
|
|
|
|
const QAction* action = menu.exec(screenPos);
|
|
|
|
if(action == copyLinkAddressAction)
|
|
{
|
|
QApplication::clipboard()->setText(link->urlOrFileName);
|
|
}
|
|
else if(action == selectLinkAddressAction)
|
|
{
|
|
QApplication::clipboard()->setText(link->urlOrFileName, QClipboard::Selection);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PageItem::showAnnotationContextMenu(Model::Annotation* annotation, QPoint screenPos)
|
|
{
|
|
if(m_page->canAddAndRemoveAnnotations())
|
|
{
|
|
QMenu menu;
|
|
|
|
const QAction* removeAnnotationAction = menu.addAction(tr("&Remove annotation"));
|
|
|
|
const QAction* action = menu.exec(screenPos);
|
|
|
|
if(action == removeAnnotationAction)
|
|
{
|
|
m_annotations.removeAll(annotation);
|
|
m_page->removeAnnotation(annotation);
|
|
|
|
annotation->deleteLater();
|
|
|
|
refresh(false, true);
|
|
emit wasModified();
|
|
}
|
|
}
|
|
}
|
|
|
|
template< typename Overlay, typename Element >
|
|
void PageItem::showOverlay(Overlay& overlay, const char* hideOverlay, const QList< Element* >& elements, Element* selectedElement)
|
|
{
|
|
foreach(Element* element, elements)
|
|
{
|
|
if(!overlay.contains(element))
|
|
{
|
|
addProxy(overlay, hideOverlay, element);
|
|
}
|
|
|
|
if(element == selectedElement)
|
|
{
|
|
overlay.value(element)->widget()->setFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
template< typename Overlay, typename Element >
|
|
void PageItem::addProxy(Overlay& overlay, const char* hideOverlay, Element* element)
|
|
{
|
|
QGraphicsProxyWidget* proxy = new QGraphicsProxyWidget(this);
|
|
proxy->setWidget(element->createWidget());
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(4,7,0)
|
|
|
|
proxy->setAutoFillBackground(true);
|
|
|
|
#endif // QT_VERSION
|
|
|
|
overlay.insert(element, proxy);
|
|
setProxyGeometry(element, proxy);
|
|
|
|
connect(proxy, SIGNAL(visibleChanged()), hideOverlay);
|
|
}
|
|
|
|
template< typename Overlay >
|
|
void PageItem::hideOverlay(Overlay& overlay, bool deleteLater)
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(4,8,0)
|
|
|
|
Overlay discardedOverlay;
|
|
discardedOverlay.swap(overlay);
|
|
|
|
#else
|
|
|
|
Overlay discardedOverlay(overlay);
|
|
overlay = Overlay();
|
|
|
|
#endif // QT_VERSION
|
|
|
|
if(!discardedOverlay.isEmpty())
|
|
{
|
|
for(typename Overlay::const_iterator i = discardedOverlay.constBegin(); i != discardedOverlay.constEnd(); ++i)
|
|
{
|
|
if(deleteLater)
|
|
{
|
|
i.value()->deleteLater();
|
|
}
|
|
else
|
|
{
|
|
delete i.value();
|
|
}
|
|
}
|
|
|
|
refresh(false, true);
|
|
}
|
|
}
|
|
|
|
template< typename Overlay >
|
|
void PageItem::updateOverlay(const Overlay& overlay) const
|
|
{
|
|
for(typename Overlay::const_iterator i = overlay.constBegin(); i != overlay.constEnd(); ++i)
|
|
{
|
|
setProxyGeometry(i.key(), i.value());
|
|
}
|
|
}
|
|
|
|
void PageItem::setProxyGeometry(Model::Annotation* annotation, QGraphicsProxyWidget* proxy) const
|
|
{
|
|
const QPointF center = m_normalizedTransform.map(annotation->boundary().center());
|
|
|
|
qreal x = center.x() - 0.5 * proxy->preferredWidth();
|
|
qreal y = center.y() - 0.5 * proxy->preferredHeight();
|
|
qreal width = proxy->preferredWidth();
|
|
qreal height = proxy->preferredHeight();
|
|
|
|
x = qMax(x, m_boundingRect.left() + proxyPadding);
|
|
y = qMax(y, m_boundingRect.top() + proxyPadding);
|
|
|
|
width = qMin(width, m_boundingRect.right() - proxyPadding - x);
|
|
height = qMin(height, m_boundingRect.bottom() - proxyPadding - y);
|
|
|
|
proxy->setGeometry(QRectF(x, y, width, height));
|
|
}
|
|
|
|
void PageItem::setProxyGeometry(Model::FormField* formField, QGraphicsProxyWidget* proxy) const
|
|
{
|
|
QRectF rect = m_normalizedTransform.mapRect(formField->boundary());
|
|
|
|
qreal x = rect.x();
|
|
qreal y = rect.y();
|
|
qreal width = rect.width();
|
|
qreal height = rect.height();
|
|
|
|
switch(m_renderParam.rotation())
|
|
{
|
|
default:
|
|
case RotateBy0:
|
|
proxy->setRotation(0.0);
|
|
break;
|
|
case RotateBy90:
|
|
x += width;
|
|
qSwap(width, height);
|
|
|
|
proxy->setRotation(90.0);
|
|
break;
|
|
case RotateBy180:
|
|
x += width;
|
|
y += height;
|
|
|
|
proxy->setRotation(180.0);
|
|
break;
|
|
case RotateBy270:
|
|
y += height;
|
|
qSwap(width, height);
|
|
|
|
proxy->setRotation(270.0);
|
|
break;
|
|
}
|
|
|
|
width /= m_renderParam.scaleFactor();
|
|
height /= m_renderParam.scaleFactor();
|
|
|
|
proxy->setScale(m_renderParam.scaleFactor());
|
|
|
|
proxy->setGeometry(QRectF(x - proxyPadding, y - proxyPadding, width + proxyPadding, height + proxyPadding));
|
|
}
|
|
|
|
void PageItem::prepareGeometry()
|
|
{
|
|
m_transform.reset();
|
|
|
|
m_transform.scale(m_renderParam.resolutionX() * m_renderParam.scaleFactor() / 72.0,
|
|
m_renderParam.resolutionY() * m_renderParam.scaleFactor() / 72.0);
|
|
|
|
switch(m_renderParam.rotation())
|
|
{
|
|
default:
|
|
case RotateBy0:
|
|
break;
|
|
case RotateBy90:
|
|
m_transform.rotate(90.0);
|
|
break;
|
|
case RotateBy180:
|
|
m_transform.rotate(180.0);
|
|
break;
|
|
case RotateBy270:
|
|
m_transform.rotate(270.0);
|
|
break;
|
|
}
|
|
|
|
m_normalizedTransform = m_transform;
|
|
m_normalizedTransform.scale(m_size.width(), m_size.height());
|
|
|
|
|
|
m_boundingRect = m_transform.mapRect(QRectF(QPointF(), m_size));
|
|
|
|
m_boundingRect.setWidth(qRound(m_boundingRect.width()));
|
|
m_boundingRect.setHeight(qRound(m_boundingRect.height()));
|
|
|
|
|
|
prepareTiling();
|
|
|
|
updateAnnotationOverlay();
|
|
updateFormFieldOverlay();
|
|
}
|
|
|
|
void PageItem::prepareTiling()
|
|
{
|
|
if(!useTiling())
|
|
{
|
|
m_tileItems.first()->setRect(QRect(0, 0, m_boundingRect.width(), m_boundingRect.height()));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
const qreal pageWidth = m_boundingRect.width();
|
|
const qreal pageHeight = m_boundingRect.height();
|
|
const qreal pageSize = qMax(pageWidth, pageHeight);
|
|
|
|
int tileSize = s_settings->pageItem().tileSize();
|
|
|
|
if(tileSize * veryLargeTilesThreshold < pageSize)
|
|
{
|
|
tileSize *= 4;
|
|
}
|
|
else if(tileSize * largeTilesThreshold < pageSize)
|
|
{
|
|
tileSize *= 2;
|
|
}
|
|
|
|
int tileWidth = pageWidth < pageHeight ? tileSize * pageWidth / pageHeight : tileSize;
|
|
int tileHeight = pageHeight < pageWidth ? tileSize * pageHeight / pageWidth : tileSize;
|
|
|
|
const int columnCount = qCeil(pageWidth / tileWidth);
|
|
const int rowCount = qCeil(pageHeight / tileHeight);
|
|
|
|
tileWidth = qCeil(pageWidth / columnCount);
|
|
tileHeight = qCeil(pageHeight / rowCount);
|
|
|
|
|
|
const int newCount = columnCount * rowCount;
|
|
const int oldCount = m_tileItems.count();
|
|
|
|
if(oldCount != newCount)
|
|
{
|
|
for(int index = newCount; index < oldCount; ++index)
|
|
{
|
|
m_tileItems.at(index)->deleteAfterRender();
|
|
}
|
|
|
|
m_tileItems.resize(newCount);
|
|
|
|
for(int index = oldCount; index < newCount; ++index)
|
|
{
|
|
m_tileItems.replace(index, new TileItem(this));
|
|
}
|
|
|
|
foreach(TileItem* tile, m_tileItems)
|
|
{
|
|
tile->dropObsoletePixmap();
|
|
}
|
|
}
|
|
|
|
m_exposedTileItems.clear();
|
|
|
|
|
|
for(int column = 0; column < columnCount; ++column)
|
|
{
|
|
for(int row = 0; row < rowCount; ++row)
|
|
{
|
|
const int left = column > 0 ? column * tileWidth : 0.0;
|
|
const int top = row > 0 ? row * tileHeight : 0.0;
|
|
|
|
const int width = column < (columnCount - 1) ? tileWidth : pageWidth - left;
|
|
const int height = row < (rowCount - 1) ? tileHeight : pageHeight - top;
|
|
|
|
m_tileItems.at(column * rowCount + row)->setRect(QRect(left, top, width, height));
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void PageItem::paintPage(QPainter* painter, const QRectF& exposedRect) const
|
|
{
|
|
if(s_settings->pageItem().decoratePages() && !presentationMode())
|
|
{
|
|
// background
|
|
|
|
QColor paperColor = s_settings->pageItem().paperColor();
|
|
|
|
if(m_renderParam.invertColors())
|
|
{
|
|
paperColor.setRgb(~paperColor.rgb());
|
|
}
|
|
|
|
painter->fillRect(m_boundingRect, QBrush(paperColor));
|
|
}
|
|
|
|
// tiles
|
|
|
|
if(!useTiling())
|
|
{
|
|
TileItem* tile = m_tileItems.first();
|
|
|
|
if(tile->paint(painter, m_boundingRect.topLeft()))
|
|
{
|
|
tile->dropPixmap();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const QRectF& translatedExposedRect = exposedRect.translated(-m_boundingRect.topLeft());
|
|
|
|
foreach(TileItem* tile, m_tileItems)
|
|
{
|
|
const bool intersects = translatedExposedRect.intersects(tile->rect());
|
|
const bool contains = m_exposedTileItems.contains(tile);
|
|
|
|
if(intersects && !contains)
|
|
{
|
|
m_exposedTileItems.insert(tile);
|
|
}
|
|
else if(!intersects && contains)
|
|
{
|
|
m_exposedTileItems.remove(tile);
|
|
|
|
tile->cancelRender();
|
|
}
|
|
}
|
|
|
|
bool allExposedPainted = true;
|
|
|
|
foreach(TileItem* tile, m_exposedTileItems)
|
|
{
|
|
if(!tile->paint(painter, m_boundingRect.topLeft()))
|
|
{
|
|
allExposedPainted = false;
|
|
}
|
|
}
|
|
|
|
if(allExposedPainted)
|
|
{
|
|
foreach(TileItem* tile, m_exposedTileItems)
|
|
{
|
|
tile->dropPixmap();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(s_settings->pageItem().decoratePages() && !presentationMode())
|
|
{
|
|
// border
|
|
|
|
painter->save();
|
|
|
|
painter->setClipping(false);
|
|
|
|
painter->drawRect(m_renderParam.trimMargins() ? PageItem::boundingRect() : PageItem::uncroppedBoundingRect());
|
|
|
|
painter->restore();
|
|
}
|
|
}
|
|
|
|
inline void PageItem::paintLinks(QPainter* painter) const
|
|
{
|
|
if(s_settings->pageItem().decorateLinks() && !presentationMode() && !m_links.isEmpty())
|
|
{
|
|
painter->save();
|
|
|
|
painter->setTransform(m_normalizedTransform, true);
|
|
painter->setPen(QPen(Qt::red, 0.0));
|
|
|
|
foreach(const Model::Link* link, m_links)
|
|
{
|
|
painter->drawPath(link->boundary);
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
}
|
|
|
|
inline void PageItem::paintFormFields(QPainter* painter) const
|
|
{
|
|
if(s_settings->pageItem().decorateFormFields() && !presentationMode() && !m_formFields.isEmpty())
|
|
{
|
|
painter->save();
|
|
|
|
painter->setTransform(m_normalizedTransform, true);
|
|
painter->setPen(QPen(Qt::blue, 0.0));
|
|
|
|
foreach(const Model::FormField* formField, m_formFields)
|
|
{
|
|
painter->drawRect(formField->boundary());
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
}
|
|
|
|
inline void PageItem::paintHighlights(QPainter* painter) const
|
|
{
|
|
if(!m_highlights.isEmpty())
|
|
{
|
|
painter->save();
|
|
|
|
painter->setTransform(m_transform, true);
|
|
painter->setPen(QPen(s_settings->pageItem().highlightColor(), 0.0));
|
|
painter->setBrush(QBrush(s_settings->pageItem().highlightColor()));
|
|
painter->setCompositionMode(QPainter::CompositionMode_Multiply);
|
|
|
|
foreach(const QRectF highlight, m_highlights)
|
|
{
|
|
painter->drawRect(highlight.normalized());
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
}
|
|
|
|
inline void PageItem::paintRubberBand(QPainter* painter) const
|
|
{
|
|
if(!m_rubberBand.isNull())
|
|
{
|
|
painter->save();
|
|
|
|
painter->setPen(QPen(Qt::white, 0.0, Qt::DashLine));
|
|
painter->setCompositionMode(QPainter::CompositionMode_Difference);
|
|
|
|
painter->drawRect(m_rubberBand);
|
|
|
|
painter->restore();
|
|
}
|
|
}
|
|
|
|
} // qpdfview
|