File indexing completed on 2024-04-28 15:51:51

0001 /*
0002     SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
0003 
0004     Work sponsored by the LiMux project of the city of Munich:
0005     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "pageviewutils.h"
0011 
0012 // qt/kde includes
0013 #include <QApplication>
0014 #include <QMenu>
0015 #include <QPainter>
0016 #include <QTimer>
0017 
0018 // local includes
0019 #include "core/form.h"
0020 #include "core/page.h"
0021 #include "formwidgets.h"
0022 #include "settings.h"
0023 #include "videowidget.h"
0024 
0025 /*********************/
0026 /** PageViewItem     */
0027 /*********************/
0028 
0029 PageViewItem::PageViewItem(const Okular::Page *page)
0030     : m_page(page)
0031     , m_zoomFactor(1.0)
0032     , m_visible(true)
0033     , m_formsVisible(false)
0034     , m_crop(0., 0., 1., 1.)
0035 {
0036 }
0037 
0038 PageViewItem::~PageViewItem()
0039 {
0040     qDeleteAll(m_formWidgets);
0041     qDeleteAll(m_videoWidgets);
0042 }
0043 
0044 const Okular::Page *PageViewItem::page() const
0045 {
0046     return m_page;
0047 }
0048 
0049 int PageViewItem::pageNumber() const
0050 {
0051     return m_page->number();
0052 }
0053 
0054 const QRect &PageViewItem::croppedGeometry() const
0055 {
0056     return m_croppedGeometry;
0057 }
0058 
0059 int PageViewItem::croppedWidth() const
0060 {
0061     return m_croppedGeometry.width();
0062 }
0063 
0064 int PageViewItem::croppedHeight() const
0065 {
0066     return m_croppedGeometry.height();
0067 }
0068 
0069 const QRect &PageViewItem::uncroppedGeometry() const
0070 {
0071     return m_uncroppedGeometry;
0072 }
0073 
0074 int PageViewItem::uncroppedWidth() const
0075 {
0076     return m_uncroppedGeometry.width();
0077 }
0078 
0079 int PageViewItem::uncroppedHeight() const
0080 {
0081     return m_uncroppedGeometry.height();
0082 }
0083 
0084 const Okular::NormalizedRect &PageViewItem::crop() const
0085 {
0086     return m_crop;
0087 }
0088 
0089 double PageViewItem::zoomFactor() const
0090 {
0091     return m_zoomFactor;
0092 }
0093 
0094 double PageViewItem::absToPageX(double absX) const
0095 {
0096     return (absX - m_uncroppedGeometry.left()) / m_uncroppedGeometry.width();
0097 }
0098 
0099 double PageViewItem::absToPageY(double absY) const
0100 {
0101     return (absY - m_uncroppedGeometry.top()) / m_uncroppedGeometry.height();
0102 }
0103 
0104 bool PageViewItem::isVisible() const
0105 {
0106     return m_visible;
0107 }
0108 
0109 QSet<FormWidgetIface *> &PageViewItem::formWidgets()
0110 {
0111     return m_formWidgets;
0112 }
0113 
0114 QHash<Okular::Movie *, VideoWidget *> &PageViewItem::videoWidgets()
0115 {
0116     return m_videoWidgets;
0117 }
0118 
0119 void PageViewItem::setWHZC(int w, int h, double z, const Okular::NormalizedRect &c)
0120 {
0121     m_croppedGeometry.setWidth(w);
0122     m_croppedGeometry.setHeight(h);
0123     m_zoomFactor = z;
0124     m_crop = c;
0125     m_uncroppedGeometry.setWidth(qRound(w / (c.right - c.left)));
0126     m_uncroppedGeometry.setHeight(qRound(h / (c.bottom - c.top)));
0127     for (FormWidgetIface *fwi : std::as_const(m_formWidgets)) {
0128         Okular::NormalizedRect r = fwi->rect();
0129         fwi->setWidthHeight(qRound(fabs(r.right - r.left) * m_uncroppedGeometry.width()), qRound(fabs(r.bottom - r.top) * m_uncroppedGeometry.height()));
0130     }
0131     for (VideoWidget *vw : std::as_const(m_videoWidgets)) {
0132         const Okular::NormalizedRect r = vw->normGeometry();
0133         vw->resize(qRound(fabs(r.right - r.left) * m_uncroppedGeometry.width()), qRound(fabs(r.bottom - r.top) * m_uncroppedGeometry.height()));
0134     }
0135 }
0136 
0137 void PageViewItem::moveTo(int x, int y)
0138 // Assumes setWHZC() has already been called
0139 {
0140     m_croppedGeometry.moveLeft(x);
0141     m_croppedGeometry.moveTop(y);
0142     m_uncroppedGeometry.moveLeft(qRound(x - m_crop.left * m_uncroppedGeometry.width()));
0143     m_uncroppedGeometry.moveTop(qRound(y - m_crop.top * m_uncroppedGeometry.height()));
0144     QSet<FormWidgetIface *>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
0145     for (; it != itEnd; ++it) {
0146         Okular::NormalizedRect r = (*it)->rect();
0147         (*it)->moveTo(qRound(x + m_uncroppedGeometry.width() * r.left) + 1, qRound(y + m_uncroppedGeometry.height() * r.top) + 1);
0148     }
0149     for (VideoWidget *vw : std::as_const(m_videoWidgets)) {
0150         const Okular::NormalizedRect r = vw->normGeometry();
0151         vw->move(qRound(x + m_uncroppedGeometry.width() * r.left) + 1, qRound(y + m_uncroppedGeometry.height() * r.top) + 1);
0152     }
0153 }
0154 
0155 void PageViewItem::setVisible(bool visible)
0156 {
0157     setFormWidgetsVisible(visible && m_formsVisible);
0158     m_visible = visible;
0159 }
0160 
0161 void PageViewItem::invalidate()
0162 {
0163     m_croppedGeometry.setRect(0, 0, 0, 0);
0164     m_uncroppedGeometry.setRect(0, 0, 0, 0);
0165 }
0166 
0167 bool PageViewItem::setFormWidgetsVisible(bool visible)
0168 {
0169     m_formsVisible = visible;
0170 
0171     if (!m_visible) {
0172         return false;
0173     }
0174 
0175     bool somehadfocus = false;
0176     QSet<FormWidgetIface *>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
0177     for (; it != itEnd; ++it) {
0178         bool hadfocus = (*it)->setVisibility(visible && (*it)->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown((*it)->formField()));
0179         somehadfocus = somehadfocus || hadfocus;
0180     }
0181     return somehadfocus;
0182 }
0183 
0184 void PageViewItem::reloadFormWidgetsState()
0185 {
0186     for (FormWidgetIface *fwi : std::as_const(m_formWidgets)) {
0187         fwi->setVisibility(fwi->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown(fwi->formField()));
0188     }
0189 }
0190 
0191 /*********************/
0192 /** PageViewMessage  */
0193 /*********************/
0194 
0195 PageViewMessage::PageViewMessage(QWidget *parent)
0196     : QWidget(parent)
0197     , m_timer(nullptr)
0198     , m_lineSpacing(0)
0199 {
0200     setObjectName(QStringLiteral("pageViewMessage"));
0201     setFocusPolicy(Qt::NoFocus);
0202     QPalette pal = palette();
0203     pal.setColor(QPalette::Active, QPalette::Window, QApplication::palette().color(QPalette::Active, QPalette::Window));
0204     setPalette(pal);
0205     // if the layout is LtR, we can safely place it in the right position
0206     if (layoutDirection() == Qt::LeftToRight) {
0207         move(10, 10);
0208     }
0209     resize(0, 0);
0210     hide();
0211 }
0212 
0213 void PageViewMessage::display(const QString &message, const QString &details, Icon icon, int durationMs)
0214 // give Caesar what belongs to Caesar: code taken from Amarok's osd.h/.cpp
0215 // "redde (reddite, pl.) cesari quae sunt cesaris", just btw.  :)
0216 // The code has been heavily modified since then.
0217 {
0218     if (!Okular::Settings::showOSD()) {
0219         hide();
0220         return;
0221     }
0222 
0223     // set text
0224     m_message = message;
0225     m_details = details;
0226     // reset vars
0227     m_lineSpacing = 0;
0228 
0229     // load icon (if set)
0230     m_symbol = QIcon();
0231     if (icon != None) {
0232         switch (icon) {
0233         case Annotation:
0234             m_symbol = QIcon::fromTheme(QStringLiteral("draw-freehand"));
0235             break;
0236         case Find:
0237             m_symbol = QIcon::fromTheme(QStringLiteral("zoom-original"));
0238             break;
0239         case Error:
0240             m_symbol = QIcon::fromTheme(QStringLiteral("dialog-error"));
0241             break;
0242         case Warning:
0243             m_symbol = QIcon::fromTheme(QStringLiteral("dialog-warning"));
0244             break;
0245         default:
0246             m_symbol = QIcon::fromTheme(QStringLiteral("dialog-information"));
0247             break;
0248         }
0249     }
0250 
0251     computeSizeAndResize();
0252     // show widget and schedule a repaint
0253     show();
0254     update();
0255 
0256     // close the message window after given mS
0257     if (durationMs > 0) {
0258         if (!m_timer) {
0259             m_timer = new QTimer(this);
0260             m_timer->setSingleShot(true);
0261             connect(m_timer, &QTimer::timeout, this, &PageViewMessage::hide);
0262         }
0263         m_timer->start(durationMs);
0264     } else if (m_timer) {
0265         m_timer->stop();
0266     }
0267 
0268     qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->installEventFilter(this);
0269 }
0270 
0271 QRect PageViewMessage::computeTextRect(const QString &message, int extra_width) const
0272 // Return the QRect which embeds the text
0273 {
0274     int charSize = fontMetrics().averageCharWidth();
0275     /* width of the viewport, minus 20 (~ size removed by further resizing),
0276        minus the extra size (usually the icon width), minus (a bit empirical)
0277        twice the mean width of a character to ensure that the bounding box is
0278        really smaller than the container.
0279      */
0280     const int boundingWidth = qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->width() - 20 - (extra_width > 0 ? 2 + extra_width : 0) - 2 * charSize;
0281     QRect textRect = fontMetrics().boundingRect(0, 0, boundingWidth, 0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
0282     textRect.translate(-textRect.left(), -textRect.top());
0283     textRect.adjust(0, 0, 2, 2);
0284 
0285     return textRect;
0286 }
0287 
0288 void PageViewMessage::computeSizeAndResize()
0289 {
0290     const auto symbolSize = !m_symbol.isNull() ? style()->pixelMetric(QStyle::PM_SmallIconSize) : 0;
0291 
0292     // determine text rectangle
0293     const QRect textRect = computeTextRect(m_message, symbolSize);
0294     int width = textRect.width(), height = textRect.height();
0295 
0296     if (!m_details.isEmpty()) {
0297         // determine details text rectangle
0298         const QRect detailsRect = computeTextRect(m_details, symbolSize);
0299         width = qMax(width, detailsRect.width());
0300         height += detailsRect.height();
0301 
0302         // plus add a ~60% line spacing
0303         m_lineSpacing = static_cast<int>(fontMetrics().height() * 0.6);
0304         height += m_lineSpacing;
0305     }
0306 
0307     // update geometry with icon information
0308     if (!m_symbol.isNull()) {
0309         width += 2 + symbolSize;
0310         height = qMax(height, symbolSize);
0311     }
0312 
0313     // resize widget
0314     resize(QRect(0, 0, width + 10, height + 8).size());
0315 
0316     // if the layout is RtL, we can move it to the right place only after we
0317     // know how much size it will take
0318     if (layoutDirection() == Qt::RightToLeft) {
0319         move(parentWidget()->width() - geometry().width() - 10 - 1, 10);
0320     }
0321 }
0322 
0323 bool PageViewMessage::eventFilter(QObject *obj, QEvent *event)
0324 {
0325     /* if the parent object (scroll area) resizes, the message should
0326        resize as well */
0327     if (event->type() == QEvent::Resize) {
0328         QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
0329         if (resizeEvent->oldSize() != resizeEvent->size()) {
0330             computeSizeAndResize();
0331         }
0332     }
0333     // standard event processing
0334     return QObject::eventFilter(obj, event);
0335 }
0336 
0337 void PageViewMessage::paintEvent(QPaintEvent * /* e */)
0338 {
0339     const auto symbolSize = !m_symbol.isNull() ? style()->pixelMetric(QStyle::PM_SmallIconSize) : 0;
0340     const QRect textRect = computeTextRect(m_message, symbolSize);
0341 
0342     QRect detailsRect;
0343     if (!m_details.isEmpty()) {
0344         detailsRect = computeTextRect(m_details, symbolSize);
0345     }
0346 
0347     int textXOffset = 0,
0348         // add 2 to account for the reduced drawRoundedRect later
0349         textYOffset = (geometry().height() - textRect.height() - detailsRect.height() - m_lineSpacing + 2) / 2, iconXOffset = 0, iconYOffset = !m_symbol.isNull() ? (geometry().height() - symbolSize) / 2 : 0, shadowOffset = 1;
0350 
0351     if (layoutDirection() == Qt::RightToLeft) {
0352         iconXOffset = 2 + textRect.width();
0353     } else {
0354         textXOffset = 2 + symbolSize;
0355     }
0356 
0357     // draw background
0358     QPainter painter(this);
0359     painter.setRenderHint(QPainter::Antialiasing, true);
0360     painter.setPen(Qt::black);
0361     painter.setBrush(palette().color(QPalette::Window));
0362     painter.translate(0.5, 0.5);
0363     painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 3, 3);
0364 
0365     // draw icon if present
0366     if (!m_symbol.isNull()) {
0367         painter.drawPixmap(5 + iconXOffset, iconYOffset, m_symbol.pixmap(symbolSize));
0368     }
0369 
0370     const int xStartPoint = 5 + textXOffset;
0371     const int yStartPoint = textYOffset;
0372     const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap;
0373 
0374     // draw shadow and text
0375     painter.setPen(palette().color(QPalette::Window).darker(115));
0376     painter.drawText(xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, m_message);
0377     if (!m_details.isEmpty()) {
0378         painter.drawText(xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing + shadowOffset, textRect.width(), detailsRect.height(), textDrawingFlags, m_details);
0379     }
0380     painter.setPen(palette().color(QPalette::WindowText));
0381     painter.drawText(xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, m_message);
0382     if (!m_details.isEmpty()) {
0383         painter.drawText(xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing, textRect.width(), detailsRect.height(), textDrawingFlags, m_details);
0384     }
0385 }
0386 
0387 void PageViewMessage::mousePressEvent(QMouseEvent * /*e*/)
0388 {
0389     if (m_timer) {
0390         m_timer->stop();
0391     }
0392     hide();
0393 }