mirror of
https://invent.kde.org/graphics/okular
synced 2024-11-05 18:34:53 +00:00
792425fc77
We now definitely has std::as_const available and Qt has started nagging about converting to std::as_const. Implementation is the same for both functions, and qAsConst was a stop-gap measure until std::as_const was sufficiently available.
393 lines
12 KiB
C++
393 lines
12 KiB
C++
/*
|
|
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>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "pageviewutils.h"
|
|
|
|
// qt/kde includes
|
|
#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 : std::as_const(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 : std::as_const(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 : std::as_const(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 : std::as_const(m_formWidgets)) {
|
|
fwi->setVisibility(fwi->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown(fwi->formField()));
|
|
}
|
|
}
|
|
|
|
/*********************/
|
|
/** PageViewMessage */
|
|
/*********************/
|
|
|
|
PageViewMessage::PageViewMessage(QWidget *parent)
|
|
: QWidget(parent)
|
|
, m_timer(nullptr)
|
|
, m_lineSpacing(0)
|
|
{
|
|
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) {
|
|
case Annotation:
|
|
m_symbol = QIcon::fromTheme(QStringLiteral("draw-freehand"));
|
|
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);
|
|
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,
|
|
// 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, 3, 3);
|
|
|
|
// 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
|
|
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();
|
|
}
|