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 }