1 #if defined(QCS_QTHTML)
2 #include "basedocument.h"
3 #include "documentpage.h"
4 #include "csoundhtmlview.h"
5 #include "ui_html5guidisplay.h"
6 #include <QLabel>
7 #include <QVBoxLayout>
8 #include <QWaitCondition>
9 #include <QFile>
10 
CsoundHtmlView(QWidget * parent)11 CsoundHtmlView::CsoundHtmlView(QWidget *parent) :
12     QDockWidget(parent),
13     webView(nullptr),
14     ui(new Ui::Html5GuiDisplay),
15     documentPage(0),
16     m_csoundEngine(nullptr),
17 	m_options(nullptr)
18 {
19     ui->setupUi(this);
20 
21 #ifdef USE_WEBKIT
22 	webView = new QWebView(this);
23 	ui->inspectRow->hide(); // inspector included in QtWebKit, no need for that
24 #else
25 	webView = new QWebEngineView(this);
26     //webView->page()->profile()->clearHttpCache();
27 #endif
28     csoundHtmlWrapper.setCsoundHtmlView(this);
29     csoundHtmlOnlyWrapper.setCsoundHtmlView(this);
30 	ui->mainLayout->addWidget(webView); // mainLayout is vertical layout box
31     webView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
32 
33 #ifdef USE_WEBKIT
34 	QObject::connect(webView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()),
35 						this, SLOT(addJSObject()));  // to enable adding the object after reload
36 	// add javascript inspector -  open with right click on htmlview
37 	webView->page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
38 	QWebInspector inspector;
39 	inspector.setPage(webView->page());
40 	inspector.setVisible(true);
41 #else
42     // Enable dev tools by default for the test browser
43 	connect(ui->inspectButton, SIGNAL(clicked()),this, SLOT(showDebugWindow()));
44     webView->page()->setWebChannel(&channel);
45     //qDebug() << "Setting JavaScript object on init.";
46     channel.registerObject("csound", &csoundHtmlWrapper);
47 #endif
48 }
49 
~CsoundHtmlView()50 CsoundHtmlView::~CsoundHtmlView()
51 {
52     delete ui;
53 }
54 
getElement(const QString & text,const QString & tag)55 QString getElement(const QString &text, const QString &tag)
56 {
57     QString::SectionFlags sectionflags = QString::SectionIncludeLeadingSep | QString::SectionIncludeTrailingSep | QString::SectionCaseInsensitiveSeps;
58     QString element = text.section("<" + tag, 1, 1, sectionflags);
59     element = element.section("</" + tag + ">", 0, 0, sectionflags);
60     return element;
61 }
62 
load(DocumentPage * documentPage_)63 void CsoundHtmlView::load(DocumentPage *documentPage_)
64 {
65     //TODO: call this whenever document is saved, not only on run. Usually always saved when run but there is also option not to save... Think.
66     documentPage = documentPage_; // consider rewrite...
67     qDebug() ;
68     auto text = documentPage.load()->getFullText();
69     auto filename = documentPage.load()->getFileName();
70     QFile csdfile(filename);
71     csdfile.open(QIODevice::WriteOnly);
72     QTextStream out(&csdfile);
73     out << text;
74     csdfile.close();
75     auto html = getElement(text, "html");
76     if (html.size() > 0) {
77 #ifdef USE_WEBENGINE
78         // Inject necessary code to load qtwebchannel/qwebchannel.js.
79         QString injection = R"(
80 <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
81 <script type="text/javascript">
82 "use strict";
83 document.addEventListener("DOMContentLoaded", function () {
84     try {
85         console.log("Initializing window.csound...");
86         window.channel = new QWebChannel(qt.webChannelTransport, function(channel) {
87         window.csound = channel.objects.csound;
88         if (typeof window.csound === 'undefined') {
89             alert('window.csound is undefined.');
90             return;
91         }
92         if (window.csound === null) {
93             alert('window.csound is null.');
94             return;
95         }
96         csound.message("Initialized csound.\n");
97         });
98     } catch (e) {
99         alert("initialize_csound error: " + e.message);
100         console.log(e.message);
101     }
102 });
103 </script>
104 )";
105         // Tricky because now HTML doesn't have to have a <head> element,
106         // and both <html> and <head> can have attributes. So we need to find an
107         // injection point that is the very first place allowed to put a <script>
108         // element.
109         int injection_index = html.indexOf("<head", 0, Qt::CaseInsensitive);
110         if (injection_index != -1) {
111             injection_index = html.indexOf(">", injection_index) + 1;
112         } else {
113             injection_index = html.indexOf("<html", 0, Qt::CaseInsensitive);
114             injection_index = html.indexOf(">", injection_index) + 1;
115         }
116         html = html.insert(injection_index, injection);
117 #endif
118         QString htmlfilename;
119         if (filename.startsWith(":/") ) { // an example file
120             htmlfilename = QDir::tempPath()+"/html-example.html"; // TODO: take name from filename
121         } else {
122             htmlfilename = filename + ".html";
123         }
124         QFile htmlfile(htmlfilename);
125         htmlfile.open(QIODevice::WriteOnly);
126         QTextStream out(&htmlfile);
127         out << html;
128         htmlfile.close();
129         loadFromUrl(QUrl::fromLocalFile(htmlfilename));
130         // kas aitab, kui on siin:
131 #ifdef USE_WEBENGINE
132         webView->page()->setWebChannel(&channel);
133         if (filename.endsWith(".html", Qt::CaseInsensitive)) {
134             // Register CsoundHtmlOnlyWrapper when performing HTML files.
135             qDebug()  << "Setting CsoundHtmlOnlyWrapper JavaScript object on load.";
136             channel.registerObject("csound", &csoundHtmlOnlyWrapper);
137             csoundHtmlOnlyWrapper.registerConsole(documentPage_->getConsole());
138             csoundHtmlOnlyWrapper.setOptions(m_options);
139         } else {
140             // Register CsoundHtmlWrapper when performing CSD files with embedded <html> element.
141             qDebug()  << "Setting CsoundWrapper JavaScript object on load.";
142             channel.registerObject("csound", &csoundHtmlWrapper);
143         }
144 #endif
145     }
146     repaint();
147 }
148 
149 void CsoundHtmlView::setCsoundEngine(CsoundEngine *csEngine)
150 {
151     csoundHtmlWrapper.setCsoundEngine(csEngine);
152 }
153 
154 
155 void CsoundHtmlView::stop()
156 {
157     qDebug() ;
158     documentPage = 0;
159     csoundHtmlOnlyWrapper.stop();
160 }
161 
162 void CsoundHtmlView::viewHtml(QString htmlText)
163 {
164     qDebug();
165     tempHtml.setFileTemplate( QDir::tempPath()+"/csoundqt-html-XXXXXX.html" ); // must have html ending for webkit
166     if (tempHtml.open()) {
167 #ifdef USE_WEBENGINE
168         // Inject necessary code to load qtwebchannel/qwebchannel.js.
169         QString injection = R"(
170 <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
171 <script>
172 "use strict";
173 document.addEventListener("DOMContentLoaded", function () {//void CsoundHtmlView::closeEvent(QCloseEvent *event)
174                             //{
175                             //    qDebug() ;
176                             //    if (webView) {
177                             //		webView->close(); // is it necessary?
178                             //    }
179                             //}
180 
181 
182 
183 
184     try {
185         console.log("Initializing Csound...");
186         window.channel = new QWebChannel(qt.webChannelTransport, function(channel) {
187         window.csound = channel.objects.csound;
188         csound.message("Initialized csound.\n");
189         });
190     } catch (e) {
191         alert("initialize_csound error: " + e.message);
192         console.log(e.message);
193     }
194 });
195 </script>
196 )";
197         // Tricky because now HTML doesn't have to have a <head> element,
198         // and both <html> and <head> can have attributes. So we need to find an
199         // injection point that is the very first place allowed to put a <script>
200         // element.
201         int injection_index = htmlText.indexOf("<head", 0, Qt::CaseInsensitive);
202         if (injection_index != -1) {
203             injection_index = htmlText.indexOf(">", injection_index) + 1;
204         } else {
205             injection_index = htmlText.indexOf("<html", 0, Qt::CaseInsensitive);
206             injection_index = htmlText.indexOf(">", injection_index) + 1;
207         }
208         htmlText = htmlText.insert(injection_index, injection);
209 #endif
210 		tempHtml.write(htmlText.toLocal8Bit());
211 		tempHtml.resize(tempHtml.pos()); // otherwise may keep contents from previous write if that was bigger
212 		tempHtml.close();
213         loadFromUrl(QUrl::fromLocalFile(tempHtml.fileName()));
214 #ifdef USE_WEBENGINE
215         webView->page()->setWebChannel(&channel);
216         auto filename = documentPage.load()->getFileName();
217         if (filename.endsWith(".html", Qt::CaseInsensitive)) {
218             // Register CsoundHtmlOnlyWrapper when performing HTML files.
219             qDebug()  << "Setting CsoundHtmlOnlyWrapper JavaScript object on view.";
220             channel.registerObject("csound", &csoundHtmlOnlyWrapper);
221         } else {
222             // Register CsoundHtmlWrapper when performing CSD files with embedded <html> element.
223             qDebug()  << "Setting CsoundWrapper JavaScript object on view.";
224             channel.registerObject("csound", &csoundHtmlWrapper);
225         }
226 #endif
227 	}
228 }
229 
230 #ifdef USE_WEBKIT
231 void CsoundHtmlView::addJSObject()
232 {
233 	if (webView) {
234         QString filename = documentPage.load()->getFileName();
235         qDebug()<<"Adding Csound as JavaScript object";
236         if (filename.endsWith(".html", Qt::CaseInsensitive)) {
237             // Register CsoundHtmlOnlyWrapper when performing HTML files.
238             webView->page()->mainFrame()->addToJavaScriptWindowObject("csound", &csoundHtmlOnlyWrapper);
239             csoundHtmlOnlyWrapper.registerConsole(documentPage.load()->getConsole());
240             csoundHtmlOnlyWrapper.setOptions(m_options);
241         } else {
242             // Register CsoundHtmlWrapper when performing CSD files with embedded <html> element.
243 			webView->page()->mainFrame()->addToJavaScriptWindowObject("csound", &csoundHtmlWrapper);
244         }
245 
246 
247 	}
248 
249 
250 
251 }
252 #endif
253 
254 void CsoundHtmlView::loadFromUrl(const QUrl &url)
255 {
256     qDebug();
257 
258     if(webView != 0) {
259         webView->setUrl(url);
260     }
261     if (!this->isVisible()) {
262         this->show();
263         this->raise();
264     }
265 }
266 
267 void CsoundHtmlView::clear()
268 {
269     this->hide(); // just hide the panel, keep qwebchannel and other connections
270     //loadFromUrl(QUrl()); // empty URL to clear -  this causes "qt is not defined" error
271 }
272 
273 void CsoundHtmlView::setOptions(CsoundOptions *options)
274 {
275 	m_options = options;
276 }
277 
278 #ifdef USE_WEBENGINE
279 void CsoundHtmlView::showDebugWindow()
280 {
281 	qDebug();
282 	QByteArray debugPort = qgetenv("QTWEBENGINE_REMOTE_DEBUGGING");
283 	if (!debugPort.isNull()) {
284 		QWidget * debugger = new QWidget();
285 		debugger->resize(600,400);
286 		QWebEngineView * debuggerView= new QWebEngineView(debugger);
287         QVBoxLayout *layout = new QVBoxLayout(debugger);
288         layout->addWidget(debuggerView);
289         debugger->setAttribute(Qt::WA_DeleteOnClose);
290         debugger->setLayout(layout);
291 		qDebug()<<"Opening window for localhost:"<<debugPort;
292 		debuggerView->setUrl(QUrl("http://localhost:"+debugPort));
293 		debugger->show();
294 	} else {
295 		qDebug()<<"Debugging port not set or reading failed";
296 	}
297 }
298 
299 #endif
300 #endif
301