okular/part/pageviewutils.cpp
snooxx 💤 667e73325a
Restore properly rounded corners of popup messages
78d983ee led to some rounded corners in the UI not being perfectly
circular anymore: Popup messages would stretch the rounding depending on
message length, and the rounded corner of the message in the annotation
bar would get distorted while changing the sidebar's width.

That commit tried to fix the following `warning: ‘void
QPainter::drawRoundRect(int, int, int, int, int, int)’ is deprecated:
Use drawRoundedRect(..., Qt::RelativeSize)` by changing to
`drawRoundedRect`, but missed to also add `Qt::RelativeSize`.

Adding the missing flag fixes both issues. The change is also
implemented in `dviRenderer::epsf_special` (introduced in be544056,
changed in 55dc43bf) for correctness.

Test Plan:
  - Start Okular. The "Welcome" message and the "Document Loaded"
    message have perfectly rounded corners again, not depending
    on message length.
  - Change width of the annotations sidebar: The "No Annotations"
    message does not change its rounded corners anymore.
  - Running in HiDPI mode retains the correct behavior.
  - Note: There does not seem to be a trivial/convenient way to trigger
    the DVI code path.
2022-03-07 14:01:53 +00:00

394 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 : 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)
{
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, 1600.0 / width(), 1600.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
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();
}