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