1 /*
2 SPDX-FileCopyrightText: 2004-2005 Enrico Ros <eros.kde@email.it>
3
4 Work sponsored by the LiMux project of the city of Munich:
5 SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "pageviewutils.h"
11
12 // qt/kde includes
13 #include <QApplication>
14 #include <QMenu>
15 #include <QPainter>
16 #include <QTimer>
17
18 // local includes
19 #include "core/form.h"
20 #include "core/page.h"
21 #include "formwidgets.h"
22 #include "settings.h"
23 #include "videowidget.h"
24
25 /*********************/
26 /** PageViewItem */
27 /*********************/
28
PageViewItem(const Okular::Page * page)29 PageViewItem::PageViewItem(const Okular::Page *page)
30 : m_page(page)
31 , m_zoomFactor(1.0)
32 , m_visible(true)
33 , m_formsVisible(false)
34 , m_crop(0., 0., 1., 1.)
35 {
36 }
37
~PageViewItem()38 PageViewItem::~PageViewItem()
39 {
40 qDeleteAll(m_formWidgets);
41 qDeleteAll(m_videoWidgets);
42 }
43
page() const44 const Okular::Page *PageViewItem::page() const
45 {
46 return m_page;
47 }
48
pageNumber() const49 int PageViewItem::pageNumber() const
50 {
51 return m_page->number();
52 }
53
croppedGeometry() const54 const QRect &PageViewItem::croppedGeometry() const
55 {
56 return m_croppedGeometry;
57 }
58
croppedWidth() const59 int PageViewItem::croppedWidth() const
60 {
61 return m_croppedGeometry.width();
62 }
63
croppedHeight() const64 int PageViewItem::croppedHeight() const
65 {
66 return m_croppedGeometry.height();
67 }
68
uncroppedGeometry() const69 const QRect &PageViewItem::uncroppedGeometry() const
70 {
71 return m_uncroppedGeometry;
72 }
73
uncroppedWidth() const74 int PageViewItem::uncroppedWidth() const
75 {
76 return m_uncroppedGeometry.width();
77 }
78
uncroppedHeight() const79 int PageViewItem::uncroppedHeight() const
80 {
81 return m_uncroppedGeometry.height();
82 }
83
crop() const84 const Okular::NormalizedRect &PageViewItem::crop() const
85 {
86 return m_crop;
87 }
88
zoomFactor() const89 double PageViewItem::zoomFactor() const
90 {
91 return m_zoomFactor;
92 }
93
absToPageX(double absX) const94 double PageViewItem::absToPageX(double absX) const
95 {
96 return (absX - m_uncroppedGeometry.left()) / m_uncroppedGeometry.width();
97 }
98
absToPageY(double absY) const99 double PageViewItem::absToPageY(double absY) const
100 {
101 return (absY - m_uncroppedGeometry.top()) / m_uncroppedGeometry.height();
102 }
103
isVisible() const104 bool PageViewItem::isVisible() const
105 {
106 return m_visible;
107 }
108
formWidgets()109 QSet<FormWidgetIface *> &PageViewItem::formWidgets()
110 {
111 return m_formWidgets;
112 }
113
videoWidgets()114 QHash<Okular::Movie *, VideoWidget *> &PageViewItem::videoWidgets()
115 {
116 return m_videoWidgets;
117 }
118
setWHZC(int w,int h,double z,const Okular::NormalizedRect & c)119 void PageViewItem::setWHZC(int w, int h, double z, const Okular::NormalizedRect &c)
120 {
121 m_croppedGeometry.setWidth(w);
122 m_croppedGeometry.setHeight(h);
123 m_zoomFactor = z;
124 m_crop = c;
125 m_uncroppedGeometry.setWidth(qRound(w / (c.right - c.left)));
126 m_uncroppedGeometry.setHeight(qRound(h / (c.bottom - c.top)));
127 for (FormWidgetIface *fwi : qAsConst(m_formWidgets)) {
128 Okular::NormalizedRect r = fwi->rect();
129 fwi->setWidthHeight(qRound(fabs(r.right - r.left) * m_uncroppedGeometry.width()), qRound(fabs(r.bottom - r.top) * m_uncroppedGeometry.height()));
130 }
131 for (VideoWidget *vw : qAsConst(m_videoWidgets)) {
132 const Okular::NormalizedRect r = vw->normGeometry();
133 vw->resize(qRound(fabs(r.right - r.left) * m_uncroppedGeometry.width()), qRound(fabs(r.bottom - r.top) * m_uncroppedGeometry.height()));
134 }
135 }
136
moveTo(int x,int y)137 void PageViewItem::moveTo(int x, int y)
138 // Assumes setWHZC() has already been called
139 {
140 m_croppedGeometry.moveLeft(x);
141 m_croppedGeometry.moveTop(y);
142 m_uncroppedGeometry.moveLeft(qRound(x - m_crop.left * m_uncroppedGeometry.width()));
143 m_uncroppedGeometry.moveTop(qRound(y - m_crop.top * m_uncroppedGeometry.height()));
144 QSet<FormWidgetIface *>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
145 for (; it != itEnd; ++it) {
146 Okular::NormalizedRect r = (*it)->rect();
147 (*it)->moveTo(qRound(x + m_uncroppedGeometry.width() * r.left) + 1, qRound(y + m_uncroppedGeometry.height() * r.top) + 1);
148 }
149 for (VideoWidget *vw : qAsConst(m_videoWidgets)) {
150 const Okular::NormalizedRect r = vw->normGeometry();
151 vw->move(qRound(x + m_uncroppedGeometry.width() * r.left) + 1, qRound(y + m_uncroppedGeometry.height() * r.top) + 1);
152 }
153 }
154
setVisible(bool visible)155 void PageViewItem::setVisible(bool visible)
156 {
157 setFormWidgetsVisible(visible && m_formsVisible);
158 m_visible = visible;
159 }
160
invalidate()161 void PageViewItem::invalidate()
162 {
163 m_croppedGeometry.setRect(0, 0, 0, 0);
164 m_uncroppedGeometry.setRect(0, 0, 0, 0);
165 }
166
setFormWidgetsVisible(bool visible)167 bool PageViewItem::setFormWidgetsVisible(bool visible)
168 {
169 m_formsVisible = visible;
170
171 if (!m_visible)
172 return false;
173
174 bool somehadfocus = false;
175 QSet<FormWidgetIface *>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
176 for (; it != itEnd; ++it) {
177 bool hadfocus = (*it)->setVisibility(visible && (*it)->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown((*it)->formField()));
178 somehadfocus = somehadfocus || hadfocus;
179 }
180 return somehadfocus;
181 }
182
reloadFormWidgetsState()183 void PageViewItem::reloadFormWidgetsState()
184 {
185 for (FormWidgetIface *fwi : qAsConst(m_formWidgets)) {
186 fwi->setVisibility(fwi->formField()->isVisible() && FormWidgetsController::shouldFormWidgetBeShown(fwi->formField()));
187 }
188 }
189
190 /*********************/
191 /** PageViewMessage */
192 /*********************/
193
PageViewMessage(QWidget * parent)194 PageViewMessage::PageViewMessage(QWidget *parent)
195 : QWidget(parent)
196 , m_timer(nullptr)
197 , m_lineSpacing(0)
198 {
199 setObjectName(QStringLiteral("pageViewMessage"));
200 setFocusPolicy(Qt::NoFocus);
201 QPalette pal = palette();
202 pal.setColor(QPalette::Active, QPalette::Window, QApplication::palette().color(QPalette::Active, QPalette::Window));
203 setPalette(pal);
204 // if the layout is LtR, we can safely place it in the right position
205 if (layoutDirection() == Qt::LeftToRight)
206 move(10, 10);
207 resize(0, 0);
208 hide();
209 }
210
display(const QString & message,const QString & details,Icon icon,int durationMs)211 void PageViewMessage::display(const QString &message, const QString &details, Icon icon, int durationMs)
212 // give Caesar what belongs to Caesar: code taken from Amarok's osd.h/.cpp
213 // "redde (reddite, pl.) cesari quae sunt cesaris", just btw. :)
214 // The code has been heavily modified since then.
215 {
216 if (!Okular::Settings::showOSD()) {
217 hide();
218 return;
219 }
220
221 // set text
222 m_message = message;
223 m_details = details;
224 // reset vars
225 m_lineSpacing = 0;
226
227 // load icon (if set)
228 m_symbol = QPixmap();
229 const auto symbolSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
230 if (icon != None) {
231 switch (icon) {
232 case Annotation:
233 m_symbol = QIcon::fromTheme(QStringLiteral("draw-freehand")).pixmap(symbolSize);
234 break;
235 case Find:
236 m_symbol = QIcon::fromTheme(QStringLiteral("zoom-original")).pixmap(symbolSize);
237 break;
238 case Error:
239 m_symbol = QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(symbolSize);
240 break;
241 case Warning:
242 m_symbol = QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(symbolSize);
243 break;
244 default:
245 m_symbol = QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(symbolSize);
246 break;
247 }
248 }
249
250 computeSizeAndResize();
251 // show widget and schedule a repaint
252 show();
253 update();
254
255 // close the message window after given mS
256 if (durationMs > 0) {
257 if (!m_timer) {
258 m_timer = new QTimer(this);
259 m_timer->setSingleShot(true);
260 connect(m_timer, &QTimer::timeout, this, &PageViewMessage::hide);
261 }
262 m_timer->start(durationMs);
263 } else if (m_timer)
264 m_timer->stop();
265
266 qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->installEventFilter(this);
267 }
268
computeTextRect(const QString & message,int extra_width) const269 QRect PageViewMessage::computeTextRect(const QString &message, int extra_width) const
270 // Return the QRect which embeds the text
271 {
272 int charSize = fontMetrics().averageCharWidth();
273 /* width of the viewport, minus 20 (~ size removed by further resizing),
274 minus the extra size (usually the icon width), minus (a bit empirical)
275 twice the mean width of a character to ensure that the bounding box is
276 really smaller than the container.
277 */
278 const int boundingWidth = qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->width() - 20 - (extra_width > 0 ? 2 + extra_width : 0) - 2 * charSize;
279 QRect textRect = fontMetrics().boundingRect(0, 0, boundingWidth, 0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
280 textRect.translate(-textRect.left(), -textRect.top());
281 textRect.adjust(0, 0, 2, 2);
282
283 return textRect;
284 }
285
computeSizeAndResize()286 void PageViewMessage::computeSizeAndResize()
287 {
288 // determine text rectangle
289 const QRect textRect = computeTextRect(m_message, m_symbol.width());
290 int width = textRect.width(), height = textRect.height();
291
292 if (!m_details.isEmpty()) {
293 // determine details text rectangle
294 const QRect detailsRect = computeTextRect(m_details, m_symbol.width());
295 width = qMax(width, detailsRect.width());
296 height += detailsRect.height();
297
298 // plus add a ~60% line spacing
299 m_lineSpacing = static_cast<int>(fontMetrics().height() * 0.6);
300 height += m_lineSpacing;
301 }
302
303 // update geometry with icon information
304 if (!m_symbol.isNull()) {
305 width += 2 + m_symbol.width();
306 height = qMax(height, m_symbol.height());
307 }
308
309 // resize widget
310 resize(QRect(0, 0, width + 10, height + 8).size());
311
312 // if the layout is RtL, we can move it to the right place only after we
313 // know how much size it will take
314 if (layoutDirection() == Qt::RightToLeft)
315 move(parentWidget()->width() - geometry().width() - 10 - 1, 10);
316 }
317
eventFilter(QObject * obj,QEvent * event)318 bool PageViewMessage::eventFilter(QObject *obj, QEvent *event)
319 {
320 /* if the parent object (scroll area) resizes, the message should
321 resize as well */
322 if (event->type() == QEvent::Resize) {
323 QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
324 if (resizeEvent->oldSize() != resizeEvent->size()) {
325 computeSizeAndResize();
326 }
327 }
328 // standard event processing
329 return QObject::eventFilter(obj, event);
330 }
331
paintEvent(QPaintEvent *)332 void PageViewMessage::paintEvent(QPaintEvent * /* e */)
333 {
334 const QRect textRect = computeTextRect(m_message, m_symbol.width());
335
336 QRect detailsRect;
337 if (!m_details.isEmpty()) {
338 detailsRect = computeTextRect(m_details, m_symbol.width());
339 }
340
341 int textXOffset = 0,
342 // add 2 to account for the reduced drawRoundedRect later
343 textYOffset = (geometry().height() - textRect.height() - detailsRect.height() - m_lineSpacing + 2) / 2, iconXOffset = 0, iconYOffset = !m_symbol.isNull() ? (geometry().height() - m_symbol.height()) / 2 : 0, shadowOffset = 1;
344
345 if (layoutDirection() == Qt::RightToLeft)
346 iconXOffset = 2 + textRect.width();
347 else
348 textXOffset = 2 + m_symbol.width();
349
350 // draw background
351 QPainter painter(this);
352 painter.setRenderHint(QPainter::Antialiasing, true);
353 painter.setPen(Qt::black);
354 painter.setBrush(palette().color(QPalette::Window));
355 painter.translate(0.5, 0.5);
356 painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 1600.0 / width(), 1600.0 / height());
357
358 // draw icon if present
359 if (!m_symbol.isNull())
360 painter.drawPixmap(5 + iconXOffset, iconYOffset, m_symbol, 0, 0, m_symbol.width(), m_symbol.height());
361
362 const int xStartPoint = 5 + textXOffset;
363 const int yStartPoint = textYOffset;
364 const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap;
365
366 // draw shadow and text
367 painter.setPen(palette().color(QPalette::Window).darker(115));
368 painter.drawText(xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, m_message);
369 if (!m_details.isEmpty())
370 painter.drawText(xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing + shadowOffset, textRect.width(), detailsRect.height(), textDrawingFlags, m_details);
371 painter.setPen(palette().color(QPalette::WindowText));
372 painter.drawText(xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, m_message);
373 if (!m_details.isEmpty())
374 painter.drawText(xStartPoint + shadowOffset, yStartPoint + textRect.height() + m_lineSpacing, textRect.width(), detailsRect.height(), textDrawingFlags, m_details);
375 }
376
mousePressEvent(QMouseEvent *)377 void PageViewMessage::mousePressEvent(QMouseEvent * /*e*/)
378 {
379 if (m_timer)
380 m_timer->stop();
381 hide();
382 }
383