okular/part/pageviewutils.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

394 lines
12 KiB
C++
Raw Normal View History

2021-05-24 07:25:56 +00:00
/*
SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
Work sponsored by the LiMux project of the city of Munich:
SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
2021-05-24 07:25:56 +00:00
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pageviewutils.h"
// qt/kde includes
2020-07-08 11:54:37 +00:00
#include <QApplication>
#include <QMenu>
#include <QPainter>
#include <QTimer>
// local includes
#include "core/form.h"
#include "core/page.h"
#include "formwidgets.h"
#include "settings.h"
#include "videowidget.h"
/*********************/
/** PageViewItem */
/*********************/
PageViewItem::PageViewItem(const Okular::Page *page)
: m_page(page)
, m_zoomFactor(1.0)
, m_visible(true)
, m_formsVisible(false)
, m_crop(0., 0., 1., 1.)
{
}
PageViewItem::~PageViewItem()
{
qDeleteAll(m_formWidgets);
qDeleteAll(m_videoWidgets);
}
const Okular::Page *PageViewItem::page() const
{
return m_page;
}
int PageViewItem::pageNumber() const
{
return m_page->number();
}
const QRect &PageViewItem::croppedGeometry() const
{
return m_croppedGeometry;
}
int PageViewItem::croppedWidth() const
{
return m_croppedGeometry.width();
}
int PageViewItem::croppedHeight() const
{
return m_croppedGeometry.height();
}
const QRect &PageViewItem::uncroppedGeometry() const
{
return m_uncroppedGeometry;
}
int PageViewItem::uncroppedWidth() const
{
return m_uncroppedGeometry.width();
}
int PageViewItem::uncroppedHeight() const
{
return m_uncroppedGeometry.height();
}
const Okular::NormalizedRect &PageViewItem::crop() const
{
return m_crop;
}
double PageViewItem::zoomFactor() const
{
return m_zoomFactor;
}
double PageViewItem::absToPageX(double absX) const
{
return (absX - m_uncroppedGeometry.left()) / m_uncroppedGeometry.width();
}
double PageViewItem::absToPageY(double absY) const
{
return (absY - m_uncroppedGeometry.top()) / m_uncroppedGeometry.height();
}
bool PageViewItem::isVisible() const
{
return m_visible;
}
QSet<FormWidgetIface *> &PageViewItem::formWidgets()
{
return m_formWidgets;
}
QHash<Okular::Movie *, VideoWidget *> &PageViewItem::videoWidgets()
{
return m_videoWidgets;
}
void PageViewItem::setWHZC(int w, int h, double z, const Okular::NormalizedRect &c)
{
m_croppedGeometry.setWidth(w);
m_croppedGeometry.setHeight(h);
m_zoomFactor = z;
m_crop = c;
m_uncroppedGeometry.setWidth(qRound(w / (c.right - c.left)));
m_uncroppedGeometry.setHeight(qRound(h / (c.bottom - c.top)));
for (FormWidgetIface *fwi : qAsConst(m_formWidgets)) {
Okular::NormalizedRect r = fwi->rect();
fwi->setWidthHeight(qRound(fabs(r.right - r.left) * m_uncroppedGeometry.width()), qRound(fabs(r.bottom - r.top) * m_uncroppedGeometry.height()));
}
for (VideoWidget *vw : qAsConst(m_videoWidgets)) {
const Okular::NormalizedRect r = vw->normGeometry();
vw->resize(qRound(fabs(r.right - r.left) * m_uncroppedGeometry.width()), qRound(fabs(r.bottom - r.top) * m_uncroppedGeometry.height()));
}
}
void PageViewItem::moveTo(int x, int y)
// Assumes setWHZC() has already been called
{
m_croppedGeometry.moveLeft(x);
m_croppedGeometry.moveTop(y);
m_uncroppedGeometry.moveLeft(qRound(x - m_crop.left * m_uncroppedGeometry.width()));
m_uncroppedGeometry.moveTop(qRound(y - m_crop.top * m_uncroppedGeometry.height()));
QSet<FormWidgetIface *>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for (; it != itEnd; ++it) {
Okular::NormalizedRect r = (*it)->rect();
(*it)->moveTo(qRound(x + m_uncroppedGeometry.width() * r.left) + 1, qRound(y + m_uncroppedGeometry.height() * r.top) + 1);
}
for (VideoWidget *vw : qAsConst(m_videoWidgets)) {
const Okular::NormalizedRect r = vw->normGeometry();
vw->move(qRound(x + m_uncroppedGeometry.width() * r.left) + 1, qRound(y + m_uncroppedGeometry.height() * r.top) + 1);
}
}
void PageViewItem::setVisible(bool visible)
{
setFormWidgetsVisible(visible && m_formsVisible);
m_visible = visible;
}
void PageViewItem::invalidate()
{
m_croppedGeometry.setRect(0, 0, 0, 0);
m_uncroppedGeometry.setRect(0, 0, 0, 0);
}
bool PageViewItem::setFormWidgetsVisible(bool visible)
{
m_formsVisible = visible;
if (!m_visible) {
return false;
}
bool somehadfocus = false;
QSet<FormWidgetIface *>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for (; it != itEnd; ++it) {
bool hadfocus = (*it)->setVisibility(visible && (*it)->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown((*it)->formField()));
somehadfocus = somehadfocus || hadfocus;
}
return somehadfocus;
}
void PageViewItem::reloadFormWidgetsState()
{
for (FormWidgetIface *fwi : qAsConst(m_formWidgets)) {
fwi->setVisibility(fwi->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown(fwi->formField()));
}
}
/*********************/
/** PageViewMessage */
/*********************/
PageViewMessage::PageViewMessage(QWidget *parent)
: QWidget(parent)
, m_timer(nullptr)
, m_lineSpacing(0)
{
2015-10-29 12:37:11 +00:00
setObjectName(QStringLiteral("pageViewMessage"));
setFocusPolicy(Qt::NoFocus);
QPalette pal = palette();
pal.setColor(QPalette::Active, QPalette::Window, QApplication::palette().color(QPalette::Active, QPalette::Window));
setPalette(pal);
// if the layout is LtR, we can safely place it in the right position
if (layoutDirection() == Qt::LeftToRight) {
move(10, 10);
}
resize(0, 0);
hide();
}
void PageViewMessage::display(const QString &message, const QString &details, Icon icon, int durationMs)
// give Caesar what belongs to Caesar: code taken from Amarok's osd.h/.cpp
// "redde (reddite, pl.) cesari quae sunt cesaris", just btw. :)
// The code has been heavily modified since then.
{
if (!Okular::Settings::showOSD()) {
hide();
return;
}
// set text
m_message = message;
m_details = details;
// reset vars
m_lineSpacing = 0;
// load icon (if set)
m_symbol = QIcon();
if (icon != None) {
switch (icon) {
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
case Annotation:
m_symbol = QIcon::fromTheme(QStringLiteral("draw-freehand"));
Adding support for annotations in framework. Only need to add and implement annotations now (and create the save/load procedure). Annotations: converging to a stable Annotation definition. Changed a bit the paint functions. Added a first 'template' annotation, a simple pen-like segments recorder for framework testing purposes only. This has events filters in place and the rough paint function implemented. PageView: removed the MouseEdit mode and using that button for toggling the editToolBox instead. Added Annotation support. When the Annotation is created, all pageView events flow through that new object. Repaint of damaged/old areas is done internally and is based on the geometry of the annotation we're creating. When an Annotation is complete, it is reparented to the Page that adds it to its internal list. From that point on the annotation will be rendered by pagePainter using the pixmap-based paint function provided by the annotation itself. PagePainter: draws annotations stored in pages when rendering (using the 'rought paint function' till the good pixmap based one will be in place. Page: added preliminary support for adding Annotation(s) to the page and deleting them all. Document: added the pass-through call to add an Annotation to the Page and notify observers. PageViewToolbox: can be draged and attached to any side. Position is remembered between runs (choose your side and that the toolbox will always be there). Available on Right and Bottom sides too. Emits -1 when the current tool is deselected. Misc: added Annotations to both the 'observers changed flags' and the 'pagepainter' ones and updated ui classes accordingly. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=390638
2005-02-18 18:24:45 +00:00
break;
case Find:
m_symbol = QIcon::fromTheme(QStringLiteral("zoom-original"));
break;
case Error:
m_symbol = QIcon::fromTheme(QStringLiteral("dialog-error"));
break;
case Warning:
m_symbol = QIcon::fromTheme(QStringLiteral("dialog-warning"));
break;
default:
m_symbol = QIcon::fromTheme(QStringLiteral("dialog-information"));
break;
}
}
computeSizeAndResize();
// show widget and schedule a repaint
show();
update();
// close the message window after given mS
if (durationMs > 0) {
if (!m_timer) {
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
2014-10-01 17:40:48 +00:00
connect(m_timer, &QTimer::timeout, this, &PageViewMessage::hide);
}
m_timer->start(durationMs);
} else if (m_timer) {
m_timer->stop();
}
qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->installEventFilter(this);
}
QRect PageViewMessage::computeTextRect(const QString &message, int extra_width) const
// Return the QRect which embeds the text
{
int charSize = fontMetrics().averageCharWidth();
/* width of the viewport, minus 20 (~ size removed by further resizing),
minus the extra size (usually the icon width), minus (a bit empirical)
twice the mean width of a character to ensure that the bounding box is
really smaller than the container.
*/
const int boundingWidth = qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->width() - 20 - (extra_width > 0 ? 2 + extra_width : 0) - 2 * charSize;
QRect textRect = fontMetrics().boundingRect(0, 0, boundingWidth, 0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
textRect.translate(-textRect.left(), -textRect.top());
textRect.adjust(0, 0, 2, 2);
return textRect;
}
void PageViewMessage::computeSizeAndResize()
{
const auto symbolSize = !m_symbol.isNull() ? style()->pixelMetric(QStyle::PM_SmallIconSize) : 0;
// determine text rectangle
const QRect textRect = computeTextRect(m_message, symbolSize);
int width = textRect.width(), height = textRect.height();
if (!m_details.isEmpty()) {
// determine details text rectangle
const QRect detailsRect = computeTextRect(m_details, symbolSize);
width = qMax(width, detailsRect.width());
height += detailsRect.height();
// plus add a ~60% line spacing
m_lineSpacing = static_cast<int>(fontMetrics().height() * 0.6);
height += m_lineSpacing;
}
// update geometry with icon information
if (!m_symbol.isNull()) {
width += 2 + symbolSize;
height = qMax(height, symbolSize);
}
// resize widget
resize(QRect(0, 0, width + 10, height + 8).size());
// if the layout is RtL, we can move it to the right place only after we
// know how much size it will take
if (layoutDirection() == Qt::RightToLeft) {
move(parentWidget()->width() - geometry().width() - 10 - 1, 10);
}
}
bool PageViewMessage::eventFilter(QObject *obj, QEvent *event)
{
/* if the parent object (scroll area) resizes, the message should
resize as well */
if (event->type() == QEvent::Resize) {
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
if (resizeEvent->oldSize() != resizeEvent->size()) {
computeSizeAndResize();
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
void PageViewMessage::paintEvent(QPaintEvent * /* e */)
{
const auto symbolSize = !m_symbol.isNull() ? style()->pixelMetric(QStyle::PM_SmallIconSize) : 0;
const QRect textRect = computeTextRect(m_message, symbolSize);
QRect detailsRect;
if (!m_details.isEmpty()) {
detailsRect = computeTextRect(m_details, symbolSize);
}
int textXOffset = 0,
2020-09-24 14:10:59 +00:00
// add 2 to account for the reduced drawRoundedRect later
textYOffset = (geometry().height() - textRect.height() - detailsRect.height() - m_lineSpacing + 2) / 2, iconXOffset = 0, iconYOffset = !m_symbol.isNull() ? (geometry().height() - symbolSize) / 2 : 0, shadowOffset = 1;
if (layoutDirection() == Qt::RightToLeft) {
iconXOffset = 2 + textRect.width();
} else {
textXOffset = 2 + symbolSize;
}
// draw background
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::black);
painter.setBrush(palette().color(QPalette::Window));
painter.translate(0.5, 0.5);
painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 600.0 / width(), 600.0 / height(), Qt::RelativeSize);
// draw icon if present
if (!m_symbol.isNull()) {
painter.drawPixmap(5 + iconXOffset, iconYOffset, m_symbol.pixmap(symbolSize));
}
const int xStartPoint = 5 + textXOffset;
const int yStartPoint = textYOffset;
const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap;
// draw shadow and text
2019-03-13 05:58:11 +00:00
painter.setPen(palette().color(QPalette::Window).darker(115));
painter.drawText(xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, m_message);
if (!m_details.isEmpty()) {
painter.drawText(xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing + shadowOffset, textRect.width(), detailsRect.height(), textDrawingFlags, m_details);
}
painter.setPen(palette().color(QPalette::WindowText));
painter.drawText(xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, m_message);
if (!m_details.isEmpty()) {
painter.drawText(xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing, textRect.width(), detailsRect.height(), textDrawingFlags, m_details);
}
}
void PageViewMessage::mousePressEvent(QMouseEvent * /*e*/)
{
if (m_timer) {
m_timer->stop();
}
hide();
}