/* SPDX-FileCopyrightText: 2004-2005 Enrico Ros Work sponsored by the LiMux project of the city of Munich: SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company SPDX-License-Identifier: GPL-2.0-or-later */ #include "pageviewutils.h" // qt/kde includes #include #include #include #include // 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 &PageViewItem::formWidgets() { return m_formWidgets; } QHash &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::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::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(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(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(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(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, 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 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(); }