diff options
Diffstat (limited to 'examples')
23 files changed, 1067 insertions, 290 deletions
diff --git a/examples/webchannel/chatclient-html/chatclient.html b/examples/webchannel/chatclient-html/chatclient.html index b392282..d3b36d8 100644 --- a/examples/webchannel/chatclient-html/chatclient.html +++ b/examples/webchannel/chatclient-html/chatclient.html @@ -21,16 +21,13 @@ window.onload = function() { var socket = new WebSocket(wsUri); - socket.onclose = function() - { + socket.onclose = function() { console.error("web channel closed"); }; - socket.onerror = function(error) - { + socket.onerror = function(error) { console.error("web channel error: " + error); }; - socket.onopen = function() - { + socket.onopen = function() { window.channel = new QWebChannel(socket, function(channel) { //connect to the changed signal of a property channel.objects.chatserver.userListChanged.connect(function() { @@ -42,11 +39,11 @@ }); //connect to a signal channel.objects.chatserver.newMessage.connect(function(time, user, message) { - $('#chat').append("[" + time + "] " + user + ": " + message + '<br>'); + $('#chat').append("[" + time + "] " + user + ": " + message + '<br>'); }); //connect to a signal channel.objects.chatserver.keepAlive.connect(function(args) { - if(window.loggedin) { + if (window.loggedin) { //call a method channel.objects.chatserver.keepAliveResponse($('#loginname').val()) console.log("sent alive"); @@ -74,11 +71,12 @@ </div> <script> $('#loginForm').submit(submitForm); + function submitForm(event) { console.log("DEBUG login: " + channel); channel.objects.chatserver.login($('#loginname').val(), function(arg) { console.log("DEBUG login response: " + arg); - if(arg === true) { + if (arg === true) { $('#loginError').hide(); $('#loginDialog').dialog('close'); window.loggedin = true; @@ -87,10 +85,9 @@ } }); console.log($('#loginname').val()); - if(event !== undefined) + if (event !== undefined) event.preventDefault(); return false; - } </script> </div> @@ -110,10 +107,11 @@ </div> <script> $('#messageForm').submit(submitMessage); + function submitMessage(event) { channel.objects.chatserver.sendMessage($('#loginname').val(), $('#message').val()); $('#message').val(''); - if(event !== undefined) + if (event !== undefined) event.preventDefault(); return false; } @@ -122,7 +120,7 @@ <script type="text/javascript"> -$(document).ready(function(){ +$(document).ready(function() { $('#loginError').hide(); }); </script> diff --git a/examples/webchannel/chatclient-qml/LoginForm.ui.qml b/examples/webchannel/chatclient-qml/LoginForm.ui.qml new file mode 100644 index 0000000..c8045e5 --- /dev/null +++ b/examples/webchannel/chatclient-qml/LoginForm.ui.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 basysKom GmbH, author Bernd Lamecker <bernd.lamecker@basyskom.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.4 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.0 + +Item { + property alias userName: userName + property alias loginButton: loginButton + property alias nameInUseError: nameInUseError + + ColumnLayout { + anchors.right: parent.right + anchors.left: parent.left + anchors.top: parent.top + + TextField { + id: userName + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + + Button { + id: loginButton + text: "Login" + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + + Label { + id: nameInUseError + text: "Name already in use" + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + } +} diff --git a/examples/webchannel/chatclient-qml/MainForm.ui.qml b/examples/webchannel/chatclient-qml/MainForm.ui.qml new file mode 100644 index 0000000..ff881df --- /dev/null +++ b/examples/webchannel/chatclient-qml/MainForm.ui.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2016 basysKom GmbH, author Bernd Lamecker <bernd.lamecker@basyskom.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +Item { + property alias chat: chat + property alias userlist: userlist + property alias message: message + + GridLayout { + anchors.fill: parent + rows: 2 + columns: 2 + + Text { + id: chat + Layout.fillWidth: true + Layout.fillHeight: true + } + + Text { + id: userlist + width: 150 + Layout.fillHeight: true + } + + TextField { + id: message + height: 50 + Layout.fillWidth: true + Layout.columnSpan: 2 + } + } +} diff --git a/examples/webchannel/chatclient-qml/chatclient-qml.pro b/examples/webchannel/chatclient-qml/chatclient-qml.pro index 185ffde..34fc08a 100644 --- a/examples/webchannel/chatclient-qml/chatclient-qml.pro +++ b/examples/webchannel/chatclient-qml/chatclient-qml.pro @@ -1,6 +1,8 @@ TEMPLATE = aux exampleassets.files += \ + LoginForm.ui.qml \ + MainForm.ui.qml \ qmlchatclient.qml exampleassets.path = $$[QT_INSTALL_EXAMPLES]/webchannel/chatclient-qml diff --git a/examples/webchannel/chatclient-qml/qmlchatclient.qml b/examples/webchannel/chatclient-qml/qmlchatclient.qml index abb0ea9..e10b118 100644 --- a/examples/webchannel/chatclient-qml/qmlchatclient.qml +++ b/examples/webchannel/chatclient-qml/qmlchatclient.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Copyright (C) 2016 basysKom GmbH, author Bernd Lamecker <bernd.lamecker@basyskom.com> ** Contact: https://www.qt.io/licensing/ ** @@ -50,7 +50,8 @@ ****************************************************************************/ import QtQuick 2.2 -import QtQuick.Controls 1.1 +import QtQuick.Dialogs 1.2 +import QtQuick.Controls 2.0 import QtQuick.Window 2.0 import QtQuick.Layouts 1.1 import Qt.WebSockets 1.0 @@ -58,133 +59,138 @@ import "qwebchannel.js" as WebChannel ApplicationWindow { id: root - title: qsTr("Hello World") + + property var channel + property string loginName: loginUi.userName.text + + title: "Chat client" width: 640 height: 480 - - property var channel; + visible: true WebSocket { id: socket - url: "ws://localhost:12345"; - active: false - - // the following three properties/functions are required to align the QML WebSocket API with the HTML5 WebSocket API. - property var send: function (arg) { + // the following three properties/functions are required to align the QML WebSocket API + // with the HTML5 WebSocket API. + property var send: function(arg) { sendTextMessage(arg); } onTextMessageReceived: { onmessage({data: message}); } - property var onmessage; - - onStatusChanged: if (socket.status == WebSocket.Error) { - console.error("Error: " + socket.errorString) - } else if (socket.status == WebSocket.Closed) { - messageBox.text += "\nSocket closed" - } else if (socket.status == WebSocket.Open) { - //open the webchannel with the socket as transport - new WebChannel.QWebChannel(socket, function(ch) { - root.channel = ch; - - //connect to the changed signal of the userList property - ch.objects.chatserver.userListChanged.connect(function(args) { - userlist.text = ''; - ch.objects.chatserver.userList.forEach(function(user) { - userlist.text += user + '\n'; - }); - }); - //connect to the newMessage signal - ch.objects.chatserver.newMessage.connect(function(time, user, message) { - chat.text = chat.text + "[" + time + "] " + user + ": " + message + '\n'; - }); - //connect to the keep alive signal - ch.objects.chatserver.keepAlive.connect(function(args) { - if (loginName.text !== '') - //and call the keep alive response method as an answer - ch.objects.chatserver.keepAliveResponse(loginName.text) - }); - }); - } - } - GridLayout { - id: grid - columns: 2 - anchors.fill: parent - Text { - id: chat - text: "" - Layout.fillHeight: true - Layout.fillWidth: true + property var onmessage + + active: true + url: "ws://localhost:12345" + + onStatusChanged: { + switch (socket.status) { + case WebSocket.Error: + errorDialog.text = "Error: " + socket.errorString; + errorDialog.visible = true; + break; + case WebSocket.Closed: + errorDialog.text = "Error: Socket at " + url + " closed."; + errorDialog.visible = true; + break; + case WebSocket.Open: + //open the webchannel with the socket as transport + new WebChannel.QWebChannel(socket, function(ch) { + root.channel = ch; + + //connect to the changed signal of the userList property + ch.objects.chatserver.userListChanged.connect(function(args) { + mainUi.userlist.text = ''; + ch.objects.chatserver.userList.forEach(function(user) { + mainUi.userlist.text += user + '\n'; + }); + }); + + //connect to the newMessage signal + ch.objects.chatserver.newMessage.connect(function(time, user, message) { + var line = "[" + time + "] " + user + ": " + message + '\n'; + mainUi.chat.text = mainUi.chat.text + line; + }); + + //connect to the keep alive signal + ch.objects.chatserver.keepAlive.connect(function(args) { + if (loginName !== '') + //and call the keep alive response method as an answer + ch.objects.chatserver.keepAliveResponse(loginName); + }); + }); + + loginWindow.show(); + break; + } } + } - Text { - id: userlist - text: "" - width: 150 - Layout.fillHeight: true - } - TextField { - id: message - height: 50 - Layout.columnSpan: 2 - Layout.fillWidth: true + MainForm { + id: mainUi + anchors.fill: parent + Connections { + target: mainUi.message onEditingFinished: { - if (message.text.length) + if (mainUi.message.text.length) { //call the sendMessage method to send the message - root.channel.objects.chatserver.sendMessage(loginName.text, message.text); - message.text = ''; + root.channel.objects.chatserver.sendMessage(loginName, + mainUi.message.text); + } + mainUi.message.text = ''; } } } - Window { - id: loginWindow; - title: "Login"; - modality: Qt.ApplicationModal - - TextField { - id: loginName - - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - } - Button { - anchors.top: loginName.bottom - anchors.horizontalCenter: parent.horizontalCenter - id: loginButton - text: "Login" - - onClicked: { - //call the login method - root.channel.objects.chatserver.login(loginName.text, function(arg) { - //check the return value for success - if (arg === true) { - loginError.visible = false; - loginWindow.close(); - } else { - loginError.visible = true; - } - }); - - } - } - Text { - id: loginError - anchors.top: loginButton.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: "Name already in use" - visible: false; - } + id: loginWindow + + title: "Login" + modality: Qt.ApplicationModal + width: 300 + height: 200 + + LoginForm { + id: loginUi + anchors.fill: parent + + nameInUseError.visible: false + + Connections { + target: loginUi.loginButton + + onClicked: { + //call the login method + root.channel.objects.chatserver.login(loginName, function(arg) { + //check the return value for success + if (arg === true) { + loginUi.nameInUseError.visible = false; + loginWindow.close(); + } else { + loginUi.nameInUseError.visible = true; + } + }); + } + } + } } - Component.onCompleted: { - loginWindow.show(); - socket.active = true; //connect + MessageDialog { + id: errorDialog + + icon: StandardIcon.Critical + standardButtons: StandardButton.Close + title: "Chat client" + + onAccepted: { + Qt.quit(); + } + onRejected: { + Qt.quit(); + } } } diff --git a/examples/webchannel/chatserver-cpp/chatserver.cpp b/examples/webchannel/chatserver-cpp/chatserver.cpp index 74da4c3..1025c80 100644 --- a/examples/webchannel/chatserver-cpp/chatserver.cpp +++ b/examples/webchannel/chatserver-cpp/chatserver.cpp @@ -50,30 +50,28 @@ #include "chatserver.h" -#include <QtCore/QDebug> -#include <QTimer> +#include <QDebug> #include <QTime> - -QT_BEGIN_NAMESPACE +#include <QTimer> ChatServer::ChatServer(QObject *parent) : QObject(parent) { - QTimer* t = new QTimer(this); - connect(t, SIGNAL(timeout()), this, SLOT(sendKeepAlive())); + QTimer *t = new QTimer(this); + connect(t, &QTimer::timeout, this, &ChatServer::sendKeepAlive); t->start(10000); m_keepAliveCheckTimer = new QTimer(this); m_keepAliveCheckTimer->setSingleShot(true); m_keepAliveCheckTimer->setInterval(2000); - connect(m_keepAliveCheckTimer, SIGNAL(timeout()), this, SLOT(checkKeepAliveResponses())); + connect(m_keepAliveCheckTimer, &QTimer::timeout, this, &ChatServer::checkKeepAliveResponses); } ChatServer::~ChatServer() {} -bool ChatServer::login(const QString& userName) +bool ChatServer::login(const QString &userName) { //stop keepAliveCheck, when a new user logged in if (m_keepAliveCheckTimer->isActive()) { @@ -93,7 +91,7 @@ bool ChatServer::login(const QString& userName) return true; } -bool ChatServer::logout(const QString& userName) +bool ChatServer::logout(const QString &userName) { if (!m_userList.contains(userName)) { return false; @@ -105,7 +103,7 @@ bool ChatServer::logout(const QString& userName) } } -bool ChatServer::sendMessage(const QString& user, const QString& msg) +bool ChatServer::sendMessage(const QString &user, const QString &msg) { if (m_userList.contains(user)) { emit newMessage(QTime::currentTime().toString("HH:mm:ss"), user, msg); @@ -130,7 +128,7 @@ void ChatServer::checkKeepAliveResponses() emit userListChanged(); } -void ChatServer::keepAliveResponse(const QString& user) +void ChatServer::keepAliveResponse(const QString &user) { m_stillAliveUsers.append(user); } @@ -140,5 +138,3 @@ QStringList ChatServer::userList() const { return m_userList; } - -QT_END_NAMESPACE diff --git a/examples/webchannel/chatserver-cpp/chatserver.h b/examples/webchannel/chatserver-cpp/chatserver.h index 20f0928..9bc8026 100644 --- a/examples/webchannel/chatserver-cpp/chatserver.h +++ b/examples/webchannel/chatserver-cpp/chatserver.h @@ -48,15 +48,15 @@ ** ****************************************************************************/ -#ifndef ChatServer_H -#define ChatServer_H +#ifndef CHATSERVER_H +#define CHATSERVER_H #include <QObject> #include <QStringList> QT_BEGIN_NAMESPACE - class QTimer; +QT_END_NAMESPACE class ChatServer : public QObject { @@ -65,22 +65,22 @@ class ChatServer : public QObject Q_PROPERTY(QStringList userList READ userList NOTIFY userListChanged) public: - explicit ChatServer(QObject *parent = 0); + explicit ChatServer(QObject *parent = nullptr); virtual ~ChatServer(); public: //a user logs in with the given username - Q_INVOKABLE bool login(const QString& userName); + Q_INVOKABLE bool login(const QString &userName); //the user logs out, will be removed from userlist immediately - Q_INVOKABLE bool logout(const QString& userName); + Q_INVOKABLE bool logout(const QString &userName); //a user sends a message to all other users - Q_INVOKABLE bool sendMessage(const QString& user, const QString& msg); + Q_INVOKABLE bool sendMessage(const QString &user, const QString &msg); //response of the keep alive signal from a client. // This is used to detect disconnects. - Q_INVOKABLE void keepAliveResponse(const QString& user); + Q_INVOKABLE void keepAliveResponse(const QString &user); QStringList userList() const; @@ -97,9 +97,7 @@ signals: private: QStringList m_userList; QStringList m_stillAliveUsers; - QTimer* m_keepAliveCheckTimer; + QTimer *m_keepAliveCheckTimer; }; -QT_END_NAMESPACE - -#endif // ChatServer_H +#endif // CHATSERVER_H diff --git a/examples/webchannel/chatserver-cpp/main.cpp b/examples/webchannel/chatserver-cpp/main.cpp index ea27e87..9e025ac 100644 --- a/examples/webchannel/chatserver-cpp/main.cpp +++ b/examples/webchannel/chatserver-cpp/main.cpp @@ -48,14 +48,14 @@ ** ****************************************************************************/ -#include "qwebchannel.h" #include "chatserver.h" #include "../shared/websocketclientwrapper.h" #include "../shared/websockettransport.h" -#include <QtWebSockets/QWebSocketServer> #include <QCoreApplication> +#include <QWebChannel> +#include <QWebSocketServer> int main(int argc, char** argv) { diff --git a/examples/webchannel/exampleassets.pri b/examples/webchannel/exampleassets.pri index bbdf656..094d4d8 100644 --- a/examples/webchannel/exampleassets.pri +++ b/examples/webchannel/exampleassets.pri @@ -1,12 +1,5 @@ # This adds the qwebchannel js library to an example, creating a self-contained bundle -QTDIR_build { - # Build from within Qt. Copy and install the reference lib. - jslib = $$dirname(_QMAKE_CONF_)/src/webchannel/qwebchannel.js - assetcopy.files = $$jslib -} else { - # This is what an actual 3rd party project would do. - jslib = qwebchannel.js -} +jslib = $$PWD/shared/qwebchannel.js # This installs all assets including qwebchannel.js, regardless of the source. exampleassets.files += $$jslib @@ -16,7 +9,10 @@ INSTALLS += exampleassets !equals(_PRO_FILE_PWD_, $$OUT_PWD) { # Shadow build, copy all example assets. - assetcopy.files = $$exampleassets.files + assetcopy.files += $$exampleassets.files +} else { + # Just copy jslib - other assets are already in place. + assetcopy.files = $$jslib } assetcopy.path = $$OUT_PWD diff --git a/examples/webchannel/nodejs/chatclient.js b/examples/webchannel/nodejs/chatclient.js index 5e458f2..76b620c 100644 --- a/examples/webchannel/nodejs/chatclient.js +++ b/examples/webchannel/nodejs/chatclient.js @@ -91,7 +91,10 @@ var createWebChannel = function(transport, rlif) { console.log(' << ' + message); rlif.prompt(); // Go to end of existing input if any - rlif.write(null, {ctrl: true, name: 'e'}) + rlif.write(null, { + ctrl: true, + name: 'e' + }) }); rlif.on('line', function(line) { @@ -118,7 +121,9 @@ socket.on('open', function(event) { var transport = { // We cant't do 'send: socket.send' here // because 'send' wouldn't be bound to 'socket' - send: function(data) {socket.send(data)} + send: function(data) { + socket.send(data) + } }; createWebChannel(transport, createReadlineInterface()); @@ -131,12 +136,12 @@ socket.on('open', function(event) { }); }); -socket.on('error', function (error) { +socket.on('error', function(error) { console.log('Connection error: ' + error.message); process.exit(1); }); -socket.on('close', function () { +socket.on('close', function() { console.log('Connection closed.'); process.exit(1); }); diff --git a/examples/webchannel/qwclient/qwclient.js b/examples/webchannel/qwclient/qwclient.js index 0e28328..0b0909f 100755 --- a/examples/webchannel/qwclient/qwclient.js +++ b/examples/webchannel/qwclient/qwclient.js @@ -49,6 +49,7 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ + 'use strict'; var repl = require('repl'); var WebSocket = require('faye-websocket').Client; @@ -62,7 +63,7 @@ if (autoConnect === __filename) { autoConnect = false; } -var openChannel = function (address) { +var openChannel = function(address) { // this should be bound to the repl var self = this; address = address ? address : serverAddress; @@ -72,25 +73,27 @@ var openChannel = function (address) { var ws = new WebSocket(address); - ws.on('open', function (event) { + ws.on('open', function(event) { var transport = { - onmessage: function (data) {}, - send: function (data) { - ws.send(data, {binary: false}); - } + onmessage: function(data) {}, + send: function(data) { + ws.send(data, { + binary: false + }); + } }; - ws.on('message', function (event) { - transport.onmessage(event); + ws.on('message', function(event) { + transport.onmessage(event); }); // onmessage - var webChannel = new QWebChannel(transport, function (channel) { + var webChannel = new QWebChannel(transport, function(channel) { channels.push(channel); var channelIdx = (channels.length - 1); console.log('channel opened', channelIdx); // Create a nice alias to access this channels objects self.context['c' + channelIdx] = channel.objects; - ws.on('close', function () { + ws.on('close', function() { for (var i = 0; i < channels.length; ++i) { if (channels[i] === channel) { console.log('channel closed', i); @@ -102,7 +105,7 @@ var openChannel = function (address) { }); // new QWebChannel }); // onopen - ws.on('error', function (error) { + ws.on('error', function(error) { console.log('websocket error', error.message); }); }; // openChannel @@ -119,7 +122,7 @@ var setupRepl = function() { r.context.channels = channels; r.context.lsObjects = function() { - channels.forEach(function(channel){ + channels.forEach(function(channel) { console.log('Channel ' + channel); Object.keys(channel.objects); }); diff --git a/examples/webchannel/shared/qwebchannel.js b/examples/webchannel/shared/qwebchannel.js new file mode 100644 index 0000000..5b047c2 --- /dev/null +++ b/examples/webchannel/shared/qwebchannel.js @@ -0,0 +1,427 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $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, this.unwrapQObject(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) { + var argument = arguments[i]; + if (typeof argument === "function") + callback = argument; + else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined) + args.push({ + "id": argument.__id__ + }); + else + args.push(argument); + } + + 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; + var valueToSend = value; + if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined) + valueToSend = { "id": valueToSend.__id__ }; + webChannel.exec({ + "type": QWebChannelMessageTypes.setProperty, + "object": object.__id__, + "property": propertyIndex, + "value": valueToSend + }); + } + }); + + } + + // ---------------------------------------------------------------------- + + 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 + }; +} diff --git a/examples/webchannel/shared/websocketclientwrapper.cpp b/examples/webchannel/shared/websocketclientwrapper.cpp index 3743bdd..16be85e 100644 --- a/examples/webchannel/shared/websocketclientwrapper.cpp +++ b/examples/webchannel/shared/websocketclientwrapper.cpp @@ -51,7 +51,7 @@ #include "websocketclientwrapper.h" #include "websockettransport.h" -#include <QtWebSockets/QWebSocketServer> +#include <QWebSocketServer> /*! \brief Wraps connected QWebSockets clients in WebSocketTransport objects. @@ -61,8 +61,6 @@ published objects. */ -QT_BEGIN_NAMESPACE - /*! Construct the client wrapper with the given parent. @@ -84,5 +82,3 @@ void WebSocketClientWrapper::handleNewConnection() { emit clientConnected(new WebSocketTransport(m_server->nextPendingConnection())); } - -QT_END_NAMESPACE diff --git a/examples/webchannel/shared/websocketclientwrapper.h b/examples/webchannel/shared/websocketclientwrapper.h index f03b825..efb8b4b 100644 --- a/examples/webchannel/shared/websocketclientwrapper.h +++ b/examples/webchannel/shared/websocketclientwrapper.h @@ -48,33 +48,32 @@ ** ****************************************************************************/ -#ifndef WEBSOCKETTRANSPORTSERVER_H -#define WEBSOCKETTRANSPORTSERVER_H +#ifndef WEBSOCKETCLIENTWRAPPER_H +#define WEBSOCKETCLIENTWRAPPER_H #include <QObject> -QT_BEGIN_NAMESPACE +class WebSocketTransport; +QT_BEGIN_NAMESPACE class QWebSocketServer; -class WebSocketTransport; +QT_END_NAMESPACE class WebSocketClientWrapper : public QObject { Q_OBJECT public: - WebSocketClientWrapper(QWebSocketServer *server, QObject *parent = 0); + WebSocketClientWrapper(QWebSocketServer *server, QObject *parent = nullptr); -Q_SIGNALS: - void clientConnected(WebSocketTransport* client); +signals: + void clientConnected(WebSocketTransport *client); -private Q_SLOTS: +private slots: void handleNewConnection(); private: QWebSocketServer *m_server; }; -QT_END_NAMESPACE - -#endif // WEBSOCKETTRANSPORTSERVER_H +#endif // WEBSOCKETCLIENTWRAPPER_H diff --git a/examples/webchannel/shared/websockettransport.cpp b/examples/webchannel/shared/websockettransport.cpp index a65bc2d..e4ce50a 100644 --- a/examples/webchannel/shared/websockettransport.cpp +++ b/examples/webchannel/shared/websockettransport.cpp @@ -50,11 +50,10 @@ #include "websockettransport.h" +#include <QDebug> #include <QJsonDocument> #include <QJsonObject> -#include <QDebug> - -#include <QtWebSockets/QWebSocket> +#include <QWebSocket> /*! \brief QWebChannelAbstractSocket implementation that uses a QWebSocket internally. @@ -64,8 +63,6 @@ be send over the QWebSocket to the remote client. */ -QT_BEGIN_NAMESPACE - /*! Construct the transport object and wrap the given socket. @@ -115,5 +112,3 @@ void WebSocketTransport::textMessageReceived(const QString &messageData) } emit messageReceived(message.object(), this); } - -QT_END_NAMESPACE diff --git a/examples/webchannel/shared/websockettransport.h b/examples/webchannel/shared/websockettransport.h index 44cb92e..252eaeb 100644 --- a/examples/webchannel/shared/websockettransport.h +++ b/examples/webchannel/shared/websockettransport.h @@ -51,11 +51,12 @@ #ifndef WEBSOCKETTRANSPORT_H #define WEBSOCKETTRANSPORT_H -#include <QtWebChannel/QWebChannelAbstractTransport> +#include <QWebChannelAbstractTransport> QT_BEGIN_NAMESPACE - class QWebSocket; +QT_END_NAMESPACE + class WebSocketTransport : public QWebChannelAbstractTransport { Q_OBJECT @@ -63,15 +64,13 @@ public: explicit WebSocketTransport(QWebSocket *socket); virtual ~WebSocketTransport(); - void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE; + void sendMessage(const QJsonObject &message) override; -private Q_SLOTS: +private slots: void textMessageReceived(const QString &message); private: QWebSocket *m_socket; }; -QT_END_NAMESPACE - #endif // WEBSOCKETTRANSPORT_H diff --git a/examples/webchannel/standalone/core.h b/examples/webchannel/standalone/core.h new file mode 100644 index 0000000..0e91bb7 --- /dev/null +++ b/examples/webchannel/standalone/core.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CORE_H +#define CORE_H + +#include "dialog.h" +#include <QObject> + +/* + An instance of this class gets published over the WebChannel and is then accessible to HTML clients. +*/ +class Core : public QObject +{ + Q_OBJECT + +public: + Core(Dialog *dialog, QObject *parent = nullptr) + : QObject(parent), m_dialog(dialog) + { + connect(dialog, &Dialog::sendText, this, &Core::sendText); + } + +signals: + /* + This signal is emitted from the C++ side and the text displayed on the HTML client side. + */ + void sendText(const QString &text); + +public slots: + + /* + This slot is invoked from the HTML client side and the text displayed on the server side. + */ + void receiveText(const QString &text) + { + m_dialog->displayMessage(Dialog::tr("Received message: %1").arg(text)); + } + +private: + Dialog *m_dialog; +}; + +#endif // CORE_H diff --git a/examples/webchannel/standalone/dialog.cpp b/examples/webchannel/standalone/dialog.cpp new file mode 100644 index 0000000..c9fc164 --- /dev/null +++ b/examples/webchannel/standalone/dialog.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "dialog.h" +#include "ui_dialog.h" + +Dialog::Dialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::Dialog) +{ + ui->setupUi(this); + connect(ui->send, &QPushButton::clicked, this, &Dialog::clicked); +} + +void Dialog::displayMessage(const QString &message) +{ + ui->output->appendPlainText(message); +} + +void Dialog::clicked() +{ + const QString text = ui->input->text(); + + if (text.isEmpty()) + return; + + emit sendText(text); + displayMessage(tr("Sent message: %1").arg(text)); + + ui->input->clear(); +} diff --git a/examples/webchannel/standalone/dialog.h b/examples/webchannel/standalone/dialog.h new file mode 100644 index 0000000..143f2d8 --- /dev/null +++ b/examples/webchannel/standalone/dialog.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DIALOG_H +#define DIALOG_H + +#include <QDialog> + +QT_BEGIN_NAMESPACE +namespace Ui { +class Dialog; +} +QT_END_NAMESPACE + +class Dialog : public QDialog +{ + Q_OBJECT + +public: + explicit Dialog(QWidget *parent = nullptr); + + void displayMessage(const QString &message); + +signals: + void sendText(const QString &text); + +private slots: + void clicked(); + +private: + Ui::Dialog *ui; +}; + +#endif // DIALOG_H diff --git a/examples/webchannel/standalone/doc/src/standalone.qdoc b/examples/webchannel/standalone/doc/src/standalone.qdoc index 0d030bf..6777492 100644 --- a/examples/webchannel/standalone/doc/src/standalone.qdoc +++ b/examples/webchannel/standalone/doc/src/standalone.qdoc @@ -64,13 +64,13 @@ \section1 Communicating with a Remote Client - The C++ application sets up a QWebChannel instance and publishes a \c Dialog object over it. + The C++ application sets up a QWebChannel instance and publishes a \c Core object over it. For the remote client side, \l {standalone/index.html}{index.html} is opened. Both show a dialog with the list of received messages and an input box to send messages to the other end. - The \c Dialog emits the \c Dialog::sendText() signal when the user sends a message. The signal + The \c Core emits the \c Core::sendText() signal when the user sends a message. The signal automatically gets propagated to the HTML client. When the user enters a message on the HTML - side, \c Dialog::receiveText() is called. + side, \c Core::receiveText() is called. All communication between the HTML client and the C++ server is done over a WebSocket. The C++ side instantiates a QWebSocketServer and wraps incoming QWebSocket connections diff --git a/examples/webchannel/standalone/index.html b/examples/webchannel/standalone/index.html index b5a9a49..7c042cd 100644 --- a/examples/webchannel/standalone/index.html +++ b/examples/webchannel/standalone/index.html @@ -5,8 +5,7 @@ <script type="text/javascript" src="./qwebchannel.js"></script> <script type="text/javascript"> //BEGIN SETUP - function output(message) - { + function output(message) { var output = document.getElementById("output"); output.innerHTML = output.innerHTML + message + "\n"; } @@ -19,20 +18,17 @@ output("Connecting to WebSocket server at " + baseUrl + "."); var socket = new WebSocket(baseUrl); - socket.onclose = function() - { + socket.onclose = function() { console.error("web channel closed"); }; - socket.onerror = function(error) - { + socket.onerror = function(error) { console.error("web channel error: " + error); }; - socket.onopen = function() - { + socket.onopen = function() { output("WebSocket connected, setting up QWebChannel."); new QWebChannel(socket, function(channel) { - // make dialog object accessible globally - window.dialog = channel.objects.dialog; + // make core object accessible globally + window.core = channel.objects.core; document.getElementById("send").onclick = function() { var input = document.getElementById("input"); @@ -43,14 +39,14 @@ output("Sent message: " + text); input.value = ""; - dialog.receiveText(text); + core.receiveText(text); } - dialog.sendText.connect(function(message) { + core.sendText.connect(function(message) { output("Received message: " + message); }); - dialog.receiveText("Client connected, ready to send/receive messages!"); + core.receiveText("Client connected, ready to send/receive messages!"); output("Connected to WebChannel, ready to send/receive messages!"); }); } diff --git a/examples/webchannel/standalone/main.cpp b/examples/webchannel/standalone/main.cpp index b53e9a6..3ea66ad 100644 --- a/examples/webchannel/standalone/main.cpp +++ b/examples/webchannel/standalone/main.cpp @@ -48,81 +48,19 @@ ** ****************************************************************************/ -#include "qwebchannel.h" +#include "dialog.h" +#include "core.h" +#include "../shared/websocketclientwrapper.h" +#include "../shared/websockettransport.h" #include <QApplication> -#include <QDialog> -#include <QVariantMap> #include <QDesktopServices> -#include <QUrl> +#include <QDialog> #include <QDir> #include <QFileInfo> -#include <QtWebSockets/QWebSocketServer> - -#include "../shared/websocketclientwrapper.h" -#include "../shared/websockettransport.h" - -#include "ui_dialog.h" - -/*! - An instance of this class gets published over the WebChannel and is then accessible to HTML clients. -*/ -class Dialog : public QObject -{ - Q_OBJECT - -public: - explicit Dialog(QObject *parent = 0) - : QObject(parent) - { - ui.setupUi(&dialog); - dialog.show(); - - connect(ui.send, SIGNAL(clicked()), SLOT(clicked())); - } - - void displayMessage(const QString &message) - { - ui.output->appendPlainText(message); - } - -signals: - /*! - This signal is emitted from the C++ side and the text displayed on the HTML client side. - */ - void sendText(const QString &text); - -public slots: - /*! - This slot is invoked from the HTML client side and the text displayed on the server side. - */ - void receiveText(const QString &text) - { - displayMessage(tr("Received message: %1").arg(text)); - } - -private slots: - /*! - Note that this slot is private and thus not accessible to HTML clients. - */ - void clicked() - { - const QString text = ui.input->text(); - - if (text.isEmpty()) { - return; - } - - emit sendText(text); - displayMessage(tr("Sent message: %1").arg(text)); - - ui.input->clear(); - } - -private: - QDialog dialog; - Ui::Dialog ui; -}; +#include <QUrl> +#include <QWebChannel> +#include <QWebSocketServer> int main(int argc, char** argv) { @@ -148,17 +86,19 @@ int main(int argc, char** argv) QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected, &channel, &QWebChannel::connectTo); - // setup the dialog and publish it to the QWebChannel + // setup the UI Dialog dialog; - channel.registerObject(QStringLiteral("dialog"), &dialog); + + // setup the core and publish it to the QWebChannel + Core core(&dialog); + channel.registerObject(QStringLiteral("core"), &core); // open a browser window with the client HTML page QUrl url = QUrl::fromLocalFile(BUILD_DIR "/index.html"); QDesktopServices::openUrl(url); - dialog.displayMessage(QObject::tr("Initialization complete, opening browser at %1.").arg(url.toDisplayString())); + dialog.displayMessage(Dialog::tr("Initialization complete, opening browser at %1.").arg(url.toDisplayString())); + dialog.show(); return app.exec(); } - -#include "main.moc" diff --git a/examples/webchannel/standalone/standalone.pro b/examples/webchannel/standalone/standalone.pro index cfe4297..eea78ef 100644 --- a/examples/webchannel/standalone/standalone.pro +++ b/examples/webchannel/standalone/standalone.pro @@ -4,10 +4,13 @@ CONFIG += warn_on SOURCES += \ main.cpp \ + dialog.cpp \ ../shared/websockettransport.cpp \ ../shared/websocketclientwrapper.cpp HEADERS += \ + core.h \ + dialog.h \ ../shared/websockettransport.h \ ../shared/websocketclientwrapper.h |