Switching to the new signal-slot syntax
[trojita.git] / src / Gui / EmbeddedWebView.cpp
blobf66e644bedcc91bf238bb3577a2b6c68967b041e
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "EmbeddedWebView.h"
23 #include "MessageView.h"
24 #include "Gui/Util.h"
26 #include <QAbstractScrollArea>
27 #include <QAction>
28 #include <QApplication>
29 #include <QDesktopServices>
30 #include <QDesktopWidget>
31 #include <QLayout>
32 #include <QMouseEvent>
33 #include <QNetworkReply>
34 #include <QScrollBar>
35 #include <QStyle>
36 #include <QStyleFactory>
37 #include <QTimer>
38 #include <QWebFrame>
39 #include <QWebHistory>
41 #include <QDebug>
43 namespace {
45 /** @short RAII pattern for counter manipulation */
46 class Incrementor {
47 int *m_int;
48 public:
49 Incrementor(int *what): m_int(what)
51 ++(*m_int);
53 ~Incrementor()
55 --(*m_int);
56 Q_ASSERT(*m_int >= 0);
62 namespace Gui
65 EmbeddedWebView::EmbeddedWebView(QWidget *parent, QNetworkAccessManager *networkManager):
66 QWebView(parent), m_scrollParent(0L), m_resizeInProgress(0), m_staticWidth(0)
68 // set to expanding, ie. "freely" - this is important so the widget will attempt to shrink below the sizehint!
69 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
70 setFocusPolicy(Qt::StrongFocus); // not by the wheel
71 setPage(new ErrorCheckingPage(this));
72 page()->setNetworkAccessManager(networkManager);
74 QWebSettings *s = settings();
75 s->setAttribute(QWebSettings::JavascriptEnabled, false);
76 s->setAttribute(QWebSettings::JavaEnabled, false);
77 s->setAttribute(QWebSettings::PluginsEnabled, false);
78 s->setAttribute(QWebSettings::PrivateBrowsingEnabled, true);
79 s->setAttribute(QWebSettings::JavaEnabled, false);
80 s->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, false);
81 s->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, false);
82 s->setAttribute(QWebSettings::LocalStorageDatabaseEnabled, false);
83 s->clearMemoryCaches();
85 page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
86 connect(this, &QWebView::linkClicked, this, &EmbeddedWebView::slotLinkClicked);
87 connect(this, &QWebView::loadFinished, this, &EmbeddedWebView::handlePageLoadFinished);
88 connect(page()->mainFrame(), &QWebFrame::contentsSizeChanged, this, &EmbeddedWebView::handlePageLoadFinished);
90 // Scrolling is implemented on upper layers
91 page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
92 page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
94 // Setup shortcuts for standard actions
95 QAction *copyAction = page()->action(QWebPage::Copy);
96 copyAction->setShortcut(tr("Ctrl+C"));
97 addAction(copyAction);
99 // Redmine#3, the QWebView uses black text color when rendering stuff on dark background
100 QPalette palette = QApplication::palette();
101 if (palette.background().color().lightness() < 50) {
102 QStyle *style = QStyleFactory::create(QLatin1String("windows"));
103 Q_ASSERT(style);
104 palette = style->standardPalette();
105 setPalette(palette);
108 m_autoScrollTimer = new QTimer(this);
109 m_autoScrollTimer->setInterval(50);
110 connect(m_autoScrollTimer, &QTimer::timeout, this, &EmbeddedWebView::autoScroll);
112 m_sizeContrainTimer = new QTimer(this);
113 m_sizeContrainTimer->setInterval(50);
114 m_sizeContrainTimer->setSingleShot(true);
115 connect(m_sizeContrainTimer, &QTimer::timeout, this, &EmbeddedWebView::constrainSize);
117 setContextMenuPolicy(Qt::NoContextMenu);
118 findScrollParent();
121 void EmbeddedWebView::constrainSize()
123 Incrementor dummy(&m_resizeInProgress);
125 if (!(m_scrollParent && page() && page()->mainFrame()))
126 return; // should not happen but who knows
128 // Prevent expensive operation where a resize triggers one extra resizing operation.
129 // This is very visible on large attachments, and in fact could possibly lead to recursion as the
130 // contentsSizeChanged signal is connected to handlePageLoadFinished.
131 if (m_resizeInProgress > 1)
132 return;
134 // the m_scrollParentPadding measures the summed up horizontal paddings of this view compared to
135 // its m_scrollParent
136 setMinimumSize(0,0);
137 setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
138 if (m_staticWidth) {
139 resize(m_staticWidth, QWIDGETSIZE_MAX - 1);
140 page()->setViewportSize(QSize(m_staticWidth, 32));
141 } else {
142 // resize so that the viewport has much vertical and wanted horizontal space
143 resize(m_scrollParent->width() - m_scrollParentPadding, QWIDGETSIZE_MAX);
144 // resize the PAGES viewport to this width and a minimum height
145 page()->setViewportSize(QSize(m_scrollParent->width() - m_scrollParentPadding, 32));
147 // now the page has an idea about it's demanded size
148 const QSize bestSize = page()->mainFrame()->contentsSize();
149 // set the viewport to that size! - Otherwise it'd still be our "suggestion"
150 page()->setViewportSize(bestSize);
151 // fix the widgets size so the layout doesn't have much choice
152 setFixedSize(bestSize);
153 m_sizeContrainTimer->stop(); // we caused spurious resize events
156 void EmbeddedWebView::slotLinkClicked(const QUrl &url)
158 // Only allow external http:// and https:// links for safety reasons
159 if (url.scheme().toLower() == QLatin1String("http") || url.scheme().toLower() == QLatin1String("https")) {
160 QDesktopServices::openUrl(url);
161 } else if (url.scheme().toLower() == QLatin1String("mailto")) {
162 // The mailto: scheme is registered by Gui::MainWindow and handled internally;
163 // even if it wasn't, opening a third-party application in response to a
164 // user-initiated click does not pose a security risk
165 QUrl betterUrl(url);
166 betterUrl.setScheme(url.scheme().toLower());
167 QDesktopServices::openUrl(betterUrl);
171 void EmbeddedWebView::handlePageLoadFinished()
173 constrainSize();
175 // We've already set in in our constructor, but apparently it isn't enough (Qt 4.8.0 on X11).
176 // Let's do it again here, it works.
177 Qt::ScrollBarPolicy policy = isWindow() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff;
178 page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, policy);
179 page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, policy);
180 page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
183 void EmbeddedWebView::changeEvent(QEvent *e)
185 QWebView::changeEvent(e);
186 if (e->type() == QEvent::ParentChange)
187 findScrollParent();
190 bool EmbeddedWebView::eventFilter(QObject *o, QEvent *e)
192 if (o == m_scrollParent) {
193 if (e->type() == QEvent::Resize) {
194 if (!m_staticWidth)
195 m_sizeContrainTimer->start();
196 } else if (e->type() == QEvent::Enter) {
197 m_autoScrollPixels = 0;
198 m_autoScrollTimer->stop();
201 return QWebView::eventFilter(o, e);
204 void EmbeddedWebView::autoScroll()
206 if (!(m_scrollParent && m_autoScrollPixels)) {
207 m_autoScrollPixels = 0;
208 m_autoScrollTimer->stop();
209 return;
211 if (QScrollBar *bar = static_cast<QAbstractScrollArea*>(m_scrollParent)->verticalScrollBar()) {
212 bar->setValue(bar->value() + m_autoScrollPixels);
216 void EmbeddedWebView::mouseMoveEvent(QMouseEvent *e)
218 if ((e->buttons() & Qt::LeftButton) && m_scrollParent) {
219 m_autoScrollPixels = 0;
220 const QPoint pos = mapTo(m_scrollParent, e->pos());
221 if (pos.y() < 0)
222 m_autoScrollPixels = pos.y();
223 else if (pos.y() > m_scrollParent->rect().height())
224 m_autoScrollPixels = pos.y() - m_scrollParent->rect().height();
225 autoScroll();
226 m_autoScrollTimer->start();
228 QWebView::mouseMoveEvent(e);
231 void EmbeddedWebView::mouseReleaseEvent(QMouseEvent *e)
233 if (!(e->buttons() & Qt::LeftButton)) {
234 m_autoScrollPixels = 0;
235 m_autoScrollTimer->stop();
237 QWebView::mouseReleaseEvent(e);
240 void EmbeddedWebView::findScrollParent()
242 if (m_scrollParent)
243 m_scrollParent->removeEventFilter(this);
244 m_scrollParent = 0;
245 const int frameWidth = 2*style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
246 m_scrollParentPadding = frameWidth;
247 QWidget *runner = this;
248 int left, top, right, bottom;
249 while (runner) {
250 runner->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
251 runner->getContentsMargins(&left, &top, &right, &bottom);
252 m_scrollParentPadding += left + right + frameWidth;
253 if (runner->layout()) {
254 runner->layout()->getContentsMargins(&left, &top, &right, &bottom);
255 m_scrollParentPadding += left + right;
257 QWidget *p = runner->parentWidget();
258 if (p && qobject_cast<MessageView*>(runner) && // is this a MessageView?
259 p->objectName() == QLatin1String("qt_scrollarea_viewport") && // in a viewport?
260 qobject_cast<QAbstractScrollArea*>(p->parentWidget())) { // that is used?
261 p->getContentsMargins(&left, &top, &right, &bottom);
262 m_scrollParentPadding += left + right + frameWidth;
263 if (p->layout()) {
264 p->layout()->getContentsMargins(&left, &top, &right, &bottom);
265 m_scrollParentPadding += left + right;
267 m_scrollParent = p->parentWidget();
268 break; // then we have our actual message view
270 runner = p;
272 m_scrollParentPadding += style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, m_scrollParent);
273 if (m_scrollParent)
274 m_scrollParent->installEventFilter(this);
277 void EmbeddedWebView::showEvent(QShowEvent *se)
279 QWebView::showEvent(se);
280 Qt::ScrollBarPolicy policy = isWindow() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff;
281 page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded);
282 page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, policy);
283 if (isWindow()) {
284 resize(640,480);
285 } else if (!m_scrollParent) // it would be much easier if the parents were just passed with the constructor ;-)
286 findScrollParent();
289 QSize EmbeddedWebView::sizeHint() const
291 return QSize(32,32); // QWebView returns 800x600 what will lead to too wide pages for our implementation
294 QWidget *EmbeddedWebView::scrollParent() const
296 return m_scrollParent;
299 void EmbeddedWebView::setStaticWidth(int staticWidth)
301 m_staticWidth = staticWidth;
304 int EmbeddedWebView::staticWidth() const
306 return m_staticWidth;
309 ErrorCheckingPage::ErrorCheckingPage(QObject *parent): QWebPage(parent)
313 bool ErrorCheckingPage::supportsExtension(Extension extension) const
315 if (extension == ErrorPageExtension)
316 return true;
317 else
318 return false;
321 bool ErrorCheckingPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
323 if (extension != ErrorPageExtension)
324 return false;
326 const ErrorPageExtensionOption *input = static_cast<const ErrorPageExtensionOption *>(option);
327 ErrorPageExtensionReturn *res = static_cast<ErrorPageExtensionReturn *>(output);
328 if (input && res) {
329 if (input->url.scheme() == QLatin1String("trojita-imap")) {
330 if (input->domain == QtNetwork && input->error == QNetworkReply::TimeoutError) {
331 res->content = tr("<img src='%2'/><span style='font-family: sans-serif; color: gray'>"
332 "Uncached data not available when offline</span>")
333 .arg(Util::resizedImageAsDataUrl(QLatin1String(":/icons/network-offline.png"), 32)).toUtf8();
334 return true;
337 res->content = input->errorString.toUtf8();
338 res->contentType = QLatin1String("text/plain");
340 return true;