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"
26 #include <QAbstractScrollArea>
28 #include <QApplication>
29 #include <QDesktopServices>
30 #include <QDesktopWidget>
32 #include <QMouseEvent>
33 #include <QNetworkReply>
36 #include <QStyleFactory>
39 #include <QWebHistory>
45 /** @short RAII pattern for counter manipulation */
49 Incrementor(int *what
): m_int(what
)
56 Q_ASSERT(*m_int
>= 0);
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"));
104 palette
= style
->standardPalette();
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
);
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)
134 // the m_scrollParentPadding measures the summed up horizontal paddings of this view compared to
135 // its m_scrollParent
137 setMaximumSize(QWIDGETSIZE_MAX
, QWIDGETSIZE_MAX
);
139 resize(m_staticWidth
, QWIDGETSIZE_MAX
- 1);
140 page()->setViewportSize(QSize(m_staticWidth
, 32));
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
166 betterUrl
.setScheme(url
.scheme().toLower());
167 QDesktopServices::openUrl(betterUrl
);
171 void EmbeddedWebView::handlePageLoadFinished()
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
)
190 bool EmbeddedWebView::eventFilter(QObject
*o
, QEvent
*e
)
192 if (o
== m_scrollParent
) {
193 if (e
->type() == QEvent::Resize
) {
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();
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());
222 m_autoScrollPixels
= pos
.y();
223 else if (pos
.y() > m_scrollParent
->rect().height())
224 m_autoScrollPixels
= pos
.y() - m_scrollParent
->rect().height();
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()
243 m_scrollParent
->removeEventFilter(this);
245 const int frameWidth
= 2*style()->pixelMetric(QStyle::PM_DefaultFrameWidth
);
246 m_scrollParentPadding
= frameWidth
;
247 QWidget
*runner
= this;
248 int left
, top
, right
, bottom
;
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
;
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
272 m_scrollParentPadding
+= style()->pixelMetric(QStyle::PM_ScrollBarExtent
, 0, 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
);
285 } else if (!m_scrollParent
) // it would be much easier if the parents were just passed with the constructor ;-)
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
)
321 bool ErrorCheckingPage::extension(Extension extension
, const ExtensionOption
*option
, ExtensionReturn
*output
)
323 if (extension
!= ErrorPageExtension
)
326 const ErrorPageExtensionOption
*input
= static_cast<const ErrorPageExtensionOption
*>(option
);
327 ErrorPageExtensionReturn
*res
= static_cast<ErrorPageExtensionReturn
*>(output
);
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();
337 res
->content
= input
->errorString
.toUtf8();
338 res
->contentType
= QLatin1String("text/plain");