2017-11-02 17:20:42 +00:00
|
|
|
//
|
|
|
|
// Copyright (C) 2017 James Turner zakalawe@mac.com
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
#include "canvasconnection.h"
|
|
|
|
|
|
|
|
#include <QUrl>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QJsonValue>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkReply>
|
2017-11-05 19:42:57 +00:00
|
|
|
#include <QDataStream>
|
2017-11-02 17:20:42 +00:00
|
|
|
|
|
|
|
#include "localprop.h"
|
2017-11-03 13:56:43 +00:00
|
|
|
#include "fgqcanvasfontcache.h"
|
|
|
|
#include "fgqcanvasimageloader.h"
|
2018-06-24 13:11:38 +00:00
|
|
|
#include "jsonutils.h"
|
2017-11-02 17:20:42 +00:00
|
|
|
|
|
|
|
CanvasConnection::CanvasConnection(QObject *parent) : QObject(parent)
|
|
|
|
{
|
|
|
|
connect(&m_webSocket, &QWebSocket::connected, this, &CanvasConnection::onWebSocketConnected);
|
|
|
|
connect(&m_webSocket, &QWebSocket::disconnected, this, &CanvasConnection::onWebSocketClosed);
|
|
|
|
connect(&m_webSocket, &QWebSocket::textMessageReceived,
|
|
|
|
this, &CanvasConnection::onTextMessageReceived);
|
|
|
|
|
|
|
|
m_destRect = QRectF(50, 50, 400, 400);
|
2018-06-26 22:23:49 +00:00
|
|
|
m_reconnectTimer = new QTimer(this);
|
|
|
|
m_reconnectTimer->setInterval(1000 * 10);
|
|
|
|
m_reconnectTimer->setSingleShot(true);
|
|
|
|
connect(m_reconnectTimer, &QTimer::timeout,
|
|
|
|
this, &CanvasConnection::reconnect);
|
2017-11-02 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CanvasConnection::~CanvasConnection()
|
|
|
|
{
|
|
|
|
disconnect(&m_webSocket, &QWebSocket::disconnected,
|
|
|
|
this, &CanvasConnection::onWebSocketClosed);
|
|
|
|
m_webSocket.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::setNetworkAccess(QNetworkAccessManager *dl)
|
|
|
|
{
|
|
|
|
m_netAccess = dl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::setRootPropertyPath(QByteArray path)
|
|
|
|
{
|
|
|
|
m_rootPropertyPath = path;
|
|
|
|
emit rootPathChanged();
|
|
|
|
}
|
|
|
|
|
2018-06-26 22:23:49 +00:00
|
|
|
void CanvasConnection::setAutoReconnect()
|
|
|
|
{
|
|
|
|
m_autoReconnect = true;
|
|
|
|
}
|
|
|
|
|
2017-11-02 17:20:42 +00:00
|
|
|
QJsonObject CanvasConnection::saveState() const
|
|
|
|
{
|
|
|
|
QJsonObject json;
|
|
|
|
json["url"] = m_webSocketUrl.toString();
|
|
|
|
json["path"] = QString::fromUtf8(m_rootPropertyPath);
|
2018-06-24 13:11:38 +00:00
|
|
|
json["rect"] = rectToJsonArray(m_destRect.toRect());
|
2017-11-02 17:20:42 +00:00
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CanvasConnection::restoreState(QJsonObject state)
|
|
|
|
{
|
|
|
|
m_webSocketUrl = state.value("url").toString();
|
|
|
|
m_rootPropertyPath = state.value("path").toString().toUtf8();
|
2018-06-24 13:11:38 +00:00
|
|
|
m_destRect = jsonArrayToRect(state.value("rect").toArray());
|
2017-11-02 17:20:42 +00:00
|
|
|
|
2017-11-03 15:31:57 +00:00
|
|
|
emit geometryChanged();
|
2017-11-02 17:20:42 +00:00
|
|
|
emit rootPathChanged();
|
|
|
|
emit webSocketUrlChanged();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-11-05 19:42:57 +00:00
|
|
|
void CanvasConnection::saveSnapshot(QDataStream &ds) const
|
|
|
|
{
|
|
|
|
ds << m_webSocketUrl << m_rootPropertyPath << m_destRect;
|
|
|
|
m_localPropertyRoot->saveToStream(ds);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::restoreSnapshot(QDataStream &ds)
|
|
|
|
{
|
|
|
|
ds >> m_webSocketUrl >> m_rootPropertyPath >> m_destRect;
|
2017-11-08 14:24:12 +00:00
|
|
|
m_localPropertyRoot.reset(LocalProp::restoreFromStream(ds, nullptr));
|
2017-11-05 19:42:57 +00:00
|
|
|
setStatus(Snapshot);
|
2017-11-08 14:24:12 +00:00
|
|
|
|
|
|
|
emit geometryChanged();
|
|
|
|
emit rootPathChanged();
|
|
|
|
emit webSocketUrlChanged();
|
|
|
|
|
|
|
|
emit updated();
|
2017-11-05 19:42:57 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 17:20:42 +00:00
|
|
|
void CanvasConnection::reconnect()
|
|
|
|
{
|
2018-06-26 22:23:49 +00:00
|
|
|
qDebug() << "starting connection attempt to:" << m_webSocketUrl;
|
2017-11-02 17:20:42 +00:00
|
|
|
m_webSocket.open(m_webSocketUrl);
|
|
|
|
setStatus(Connecting);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::showDebugTree()
|
|
|
|
{
|
|
|
|
qWarning() << Q_FUNC_INFO << "implement me!";
|
|
|
|
}
|
|
|
|
|
2017-11-03 15:31:57 +00:00
|
|
|
void CanvasConnection::setOrigin(QPointF c)
|
2017-11-02 17:20:42 +00:00
|
|
|
{
|
2017-11-03 15:31:57 +00:00
|
|
|
if (m_destRect.topLeft() == c)
|
2017-11-02 17:20:42 +00:00
|
|
|
return;
|
|
|
|
|
2017-11-03 15:31:57 +00:00
|
|
|
m_destRect.moveTopLeft(c);
|
|
|
|
emit geometryChanged();
|
2017-11-02 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::setSize(QSizeF sz)
|
|
|
|
{
|
|
|
|
if (size() == sz)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_destRect.setSize(sz);
|
2017-11-03 15:31:57 +00:00
|
|
|
emit geometryChanged();
|
2017-11-02 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::connectWebSocket(QByteArray hostName, int port)
|
|
|
|
{
|
|
|
|
QUrl wsUrl;
|
|
|
|
wsUrl.setScheme("ws");
|
|
|
|
wsUrl.setHost(hostName);
|
|
|
|
wsUrl.setPort(port);
|
|
|
|
wsUrl.setPath("/PropertyTreeMirror" + m_rootPropertyPath);
|
|
|
|
|
|
|
|
m_webSocketUrl = wsUrl;
|
|
|
|
emit webSocketUrlChanged();
|
|
|
|
|
|
|
|
m_webSocket.open(wsUrl);
|
|
|
|
setStatus(Connecting);
|
|
|
|
}
|
|
|
|
|
2017-11-03 15:31:57 +00:00
|
|
|
QPointF CanvasConnection::origin() const
|
2017-11-02 17:20:42 +00:00
|
|
|
{
|
2017-11-03 15:31:57 +00:00
|
|
|
return m_destRect.topLeft();
|
2017-11-02 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QSizeF CanvasConnection::size() const
|
|
|
|
{
|
|
|
|
return m_destRect.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
LocalProp *CanvasConnection::propertyRoot() const
|
|
|
|
{
|
|
|
|
return m_localPropertyRoot.get();
|
|
|
|
}
|
|
|
|
|
2017-11-03 13:56:43 +00:00
|
|
|
FGQCanvasImageLoader *CanvasConnection::imageLoader() const
|
2017-11-02 17:20:42 +00:00
|
|
|
{
|
2017-11-03 13:56:43 +00:00
|
|
|
if (!m_imageLoader) {
|
|
|
|
m_imageLoader = new FGQCanvasImageLoader(m_netAccess, const_cast<CanvasConnection*>(this));
|
|
|
|
m_imageLoader->setHost(m_webSocketUrl.host(),
|
|
|
|
m_webSocketUrl.port());
|
|
|
|
}
|
2017-11-02 17:20:42 +00:00
|
|
|
|
2017-11-03 13:56:43 +00:00
|
|
|
return m_imageLoader;
|
|
|
|
}
|
2017-11-02 17:20:42 +00:00
|
|
|
|
2017-11-03 13:56:43 +00:00
|
|
|
FGQCanvasFontCache *CanvasConnection::fontCache() const
|
|
|
|
{
|
|
|
|
if (!m_fontCache) {
|
|
|
|
m_fontCache = new FGQCanvasFontCache(m_netAccess, const_cast<CanvasConnection*>(this));
|
|
|
|
m_fontCache->setHost(m_webSocketUrl.host(),
|
|
|
|
m_webSocketUrl.port());
|
|
|
|
}
|
2017-11-02 17:20:42 +00:00
|
|
|
|
2017-11-03 13:56:43 +00:00
|
|
|
return m_fontCache;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::onWebSocketConnected()
|
|
|
|
{
|
2018-06-26 22:23:49 +00:00
|
|
|
qDebug() << Q_FUNC_INFO << m_webSocketUrl;
|
2017-11-03 13:56:43 +00:00
|
|
|
m_localPropertyRoot.reset(new LocalProp{nullptr, NameIndexTuple("")});
|
2017-11-02 17:20:42 +00:00
|
|
|
setStatus(Connected);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::onTextMessageReceived(QString message)
|
|
|
|
{
|
|
|
|
QJsonDocument json = QJsonDocument::fromJson(message.toUtf8());
|
|
|
|
if (json.isObject()) {
|
|
|
|
// process new nodes
|
|
|
|
QJsonArray created = json.object().value("created").toArray();
|
|
|
|
Q_FOREACH (QJsonValue v, created) {
|
|
|
|
QJsonObject newProp = v.toObject();
|
|
|
|
|
|
|
|
QByteArray nodePath = newProp.value("path").toString().toUtf8();
|
|
|
|
if (nodePath.indexOf(m_rootPropertyPath) != 0) {
|
|
|
|
qWarning() << "not a property path we are mirroring:" << nodePath;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray localPath = nodePath.mid(m_rootPropertyPath.size() + 1);
|
|
|
|
LocalProp* newNode = propertyFromPath(localPath);
|
|
|
|
newNode->setPosition(newProp.value("position").toInt());
|
|
|
|
// store in the global dict
|
|
|
|
unsigned int propId = newProp.value("id").toInt();
|
|
|
|
if (idPropertyDict.contains(propId)) {
|
|
|
|
qWarning() << "duplicate add of:" << nodePath << "old is" << idPropertyDict.value(propId)->path();
|
|
|
|
} else {
|
|
|
|
idPropertyDict.insert(propId, newNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set initial value
|
|
|
|
newNode->processChange(newProp.value("value"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// process removes
|
|
|
|
QJsonArray removed = json.object().value("removed").toArray();
|
|
|
|
Q_FOREACH (QJsonValue v, removed) {
|
|
|
|
unsigned int propId = v.toInt();
|
2017-11-28 22:32:30 +00:00
|
|
|
if (!idPropertyDict.contains(propId)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto prop = idPropertyDict.value(propId);
|
|
|
|
idPropertyDict.remove(propId);
|
|
|
|
|
|
|
|
// depending on the order removes are sent, the LocalProp
|
|
|
|
// may already have been deleted when its parent was removed,
|
|
|
|
// so check if the QPointer is null
|
|
|
|
if (!prop.isNull()) {
|
2017-11-02 17:20:42 +00:00
|
|
|
prop->parent()->removeChild(prop);
|
|
|
|
}
|
2017-11-28 22:32:30 +00:00
|
|
|
} // of removes processing
|
2017-11-02 17:20:42 +00:00
|
|
|
|
|
|
|
// process changes
|
|
|
|
QJsonArray changed = json.object().value("changed").toArray();
|
|
|
|
|
|
|
|
Q_FOREACH (QJsonValue v, changed) {
|
|
|
|
QJsonArray change = v.toArray();
|
|
|
|
if (change.size() != 2) {
|
|
|
|
qWarning() << "malformed change notification";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int propId = change.at(0).toInt();
|
|
|
|
if (!idPropertyDict.contains(propId)) {
|
|
|
|
qWarning() << "ignoring unknown prop ID " << propId;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
LocalProp* lp = idPropertyDict.value(propId);
|
2017-11-28 22:32:30 +00:00
|
|
|
if (lp != nullptr) {
|
|
|
|
lp->processChange(change.at(1));
|
|
|
|
}
|
|
|
|
} // of change processing
|
2017-11-02 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
emit updated();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::onWebSocketClosed()
|
|
|
|
{
|
|
|
|
qDebug() << "saw web-socket closed";
|
|
|
|
m_localPropertyRoot.reset();
|
|
|
|
idPropertyDict.clear();
|
|
|
|
|
|
|
|
setStatus(Closed);
|
|
|
|
|
2018-06-26 22:23:49 +00:00
|
|
|
if (m_autoReconnect) {
|
|
|
|
m_reconnectTimer->start();
|
|
|
|
}
|
2017-11-02 17:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CanvasConnection::setStatus(CanvasConnection::Status newStatus)
|
|
|
|
{
|
|
|
|
if (newStatus == m_status)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_status = newStatus;
|
|
|
|
emit statusChanged(m_status);
|
|
|
|
}
|
|
|
|
|
|
|
|
LocalProp *CanvasConnection::propertyFromPath(QByteArray path) const
|
|
|
|
{
|
|
|
|
return m_localPropertyRoot->getOrCreateWithPath(path);
|
|
|
|
}
|
|
|
|
|