summaryrefslogtreecommitdiffstats
path: root/examples/webenginewidgets
diff options
context:
space:
mode:
authorKai Koehne <kai.koehne@theqtcompany.com>2015-11-06 13:53:42 +0100
committerKai Koehne <kai.koehne@theqtcompany.com>2015-11-17 14:23:48 +0000
commita6ae4526a390e7f53fbdb66bcb1d2c62816e3f95 (patch)
treedf5d981732070d0adeb19d9e989cf0ea5eb3ab8c /examples/webenginewidgets
parentaf859695ec56390951204ce72eb6a888b1f9d0c8 (diff)
Add 'markdowneditor' example
This example shows the use of QWebEngineView in a hybrid application, and how one can leverage JavaScript libraries to provide functionality with minimal effort. QWebEngineView is used to preview a MarkDown document. The text is exposed to the view through QWebChannel. An off-the-self js library converts it to HTML. Change-Id: I24c38106da3ec18975c71c16f7f7a58e93142f9e Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Diffstat (limited to 'examples/webenginewidgets')
-rw-r--r--examples/webenginewidgets/markdowneditor/3RDPARTY.md23
-rw-r--r--examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.pngbin0 -> 41883 bytes
-rw-r--r--examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc164
-rw-r--r--examples/webenginewidgets/markdowneditor/document.cpp50
-rw-r--r--examples/webenginewidgets/markdowneditor/document.h64
-rw-r--r--examples/webenginewidgets/markdowneditor/main.cpp56
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.cpp172
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.h77
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.ui115
-rw-r--r--examples/webenginewidgets/markdowneditor/markdowneditor.pro28
-rw-r--r--examples/webenginewidgets/markdowneditor/previewpage.cpp55
-rw-r--r--examples/webenginewidgets/markdowneditor/previewpage.h57
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/default.md12
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/index.html32
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/markdown.css260
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc9
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/marked.min.js6
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/qwebchannel.js413
18 files changed, 1593 insertions, 0 deletions
diff --git a/examples/webenginewidgets/markdowneditor/3RDPARTY.md b/examples/webenginewidgets/markdowneditor/3RDPARTY.md
new file mode 100644
index 000000000..9e91ab302
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/3RDPARTY.md
@@ -0,0 +1,23 @@
+## markd license
+
+```
+Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
diff --git a/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png b/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png
new file mode 100644
index 000000000..9f456c4db
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png
Binary files differ
diff --git a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc b/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc
new file mode 100644
index 000000000..44b1246b3
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \example webenginewidgets/markdowneditor
+ \title Markdown Editor Example
+ \ingroup webengine-widgetexamples
+ \brief Demonstrates how to integrate a web engine in a hybrid desktop
+ application.
+
+ \image markdowneditor-example.png
+
+ \e {Markdown Editor} demonstrates how to use QWebChannel and JavaScript
+ libraries to provide a rich text preview tool for a custom markup language.
+
+ \l{http://daringfireball.net/projects/markdown/}{Markdown} is a lightweight
+ markup language with a plain text formatting syntax.
+ Some services, such as \l{http://github.com}{github}, acknowledge the
+ format, and render the content as rich text when viewed in a browser.
+
+ The Markdown Editor main window is split into an editor and a preview area.
+ The editor supports the Markdown syntax and is implemented by using
+ QPlainTextEdit. The document is rendered as rich text in the preview area,
+ which is implemented by using QWebEngineView. To render the text, the
+ Markdown text is converted to HTML format with the help of a JavaScript
+ library inside the web engine. The preview is updated from the editor
+ through QWebChannel.
+
+ \include examples-run.qdocinc
+
+ \section1 Exposing Document Text
+
+ Because we expose the current Markdown text to be rendered to the web engine
+ through QWebChannel, we need to somehow make the current text available
+ through the Qt metatype system. This is done by using a dedicated
+ \c Document class that exposes the document text as a \c{Q_PROPERTY}:
+
+ \quotefromfile webenginewidgets/markdowneditor/document.h
+ \skipto class Document
+ \printto #endif
+
+ The \c Document class wraps a QString to be set on the C++ side with
+ the \c setText() method and exposes it at runtime as a \c text property
+ with a \c textChanged signal.
+
+ We define the \c setText method as follows:
+
+ \quotefromfile webenginewidgets/markdowneditor/document.cpp
+ \skipto Document::setText
+ \printuntil
+
+ \section1 Previewing Text
+
+ We implement our own \c PreviewPage class that publicly inherits from
+ \c QWebEnginePage:
+
+ \quotefromfile webenginewidgets/markdowneditor/previewpage.h
+ \skipto class PreviewPage
+ \printto #endif
+
+ We reimplement the virtual \c acceptNavigationRequest method to
+ stop the page from navigating away from the current document. Instead,
+ we redirect external links to the system browser:
+
+ \quotefromfile webenginewidgets/markdowneditor/previewpage.cpp
+ \skipto acceptNavigationRequest
+ \printuntil
+
+ \section1 Creating the Main Window
+
+ The \c MainWindow class inherits the QMainWindow class:
+
+ \quotefromfile webenginewidgets/markdowneditor/mainwindow.h
+ \skipto class MainWindow :
+ \printto endif
+
+ The class declares private slots that match the actions in the menu,
+ as well as the \c isModified() helper method.
+
+ The actual layout of the main window is specified in a \c .ui file.
+ The widgets and actions are available at runtime in the \c ui member
+ variable.
+
+ \c m_filePath holds the file path to the currently loaded document.
+ \c m_content is an instance of the \c Document class.
+
+ The actual setup of the different objects is done in the \c MainWindow
+ constructor:
+
+ \quotefromfile webenginewidgets/markdowneditor/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printto connect
+
+ The constructor first calls \c setupUi to construct the widgets and menu
+ actions according to the UI file. It then makes sure our custom
+ \c PreviewPage is used by the QWebEngineView instance in \c{ui->preview}.
+
+ \printto ui->preview
+
+ Here the \c textChanged signal of the editor is connected to a lambda that
+ updates the text in \c m_content. This object is then exposed to the JS side
+ by \c QWebChannel under the name \c{content}.
+
+ \printto connect
+
+ Now we can actually load the \e index.html file from the
+ resources. For more information about the file, see
+ \l{Creating an Index File}.
+
+ \printto defaultTextFile
+
+ The menu items are connected to the mapping member slots. The
+ \uicontrol Save item is activated or deactivated depending on whether
+ the user has edited the content.
+
+ \printuntil }
+
+ Finally, we load a default document \e default.md from the resources.
+
+ \section1 Creating an Index File
+
+ \quotefile webenginewidgets/markdowneditor/resources/index.html
+
+ In the \e index.html, we load a custom stylesheet and two JavaScript
+ libraries. \l{http://kevinburke.bitbucket.org/markdowncss/}{markdown.css} is
+ a markdown-friendly stylesheet created by Kevin Burke.
+ \l{https://github.com/chjj/marked}{marked.min.js} is a markdown parser and
+ compiler designed for speed written by Christopher Jeffrey and
+ \e qwebchannel.js is part of the \l{QWebChannel} module.
+
+ In the \c <body> element we first define a \c placeholder element, and
+ make it available as a JavaScript variable. We then define the \c updateText
+ helper method that updates the content of \c placeholder with the HTML
+ that the JavaScript method \c marked() returns.
+
+ Finally, we set up the web channel to access the \c content proxy object
+ and make sure that \c updateText() is called whenever \c content.text
+ changes.
+*/
+
diff --git a/examples/webenginewidgets/markdowneditor/document.cpp b/examples/webenginewidgets/markdowneditor/document.cpp
new file mode 100644
index 000000000..98665680c
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/document.cpp
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "document.h"
+
+void Document::setText(const QString &text)
+{
+ if (text == m_text)
+ return;
+ m_text = text;
+ emit textChanged(m_text);
+}
diff --git a/examples/webenginewidgets/markdowneditor/document.h b/examples/webenginewidgets/markdowneditor/document.h
new file mode 100644
index 000000000..d2f33ec81
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/document.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DOCUMENT_H
+#define DOCUMENT_H
+
+#include <QObject>
+#include <QString>
+
+class Document : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
+public:
+ explicit Document(QObject *parent = nullptr) : QObject(parent) {}
+
+ void setText(const QString &text);
+
+signals:
+ void textChanged(const QString &text);
+
+private:
+ QString m_text;
+};
+
+#endif // DOCUMENT_H
diff --git a/examples/webenginewidgets/markdowneditor/main.cpp b/examples/webenginewidgets/markdowneditor/main.cpp
new file mode 100644
index 000000000..2cf4e1f60
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/main.cpp
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "document.h"
+#include "mainwindow.h"
+
+#include <QApplication>
+#include <QFile>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ MainWindow window;
+ window.show();
+
+ return a.exec();
+}
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.cpp b/examples/webenginewidgets/markdowneditor/mainwindow.cpp
new file mode 100644
index 000000000..fec7c661e
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.cpp
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+#include "previewpage.h"
+#include "ui_mainwindow.h"
+
+#include <QFile>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QTextStream>
+#include <QWebChannel>
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ PreviewPage *page = new PreviewPage(this);
+ ui->preview->setPage(page);
+
+ connect(ui->editor, &QPlainTextEdit::textChanged,
+ [this]() { m_content.setText(ui->editor->toPlainText()); });
+
+ QWebChannel *channel = new QWebChannel(this);
+ channel->registerObject(QStringLiteral("content"), &m_content);
+ page->setWebChannel(channel);
+
+ ui->preview->setUrl(QUrl("qrc:/index.html"));
+
+ connect(ui->actionNew, &QAction::triggered, this, &MainWindow::onFileNew);
+ connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onFileOpen);
+ connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onFileSave);
+ connect(ui->actionSaveAs, &QAction::triggered, this, &MainWindow::onFileSaveAs);
+ connect(ui->actionExit, &QAction::triggered, this, &MainWindow::onExit);
+
+ connect(ui->editor->document(), &QTextDocument::modificationChanged,
+ ui->actionSave, &QAction::setEnabled);
+
+ QFile defaultTextFile(":/default.md");
+ defaultTextFile.open(QIODevice::ReadOnly);
+ ui->editor->setPlainText(defaultTextFile.readAll());
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+bool MainWindow::isModified() const
+{
+ return ui->editor->document()->isModified();
+}
+
+void MainWindow::onFileNew()
+{
+ if (isModified()) {
+ QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(),
+ tr("You have unsaved changes. Do you want to create a new document anyway?"));
+ if (button != QMessageBox::Yes)
+ return;
+ }
+
+ m_filePath.clear();
+ ui->editor->setPlainText(tr("## New document"));
+ ui->editor->document()->setModified(false);
+}
+
+void MainWindow::onFileOpen()
+{
+ if (isModified()) {
+ QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(),
+ tr("You have unsaved changes. Do you want to open a new document anyway?"));
+ if (button != QMessageBox::Yes)
+ return;
+ }
+
+ QString path = QFileDialog::getOpenFileName(this,
+ tr("Open MarkDown File"), "", tr("MarkDown File (*.md)"));
+ if (path.isEmpty())
+ return;
+
+ QFile f(path);
+ if (!f.open(QIODevice::ReadOnly)) {
+ QMessageBox::warning(this, windowTitle(),
+ tr("Could not open file %1: %2").arg(
+ QDir::toNativeSeparators(path), f.errorString()));
+ return;
+ }
+ m_filePath = path;
+ ui->editor->setPlainText(f.readAll());
+}
+
+void MainWindow::onFileSave()
+{
+ if (m_filePath.isEmpty()) {
+ onFileSaveAs();
+ return;
+ }
+
+ QFile f(m_filePath);
+ if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ QMessageBox::warning(this, windowTitle(),
+ tr("Could not write to file %1: %2").arg(
+ QDir::toNativeSeparators(m_filePath), f.errorString()));
+ return;
+ }
+ QTextStream str(&f);
+ str << ui->editor->toPlainText();
+
+ ui->editor->document()->setModified(false);
+}
+
+void MainWindow::onFileSaveAs()
+{
+ QString path = QFileDialog::getSaveFileName(this,
+ tr("Save MarkDown File"), "", tr("MarkDown File (*.md, *.markdown)"));
+ if (path.isEmpty())
+ return;
+ m_filePath = path;
+ onFileSave();
+}
+
+void MainWindow::onExit()
+{
+ if (isModified()) {
+ QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(),
+ tr("You have unsaved changes. Do you want to exit anyway?"));
+ if (button != QMessageBox::Yes)
+ return;
+ }
+ close();
+}
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.h b/examples/webenginewidgets/markdowneditor/mainwindow.h
new file mode 100644
index 000000000..6099f1f79
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "document.h"
+
+#include <QMainWindow>
+#include <QString>
+
+namespace Ui {
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void onFileNew();
+ void onFileOpen();
+ void onFileSave();
+ void onFileSaveAs();
+ void onExit();
+
+private:
+ bool isModified() const;
+
+ Ui::MainWindow *ui;
+ QString m_filePath;
+ Document m_content;
+};
+
+#endif // MAINWINDOW_H
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.ui b/examples/webenginewidgets/markdowneditor/mainwindow.ui
new file mode 100644
index 000000000..36ab352b7
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.ui
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MarkDown Editor</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QPlainTextEdit" name="editor"/>
+ <widget class="QWebEngineView" name="preview" native="true"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>26</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <addaction name="actionNew"/>
+ <addaction name="actionOpen"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSaveAs"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <addaction name="menu_File"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionOpen">
+ <property name="text">
+ <string>&amp;Open...</string>
+ </property>
+ <property name="toolTip">
+ <string>Open document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="text">
+ <string>&amp;Save</string>
+ </property>
+ <property name="toolTip">
+ <string>Save current document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="text">
+ <string>E&amp;xit</string>
+ </property>
+ <property name="toolTip">
+ <string>Exit editor</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionSaveAs">
+ <property name="text">
+ <string>Save &amp;As...</string>
+ </property>
+ <property name="toolTip">
+ <string>Save document under different name</string>
+ </property>
+ </action>
+ <action name="actionNew">
+ <property name="text">
+ <string>&amp;New</string>
+ </property>
+ <property name="toolTip">
+ <string>Create new document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QWebEngineView</class>
+ <extends>QWidget</extends>
+ <header>qwebengineview.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/webenginewidgets/markdowneditor/markdowneditor.pro b/examples/webenginewidgets/markdowneditor/markdowneditor.pro
new file mode 100644
index 000000000..bfd16698a
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/markdowneditor.pro
@@ -0,0 +1,28 @@
+TEMPLATE = app
+
+QT += webenginewidgets webchannel
+CONFIG += c++11
+
+HEADERS += \
+ mainwindow.h \
+ previewpage.h \
+ document.h
+
+SOURCES = \
+ main.cpp \
+ mainwindow.cpp \
+ previewpage.cpp \
+ document.cpp
+
+RESOURCES = \
+ resources/markdowneditor.qrc
+
+FORMS += \
+ mainwindow.ui
+
+DISTFILES += \
+ 3RDPARTY.md
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/markdowneditor
+INSTALLS += target
diff --git a/examples/webenginewidgets/markdowneditor/previewpage.cpp b/examples/webenginewidgets/markdowneditor/previewpage.cpp
new file mode 100644
index 000000000..2d4322664
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/previewpage.cpp
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "previewpage.h"
+
+#include <QDesktopServices>
+
+bool PreviewPage::acceptNavigationRequest(const QUrl &url,
+ QWebEnginePage::NavigationType /*type*/,
+ bool /*isMainFrame*/)
+{
+ // Only allow qrc:/index.html.
+ if (url.scheme() == QString("qrc"))
+ return true;
+ QDesktopServices::openUrl(url);
+ return false;
+}
diff --git a/examples/webenginewidgets/markdowneditor/previewpage.h b/examples/webenginewidgets/markdowneditor/previewpage.h
new file mode 100644
index 000000000..53b0e23e6
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/previewpage.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PREVIEWPAGE_H
+#define PREVIEWPAGE_H
+
+#include <QWebEnginePage>
+
+class PreviewPage : public QWebEnginePage
+{
+ Q_OBJECT
+public:
+ explicit PreviewPage(QObject *parent = nullptr) : QWebEnginePage(parent) {}
+
+protected:
+ bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame);
+};
+
+#endif // PREVIEWPAGE_H
diff --git a/examples/webenginewidgets/markdowneditor/resources/default.md b/examples/webenginewidgets/markdowneditor/resources/default.md
new file mode 100644
index 000000000..e5905b101
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/default.md
@@ -0,0 +1,12 @@
+## Markdown Editor Example
+
+This example uses [QWebEngineView](http://doc.qt.io/qt-5/qwebengineview.html)
+to preview text written using the [Markdown](https://en.wikipedia.org/wiki/Markdown)
+syntax.
+
+### Acknowledgments
+
+The conversion from Markdown to HTML is done with the help of the
+[marked JavaScript library](https://github.com/chjj/marked) by _Christopher Jeffrey_.
+The [style sheet](http://kevinburke.bitbucket.org/markdowncss/markdown.css)
+was created by _Kevin Burke_.
diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html
new file mode 100644
index 000000000..2f45479ed
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/index.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en">
+<meta charset="utf-8">
+<head>
+ <link rel="stylesheet" type="text/css" href="markdown.css">
+ <script src="marked.min.js"></script>
+ <script src="qwebchannel.js"></script>
+</head>
+<body>
+ <div id="placeholder"></div>
+ <script>
+ 'use strict';
+
+ var placeholder = document.getElementById('placeholder');
+
+ var updateText = function(text) {
+ placeholder.innerHTML = marked(text);
+ }
+
+ new QWebChannel(qt.webChannelTransport,
+ function(channel) {
+ var content = channel.objects.content;
+ updateText(content.text);
+ content.textChanged.connect(updateText);
+ }
+ );
+ </script>
+</body>
+</html>
+
+
+
diff --git a/examples/webenginewidgets/markdowneditor/resources/markdown.css b/examples/webenginewidgets/markdowneditor/resources/markdown.css
new file mode 100644
index 000000000..24fc2ffe2
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/markdown.css
@@ -0,0 +1,260 @@
+body{
+ margin: 0 auto;
+ font-family: Georgia, Palatino, serif;
+ color: #444444;
+ line-height: 1;
+ max-width: 960px;
+ padding: 30px;
+}
+h1, h2, h3, h4 {
+ color: #111111;
+ font-weight: 400;
+}
+h1, h2, h3, h4, h5, p {
+ margin-bottom: 24px;
+ padding: 0;
+}
+h1 {
+ font-size: 48px;
+}
+h2 {
+ font-size: 36px;
+ /* The bottom margin is small. It's designed to be used with gray meta text
+ * below a post title. */
+ margin: 24px 0 6px;
+}
+h3 {
+ font-size: 24px;
+}
+h4 {
+ font-size: 21px;
+}
+h5 {
+ font-size: 18px;
+}
+a {
+ color: #0099ff;
+ margin: 0;
+ padding: 0;
+ vertical-align: baseline;
+}
+a:hover {
+ text-decoration: none;
+ color: #ff6600;
+}
+a:visited {
+ color: purple;
+}
+ul, ol {
+ padding: 0;
+ margin: 0;
+}
+li {
+ line-height: 24px;
+}
+li ul, li ul {
+ margin-left: 24px;
+}
+p, ul, ol {
+ font-size: 16px;
+ line-height: 24px;
+ max-width: 540px;
+}
+pre {
+ padding: 0px 24px;
+ max-width: 800px;
+ white-space: pre-wrap;
+}
+code {
+ font-family: Consolas, Monaco, Andale Mono, monospace;
+ line-height: 1.5;
+ font-size: 13px;
+}
+aside {
+ display: block;
+ float: right;
+ width: 390px;
+}
+blockquote {
+ border-left:.5em solid #eee;
+ padding: 0 2em;
+ margin-left:0;
+ max-width: 476px;
+}
+blockquote cite {
+ font-size:14px;
+ line-height:20px;
+ color:#bfbfbf;
+}
+blockquote cite:before {
+ content: '\2014 \00A0';
+}
+
+blockquote p {
+ color: #666;
+ max-width: 460px;
+}
+hr {
+ width: 540px;
+ text-align: left;
+ margin: 0 auto 0 0;
+ color: #999;
+}
+
+/* Code below this line is copyright Twitter Inc. */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%;
+ margin: 0;
+ vertical-align: baseline;
+ *vertical-align: middle;
+}
+button, input {
+ line-height: normal;
+ *overflow: visible;
+}
+button::-moz-focus-inner, input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+input[type=checkbox], input[type=radio] {
+ cursor: pointer;
+}
+/* override default chrome & firefox settings */
+input:not([type="image"]), textarea {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+input[type="search"] {
+ -webkit-appearance: textfield;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+label,
+input,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: normal;
+ margin-bottom: 18px;
+}
+input[type=checkbox], input[type=radio] {
+ cursor: pointer;
+ margin-bottom: 0;
+}
+input[type=text],
+input[type=password],
+textarea,
+select {
+ display: inline-block;
+ width: 210px;
+ padding: 4px;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 18px;
+ height: 18px;
+ color: #808080;
+ border: 1px solid #ccc;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+select, input[type=file] {
+ height: 27px;
+ line-height: 27px;
+}
+textarea {
+ height: auto;
+}
+
+/* grey out placeholders */
+:-moz-placeholder {
+ color: #bfbfbf;
+}
+::-webkit-input-placeholder {
+ color: #bfbfbf;
+}
+
+input[type=text],
+input[type=password],
+select,
+textarea {
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+input[type=text]:focus, input[type=password]:focus, textarea:focus {
+ outline: none;
+ border-color: rgba(82, 168, 236, 0.8);
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+/* buttons */
+button {
+ display: inline-block;
+ padding: 4px 14px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #0064cd;
+ background-repeat: repeat-x;
+ background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));
+ background-image: -moz-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -ms-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));
+ background-image: -webkit-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -o-linear-gradient(top, #049cdb, #0064cd);
+ background-image: linear-gradient(top, #049cdb, #0064cd);
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ border: 1px solid #004b9a;
+ border-bottom-color: #003f81;
+ -webkit-transition: 0.1s linear all;
+ -moz-transition: 0.1s linear all;
+ transition: 0.1s linear all;
+ border-color: #0064cd #0064cd #003f81;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+button:hover {
+ color: #fff;
+ background-position: 0 -15px;
+ text-decoration: none;
+}
+button:active {
+ -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+button::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
diff --git a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc
new file mode 100644
index 000000000..8b7471a9c
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc
@@ -0,0 +1,9 @@
+<RCC>
+ <qresource prefix="/">
+ <file>index.html</file>
+ <file>qwebchannel.js</file>
+ <file>marked.min.js</file>
+ <file>default.md</file>
+ <file>markdown.css</file>
+ </qresource>
+</RCC>
diff --git a/examples/webenginewidgets/markdowneditor/resources/marked.min.js b/examples/webenginewidgets/markdowneditor/resources/marked.min.js
new file mode 100644
index 000000000..f3542fff0
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/marked.min.js
@@ -0,0 +1,6 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
+(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",/<!--[\s\S]*?-->/)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].split(/ *\| */)}this.tokens.push(item);continue}if(cap=this.rules.lheading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[2]==="="?1:2,text:cap[1]});continue}if(cap=this.rules.hr.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"hr"});continue}if(cap=this.rules.blockquote.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"blockquote_start"});cap=cap[0].replace(/^ *> ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i<l;i++){item=cap[i];space=item.length;item=item.replace(/^ *([*+-]|\d+\.) +/,"");if(~item.indexOf("\n ")){space-=item.length;item=!this.options.pedantic?item.replace(new RegExp("^ {1,"+space+"}","gm"),""):item.replace(/^ {1,4}/gm,"")}if(this.options.smartLists&&i!==l-1){b=block.bullet.exec(cap[i+1])[0];if(bull!==b&&!(bull.length>1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].replace(/^ *\| *| *\| *$/g,"").split(/ *\| */)}this.tokens.push(item);continue}if(top&&(cap=this.rules.paragraph.exec(src))){src=src.substring(cap[0].length);this.tokens.push({type:"paragraph",text:cap[1].charAt(cap[1].length-1)==="\n"?cap[1].slice(0,-1):cap[1]});continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"text",text:cap[0]});continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return this.tokens};var inline={escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/};inline._inside=/(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;inline._href=/\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^<a /i.test(cap[0])){this.inLink=true}else if(this.inLink&&/^<\/a>/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i<l;i++){ch=text.charCodeAt(i);if(Math.random()>.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"<pre><code>"+(escaped?code:escape(code,true))+"\n</code></pre>"}return'<pre><code class="'+this.options.langPrefix+escape(lang,true)+'">'+(escaped?code:escape(code,true))+"\n</code></pre>\n"};Renderer.prototype.blockquote=function(quote){return"<blockquote>\n"+quote+"</blockquote>\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"<h"+level+' id="'+this.options.headerPrefix+raw.toLowerCase().replace(/[^\w]+/g,"-")+'">'+text+"</h"+level+">\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"<hr/>\n":"<hr>\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"</"+type+">\n"};Renderer.prototype.listitem=function(text){return"<li>"+text+"</li>\n"};Renderer.prototype.paragraph=function(text){return"<p>"+text+"</p>\n"};Renderer.prototype.table=function(header,body){return"<table>\n"+"<thead>\n"+header+"</thead>\n"+"<tbody>\n"+body+"</tbody>\n"+"</table>\n"};Renderer.prototype.tablerow=function(content){return"<tr>\n"+content+"</tr>\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"</"+type+">\n"};Renderer.prototype.strong=function(text){return"<strong>"+text+"</strong>"};Renderer.prototype.em=function(text){return"<em>"+text+"</em>"};Renderer.prototype.codespan=function(text){return"<code>"+text+"</code>"};Renderer.prototype.br=function(){return this.options.xhtml?"<br/>":"<br>"};Renderer.prototype.del=function(text){return"<del>"+text+"</del>"};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='<a href="'+href+'"';if(title){out+=' title="'+title+'"'}out+=">"+text+"</a>";return out};Renderer.prototype.image=function(href,title,text){var out='<img src="'+href+'" alt="'+text+'"';if(title){out+=' title="'+title+'"'}out+=this.options.xhtml?"/>":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i<this.token.header.length;i++){flags={header:true,align:this.token.align[i]};cell+=this.renderer.tablecell(this.inline.output(this.token.header[i]),{header:true,align:this.token.align[i]})}header+=this.renderer.tablerow(cell);for(i=0;i<this.token.cells.length;i++){row=this.token.cells[i];cell="";for(j=0;j<row.length;j++){cell+=this.renderer.tablecell(this.inline.output(row[j]),{header:false,align:this.token.align[j]})}body+=this.renderer.tablerow(cell)}return this.renderer.table(header,body)}case"blockquote_start":{var body="";while(this.next().type!=="blockquote_end"){body+=this.tok()}return this.renderer.blockquote(body)}case"list_start":{var body="",ordered=this.token.ordered;while(this.next().type!=="list_end"){body+=this.tok()}return this.renderer.list(body,ordered)}case"list_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.token.type==="text"?this.parseText():this.tok()}return this.renderer.listitem(body)}case"loose_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.tok()}return this.renderer.listitem(body)}case"html":{var html=!this.token.pre&&!this.options.pedantic?this.inline.output(this.token.text):this.token.text;return this.renderer.html(html)}case"paragraph":{return this.renderer.paragraph(this.inline.output(this.token.text))}case"text":{return this.renderer.paragraph(this.parseText())}}};function escape(html,encode){return html.replace(!encode?/&(?!#?\w+;)/g:/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;i<arguments.length;i++){target=arguments[i];for(key in target){if(Object.prototype.hasOwnProperty.call(target,key)){obj[key]=target[key]}}}return obj}function marked(src,opt,callback){if(callback||typeof opt==="function"){if(!callback){callback=opt;opt=null}opt=merge({},marked.defaults,opt||{});var highlight=opt.highlight,tokens,pending,i=0;try{tokens=Lexer.lex(src,opt)}catch(e){return callback(e)}pending=tokens.length;var done=function(err){if(err){opt.highlight=highlight;return callback(err)}var out;try{out=Parser.parse(tokens,opt)}catch(e){err=e}opt.highlight=highlight;return err?callback(err):callback(null,out)};if(!highlight||highlight.length<3){return done()}delete opt.highlight;if(!pending)return done();for(;i<tokens.length;i++){(function(token){if(token.type!=="code"){return--pending||done()}return highlight(token.text,token.lang,function(err,code){if(err)return done(err);if(code==null||code===token.text){return--pending||done()}token.text=code;token.escaped=true;--pending||done()})})(tokens[i])}return}try{if(opt)opt=merge({},marked.defaults,opt);return Parser.parse(Lexer.lex(src,opt),opt)}catch(e){e.message+="\nPlease report this to https://github.com/chjj/marked.";if((opt||marked.defaults).silent){return"<p>An error occurred:</p><pre>"+escape(e.message+"",true)+"</pre>"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file
diff --git a/examples/webenginewidgets/markdowneditor/resources/qwebchannel.js b/examples/webenginewidgets/markdowneditor/resources/qwebchannel.js
new file mode 100644
index 000000000..d8c28bc66
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/qwebchannel.js
@@ -0,0 +1,413 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtWebChannel module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+"use strict";
+
+var QWebChannelMessageTypes = {
+ signal: 1,
+ propertyUpdate: 2,
+ init: 3,
+ idle: 4,
+ debug: 5,
+ invokeMethod: 6,
+ connectToSignal: 7,
+ disconnectFromSignal: 8,
+ setProperty: 9,
+ response: 10,
+};
+
+var QWebChannel = function(transport, initCallback)
+{
+ if (typeof transport !== "object" || typeof transport.send !== "function") {
+ console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
+ " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
+ return;
+ }
+
+ var channel = this;
+ this.transport = transport;
+
+ this.send = function(data)
+ {
+ if (typeof(data) !== "string") {
+ data = JSON.stringify(data);
+ }
+ channel.transport.send(data);
+ }
+
+ this.transport.onmessage = function(message)
+ {
+ var data = message.data;
+ if (typeof data === "string") {
+ data = JSON.parse(data);
+ }
+ switch (data.type) {
+ case QWebChannelMessageTypes.signal:
+ channel.handleSignal(data);
+ break;
+ case QWebChannelMessageTypes.response:
+ channel.handleResponse(data);
+ break;
+ case QWebChannelMessageTypes.propertyUpdate:
+ channel.handlePropertyUpdate(data);
+ break;
+ default:
+ console.error("invalid message received:", message.data);
+ break;
+ }
+ }
+
+ this.execCallbacks = {};
+ this.execId = 0;
+ this.exec = function(data, callback)
+ {
+ if (!callback) {
+ // if no callback is given, send directly
+ channel.send(data);
+ return;
+ }
+ if (channel.execId === Number.MAX_VALUE) {
+ // wrap
+ channel.execId = Number.MIN_VALUE;
+ }
+ if (data.hasOwnProperty("id")) {
+ console.error("Cannot exec message with property id: " + JSON.stringify(data));
+ return;
+ }
+ data.id = channel.execId++;
+ channel.execCallbacks[data.id] = callback;
+ channel.send(data);
+ };
+
+ this.objects = {};
+
+ this.handleSignal = function(message)
+ {
+ var object = channel.objects[message.object];
+ if (object) {
+ object.signalEmitted(message.signal, message.args);
+ } else {
+ console.warn("Unhandled signal: " + message.object + "::" + message.signal);
+ }
+ }
+
+ this.handleResponse = function(message)
+ {
+ if (!message.hasOwnProperty("id")) {
+ console.error("Invalid response message received: ", JSON.stringify(message));
+ return;
+ }
+ channel.execCallbacks[message.id](message.data);
+ delete channel.execCallbacks[message.id];
+ }
+
+ this.handlePropertyUpdate = function(message)
+ {
+ for (var i in message.data) {
+ var data = message.data[i];
+ var object = channel.objects[data.object];
+ if (object) {
+ object.propertyUpdate(data.signals, data.properties);
+ } else {
+ console.warn("Unhandled property update: " + data.object + "::" + data.signal);
+ }
+ }
+ channel.exec({type: QWebChannelMessageTypes.idle});
+ }
+
+ this.debug = function(message)
+ {
+ channel.send({type: QWebChannelMessageTypes.debug, data: message});
+ };
+
+ channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
+ for (var objectName in data) {
+ var object = new QObject(objectName, data[objectName], channel);
+ }
+ // now unwrap properties, which might reference other registered objects
+ for (var objectName in channel.objects) {
+ channel.objects[objectName].unwrapProperties();
+ }
+ if (initCallback) {
+ initCallback(channel);
+ }
+ channel.exec({type: QWebChannelMessageTypes.idle});
+ });
+};
+
+function QObject(name, data, webChannel)
+{
+ this.__id__ = name;
+ webChannel.objects[name] = this;
+
+ // List of callbacks that get invoked upon signal emission
+ this.__objectSignals__ = {};
+
+ // Cache of all properties, updated when a notify signal is emitted
+ this.__propertyCache__ = {};
+
+ var object = this;
+
+ // ----------------------------------------------------------------------
+
+ this.unwrapQObject = function(response)
+ {
+ if (response instanceof Array) {
+ // support list of objects
+ var ret = new Array(response.length);
+ for (var i = 0; i < response.length; ++i) {
+ ret[i] = object.unwrapQObject(response[i]);
+ }
+ return ret;
+ }
+ if (!response
+ || !response["__QObject*__"]
+ || response.id === undefined) {
+ return response;
+ }
+
+ var objectId = response.id;
+ if (webChannel.objects[objectId])
+ return webChannel.objects[objectId];
+
+ if (!response.data) {
+ console.error("Cannot unwrap unknown QObject " + objectId + " without data.");
+ return;
+ }
+
+ var qObject = new QObject( objectId, response.data, webChannel );
+ qObject.destroyed.connect(function() {
+ if (webChannel.objects[objectId] === qObject) {
+ delete webChannel.objects[objectId];
+ // reset the now deleted QObject to an empty {} object
+ // just assigning {} though would not have the desired effect, but the
+ // below also ensures all external references will see the empty map
+ // NOTE: this detour is necessary to workaround QTBUG-40021
+ var propertyNames = [];
+ for (var propertyName in qObject) {
+ propertyNames.push(propertyName);
+ }
+ for (var idx in propertyNames) {
+ delete qObject[propertyNames[idx]];
+ }
+ }
+ });
+ // here we are already initialized, and thus must directly unwrap the properties
+ qObject.unwrapProperties();
+ return qObject;
+ }
+
+ this.unwrapProperties = function()
+ {
+ for (var propertyIdx in object.__propertyCache__) {
+ object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);
+ }
+ }
+
+ function addSignal(signalData, isPropertyNotifySignal)
+ {
+ var signalName = signalData[0];
+ var signalIndex = signalData[1];
+ object[signalName] = {
+ connect: function(callback) {
+ if (typeof(callback) !== "function") {
+ console.error("Bad callback given to connect to signal " + signalName);
+ return;
+ }
+
+ object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
+ object.__objectSignals__[signalIndex].push(callback);
+
+ if (!isPropertyNotifySignal && signalName !== "destroyed") {
+ // only required for "pure" signals, handled separately for properties in propertyUpdate
+ // also note that we always get notified about the destroyed signal
+ webChannel.exec({
+ type: QWebChannelMessageTypes.connectToSignal,
+ object: object.__id__,
+ signal: signalIndex
+ });
+ }
+ },
+ disconnect: function(callback) {
+ if (typeof(callback) !== "function") {
+ console.error("Bad callback given to disconnect from signal " + signalName);
+ return;
+ }
+ object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
+ var idx = object.__objectSignals__[signalIndex].indexOf(callback);
+ if (idx === -1) {
+ console.error("Cannot find connection of signal " + signalName + " to " + callback.name);
+ return;
+ }
+ object.__objectSignals__[signalIndex].splice(idx, 1);
+ if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {
+ // only required for "pure" signals, handled separately for properties in propertyUpdate
+ webChannel.exec({
+ type: QWebChannelMessageTypes.disconnectFromSignal,
+ object: object.__id__,
+ signal: signalIndex
+ });
+ }
+ }
+ };
+ }
+
+ /**
+ * Invokes all callbacks for the given signalname. Also works for property notify callbacks.
+ */
+ function invokeSignalCallbacks(signalName, signalArgs)
+ {
+ var connections = object.__objectSignals__[signalName];
+ if (connections) {
+ connections.forEach(function(callback) {
+ callback.apply(callback, signalArgs);
+ });
+ }
+ }
+
+ this.propertyUpdate = function(signals, propertyMap)
+ {
+ // update property cache
+ for (var propertyIndex in propertyMap) {
+ var propertyValue = propertyMap[propertyIndex];
+ object.__propertyCache__[propertyIndex] = propertyValue;
+ }
+
+ for (var signalName in signals) {
+ // Invoke all callbacks, as signalEmitted() does not. This ensures the
+ // property cache is updated before the callbacks are invoked.
+ invokeSignalCallbacks(signalName, signals[signalName]);
+ }
+ }
+
+ this.signalEmitted = function(signalName, signalArgs)
+ {
+ invokeSignalCallbacks(signalName, signalArgs);
+ }
+
+ function addMethod(methodData)
+ {
+ var methodName = methodData[0];
+ var methodIdx = methodData[1];
+ object[methodName] = function() {
+ var args = [];
+ var callback;
+ for (var i = 0; i < arguments.length; ++i) {
+ if (typeof arguments[i] === "function")
+ callback = arguments[i];
+ else
+ args.push(arguments[i]);
+ }
+
+ webChannel.exec({
+ "type": QWebChannelMessageTypes.invokeMethod,
+ "object": object.__id__,
+ "method": methodIdx,
+ "args": args
+ }, function(response) {
+ if (response !== undefined) {
+ var result = object.unwrapQObject(response);
+ if (callback) {
+ (callback)(result);
+ }
+ }
+ });
+ };
+ }
+
+ function bindGetterSetter(propertyInfo)
+ {
+ var propertyIndex = propertyInfo[0];
+ var propertyName = propertyInfo[1];
+ var notifySignalData = propertyInfo[2];
+ // initialize property cache with current value
+ // NOTE: if this is an object, it is not directly unwrapped as it might
+ // reference other QObject that we do not know yet
+ object.__propertyCache__[propertyIndex] = propertyInfo[3];
+
+ if (notifySignalData) {
+ if (notifySignalData[0] === 1) {
+ // signal name is optimized away, reconstruct the actual name
+ notifySignalData[0] = propertyName + "Changed";
+ }
+ addSignal(notifySignalData, true);
+ }
+
+ Object.defineProperty(object, propertyName, {
+ configurable: true,
+ get: function () {
+ var propertyValue = object.__propertyCache__[propertyIndex];
+ if (propertyValue === undefined) {
+ // This shouldn't happen
+ console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
+ }
+
+ return propertyValue;
+ },
+ set: function(value) {
+ if (value === undefined) {
+ console.warn("Property setter for " + propertyName + " called with undefined value!");
+ return;
+ }
+ object.__propertyCache__[propertyIndex] = value;
+ webChannel.exec({
+ "type": QWebChannelMessageTypes.setProperty,
+ "object": object.__id__,
+ "property": propertyIndex,
+ "value": value
+ });
+ }
+ });
+
+ }
+
+ // ----------------------------------------------------------------------
+
+ data.methods.forEach(addMethod);
+
+ data.properties.forEach(bindGetterSetter);
+
+ data.signals.forEach(function(signal) { addSignal(signal, false); });
+
+ for (var name in data.enums) {
+ object[name] = data.enums[name];
+ }
+}
+
+//required for use with nodejs
+if (typeof module === 'object') {
+ module.exports = {
+ QWebChannel: QWebChannel
+ };
+}