1 /************************************************************************
2 *
3 * Copyright 2011-2012 Jakob Leben (jakob.leben@gmail.com)
4 *
5 * This file is part of SuperCollider Qt GUI.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 ************************************************************************/
21
22 #ifdef SC_USE_QTWEBENGINE
23
24 # include "QcWebView.h"
25 # include "../widgets/web_page.hpp"
26 # include "../QcWidgetFactory.h"
27 # include <QWebEnginePage>
28 # include <QWebEngineSettings>
29 # include <QWebEngineContextMenuData>
30 # include <QAction>
31 # include <QMenu>
32 # include <QShortcut>
33 # include <QKeyEvent>
34 # include <QApplication>
35 # include <QStyle>
36 # include <QWebEngineCallback>
37
38 QC_DECLARE_QWIDGET_FACTORY(WebView);
39
40 namespace QtCollider {
41
WebView(QWidget * parent)42 WebView::WebView(QWidget* parent): QWebEngineView(parent), _interpretSelection(false), _editable(false) {
43 QtCollider::WebPage* page = new WebPage(this);
44 page->setDelegateReload(true);
45 setPage(page);
46 connectPage(page);
47
48 // Set the style's standard palette to avoid system's palette incoherencies
49 // get in the way of rendering web pages
50 setPalette(style()->standardPalette());
51
52 setAttribute(Qt::WA_AcceptTouchEvents);
53
54 page->action(QWebEnginePage::Copy)->setShortcut(QKeySequence::Copy);
55 page->action(QWebEnginePage::Paste)->setShortcut(QKeySequence::Paste);
56 page->action(QWebEnginePage::Reload)->setShortcut(QKeySequence::Refresh);
57
58 connect(this, SIGNAL(interpret(QString)), qApp, SLOT(interpret(QString)), Qt::QueuedConnection);
59
60 connect(this, SIGNAL(loadFinished(bool)), this, SLOT(updateEditable(bool)));
61 }
62
connectPage(QtCollider::WebPage * page)63 void WebView::connectPage(QtCollider::WebPage* page) {
64 connect(page, SIGNAL(jsConsoleMsg(const QString&, int, const QString&)), this,
65 SIGNAL(jsConsoleMsg(const QString&, int, const QString&)));
66
67 connect(page, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&)));
68
69 connect(page, SIGNAL(geometryChangeRequested(const QRect&)), this, SIGNAL(geometryChangeRequested(const QRect&)));
70
71 connect(page, SIGNAL(windowCloseRequested()), this, SIGNAL(windowCloseRequested()));
72
73 connect(page, SIGNAL(scrollPositionChanged(const QPointF&)), this, SIGNAL(scrollPositionChanged(const QPointF&)));
74
75 connect(page, SIGNAL(contentsSizeChanged(const QSizeF&)), this, SIGNAL(contentsSizeChanged(const QSizeF&)));
76
77 connect(page, SIGNAL(audioMutedChanged(bool)), this, SIGNAL(audioMutedChanged(bool)));
78
79 connect(page, SIGNAL(recentlyAudibleChanged(bool)), this, SIGNAL(recentlyAudibleChanged(bool)));
80
81 connect(page, SIGNAL(navigationRequested(QUrl, QWebEnginePage::NavigationType, bool)), this,
82 SLOT(onLinkClicked(QUrl, QWebEnginePage::NavigationType, bool)));
83
84 connect(page->action(QWebEnginePage::Reload), SIGNAL(triggered(bool)), this, SLOT(onPageReload()));
85
86 connect(page, &WebPage::renderProcessTerminated, this, &WebView::onRenderProcessTerminated);
87 }
88
onRenderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus status,int code)89 void WebView::onRenderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus status, int code) {
90 Q_EMIT(renderProcessTerminated((int)status, code));
91 }
92
triggerPageAction(int action,bool checked)93 void WebView::triggerPageAction(int action, bool checked) {
94 QWebEngineView::triggerPageAction((QWebEnginePage::WebAction)action, checked);
95 }
96
url() const97 QString WebView::url() const { return QWebEngineView::url().toString(); }
98
setUrl(const QString & str)99 void WebView::setUrl(const QString& str) { load(urlFromString(str)); }
100
delegateReload() const101 bool WebView::delegateReload() const {
102 WebPage* p = qobject_cast<QtCollider::WebPage*>(page());
103 Q_ASSERT(p);
104 return p->delegateReload();
105 }
106
setDelegateReload(bool flag)107 void WebView::setDelegateReload(bool flag) {
108 WebPage* p = qobject_cast<QtCollider::WebPage*>(page());
109 Q_ASSERT(p);
110 p->setDelegateReload(flag);
111 }
112
setFontFamily(int generic,const QString & specific)113 void WebView::setFontFamily(int generic, const QString& specific) {
114 settings()->setFontFamily((QWebEngineSettings::FontFamily)generic, specific);
115 }
116
pageAction(QWebEnginePage::WebAction action) const117 QAction* WebView::pageAction(QWebEnginePage::WebAction action) const { return QWebEngineView::pageAction(action); }
118
setHtml(const QString & html,const QString & baseUrl)119 void WebView::setHtml(const QString& html, const QString& baseUrl) {
120 if (page()) {
121 page()->setHtml(html, baseUrl);
122 }
123 }
124
setContent(const QVector<int> & data,const QString & mimeType,const QString & baseUrl)125 void WebView::setContent(const QVector<int>& data, const QString& mimeType, const QString& baseUrl) {
126 if (page()) {
127 QByteArray byteData;
128 for (int val : data) {
129 byteData.push_back((char)val);
130 }
131 page()->setContent(byteData, mimeType, baseUrl);
132 }
133 }
134
toHtml(QcCallback * cb) const135 void WebView::toHtml(QcCallback* cb) const {
136 if (page()) {
137 if (cb) {
138 page()->toHtml(cb->asFunctor());
139 } else {
140 page()->toHtml([](const QString&) {});
141 }
142 } else {
143 cb->asFunctor()(QString());
144 }
145 }
146
toPlainText(QcCallback * cb) const147 void WebView::toPlainText(QcCallback* cb) const {
148 if (page()) {
149 if (cb) {
150 page()->toPlainText(cb->asFunctor());
151 } else {
152 page()->toPlainText([](const QString&) {});
153 }
154 } else {
155 cb->asFunctor()(QString());
156 }
157 }
158
runJavaScript(const QString & script,QcCallback * cb)159 void WebView::runJavaScript(const QString& script, QcCallback* cb) {
160 if (page()) {
161 if (cb) {
162 page()->runJavaScript(script, cb->asFunctor());
163 } else {
164 page()->runJavaScript(script, [](const QVariant&) {});
165 }
166 } else {
167 cb->asFunctor()(QString());
168 }
169 }
170
setWebAttribute(int attr,bool on)171 void WebView::setWebAttribute(int attr, bool on) {
172 if (page()) {
173 page()->settings()->setAttribute((QWebEngineSettings::WebAttribute)attr, on);
174 }
175 }
176
testWebAttribute(int attr)177 bool WebView::testWebAttribute(int attr) {
178 return page() ? page()->settings()->testAttribute((QWebEngineSettings::WebAttribute)attr) : false;
179 }
180
resetWebAttribute(int attr)181 void WebView::resetWebAttribute(int attr) {
182 if (page()) {
183 page()->settings()->resetAttribute((QWebEngineSettings::WebAttribute)attr);
184 }
185 }
186
navigate(const QString & urlString)187 void WebView::navigate(const QString& urlString) {
188 QUrl url(urlString);
189 this->load(url);
190 }
191
findText(const QString & searchText,bool reversed,QcCallback * cb)192 void WebView::findText(const QString& searchText, bool reversed, QcCallback* cb) {
193 QWebEnginePage::FindFlags flags;
194 if (reversed)
195 flags |= QWebEnginePage::FindBackward;
196
197 if (!cb) {
198 QWebEngineView::findText(searchText, flags);
199 } else {
200 QWebEngineView::findText(searchText, flags, cb->asFunctor());
201 }
202 }
203
onPageReload()204 void WebView::onPageReload() { Q_EMIT(reloadTriggered(url())); }
205
contextMenuEvent(QContextMenuEvent * event)206 void WebView::contextMenuEvent(QContextMenuEvent* event) {
207 QMenu menu;
208
209 const QWebEngineContextMenuData& contextData = page()->contextMenuData();
210
211 if (!contextData.linkUrl().isEmpty()) {
212 menu.addAction(pageAction(QWebEnginePage::CopyLinkToClipboard));
213 menu.addSeparator();
214 }
215
216 if (contextData.isContentEditable() || !contextData.selectedText().isEmpty()) {
217 menu.addAction(pageAction(QWebEnginePage::Copy));
218 if (contextData.isContentEditable()) {
219 menu.addAction(pageAction(QWebEnginePage::Paste));
220 }
221 menu.addSeparator();
222 }
223
224 menu.addAction(pageAction(QWebEnginePage::Back));
225 menu.addAction(pageAction(QWebEnginePage::Forward));
226 menu.addAction(pageAction(QWebEnginePage::Reload));
227
228 menu.exec(event->globalPos());
229 }
230
keyPressEvent(QKeyEvent * e)231 void WebView::keyPressEvent(QKeyEvent* e) {
232 int key = e->key();
233 int mods = e->modifiers();
234
235 if (_interpretSelection
236 && (key == Qt::Key_Enter || (key == Qt::Key_Return && mods & (Qt::ControlModifier | Qt::ShiftModifier)))) {
237 QString selection = selectedText();
238 if (!selection.isEmpty()) {
239 Q_EMIT(interpret(selection));
240 return;
241 }
242 }
243
244 QWebEngineView::keyPressEvent(e);
245 }
246
updateEditable(bool ok)247 void WebView::updateEditable(bool ok) {
248 if (ok) {
249 if (_editable) {
250 page()->runJavaScript("document.documentElement.contentEditable = true");
251 } else {
252 page()->runJavaScript("document.documentElement.contentEditable = false");
253 }
254 }
255 }
256
overrideNavigation() const257 bool WebView::overrideNavigation() const {
258 WebPage* p = qobject_cast<WebPage*>(page());
259 return p ? p->delegateNavigation() : false;
260 }
261
setOverrideNavigation(bool b)262 void WebView::setOverrideNavigation(bool b) {
263 WebPage* p = qobject_cast<WebPage*>(page());
264 if (p) {
265 p->setDelegateNavigation(b);
266 }
267 }
268
onLinkClicked(const QUrl & url,QWebEnginePage::NavigationType type,bool isMainFrame)269 void WebView::onLinkClicked(const QUrl& url, QWebEnginePage::NavigationType type, bool isMainFrame) {
270 Q_EMIT(navigationRequested(url, (int)type, isMainFrame));
271 }
272
273 } // namespace QtCollider
274
275 #endif // SC_USE_QTWEBENGINE
276