1
0
Fork 0
flightgear/utils/fgqcanvas/applicationcontroller.cpp
2018-11-07 09:39:54 +01:00

557 lines
14 KiB
C++

//
// 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 "applicationcontroller.h"
#include <QNetworkDiskCache>
#include <QStandardPaths>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
#include <QDataStream>
#include <QWindow>
#include <QTimer>
#include <QGuiApplication>
#include "jsonutils.h"
#include "canvasconnection.h"
ApplicationController::ApplicationController(QObject *parent)
: QObject(parent)
, m_status(Idle)
{
m_netAccess = new QNetworkAccessManager;
m_host = "localhost";
m_port = 8080;
QNetworkDiskCache* cache = new QNetworkDiskCache;
cache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
m_netAccess->setCache(cache); // takes ownership
setStatus(Idle);
rebuildConfigData();
rebuildSnapshotData();
m_uiIdleTimer = new QTimer(this);
m_uiIdleTimer->setInterval(10 * 1000);
connect(m_uiIdleTimer, &QTimer::timeout, this,
&ApplicationController::onUIIdleTimeout);
m_uiIdleTimer->start();
qApp->installEventFilter(this);
}
ApplicationController::~ApplicationController()
{
delete m_netAccess;
}
void ApplicationController::setWindow(QWindow *window)
{
m_window = window;
}
void ApplicationController::restoreWindowState()
{
if (!m_window)
return;
m_window->setWindowState(m_windowState);
}
void ApplicationController::loadFromFile(QString path)
{
if (!QFile::exists(path)) {
qWarning() << Q_FUNC_INFO << "no such file:" << path;
}
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << Q_FUNC_INFO << "failed to open" << path;
return;
}
restoreState(f.readAll());
}
void ApplicationController::setDaemonMode()
{
m_daemonMode = true;
}
void ApplicationController::save(QString configName)
{
QDir d(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
if (!d.exists()) {
d.mkpath(".");
}
// convert spaces to underscores
QString filesystemCleanName = configName.replace(QRegularExpression("[\\s-\\\"/]"), "_");
QFile f(d.filePath(filesystemCleanName + ".json"));
if (f.exists()) {
qWarning() << "not over-writing" << f.fileName();
return;
}
f.open(QIODevice::WriteOnly | QIODevice::Truncate);
f.write(saveState(configName));
QVariantMap m;
m["path"] = f.fileName();
m["name"] = configName;
m_configs.append(m);
emit configListChanged(m_configs);
}
void ApplicationController::rebuildConfigData()
{
m_configs.clear();
QDir d(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
if (!d.exists()) {
emit configListChanged(m_configs);
return;
}
// this requires parsing each config in its entirety just to extract
// the name, which is horrible.
Q_FOREACH (auto entry, d.entryList(QStringList() << "*.json")) {
QString path = d.filePath(entry);
QFile f(path);
f.open(QIODevice::ReadOnly);
QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
QVariantMap m;
m["path"] = path;
m["name"] = doc.object().value("configName").toString();
m_configs.append(m);
}
emit configListChanged(m_configs);
}
void ApplicationController::saveSnapshot(QString snapshotName)
{
QDir d(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
d.cd("Snapshots");
if (!d.exists()) {
d.mkpath(".");
}
// convert spaces to underscores
QString filesystemCleanName = snapshotName.replace(QRegularExpression("[\\s-\\\"/]"), "_");
QFile f(d.filePath(filesystemCleanName + ".fgcanvassnapshot"));
if (f.exists()) {
qWarning() << "not over-writing" << f.fileName();
return;
}
f.open(QIODevice::WriteOnly | QIODevice::Truncate);
f.write(createSnapshot(snapshotName));
QVariantMap m;
m["path"] = f.fileName();
m["name"] = snapshotName;
m_snapshots.append(m);
emit snapshotListChanged();
}
void ApplicationController::restoreSnapshot(int index)
{
QString path = m_snapshots.at(index).toMap().value("path").toString();
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << Q_FUNC_INFO << "failed to open the file";
return;
}
clearConnections();
{
QDataStream ds(&f);
int version, canvasCount;
QString name;
ds >> version >> name >> canvasCount;
for (int i=0; i < canvasCount; ++i) {
CanvasConnection* cc = new CanvasConnection(this);
cc->restoreSnapshot(ds);
m_activeCanvases.append(cc);
}
}
emit activeCanvasesChanged();
}
void ApplicationController::rebuildSnapshotData()
{
m_snapshots.clear();
QDir d(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
d.cd("Snapshots");
if (!d.exists()) {
emit snapshotListChanged();
return;
}
Q_FOREACH (auto entry, d.entryList(QStringList() << "*.fgcanvassnapshot")) {
QFile f(d.filePath(entry));
f.open(QIODevice::ReadOnly);
{
QDataStream ds(&f);
int version;
QString name;
ds >> version;
QVariantMap m;
m["path"] = f.fileName();
ds >>name;
m["name"] = name;
m_snapshots.append(m);
}
}
emit snapshotListChanged();
}
void ApplicationController::query()
{
if (m_query) {
cancelQuery();
}
if (m_host.isEmpty() || (m_port == 0))
return;
QUrl queryUrl;
queryUrl.setScheme("http");
queryUrl.setHost(m_host);
queryUrl.setPort(m_port);
queryUrl.setPath("/json/canvas/by-index");
queryUrl.setQuery("d=2");
m_query = m_netAccess->get(QNetworkRequest(queryUrl));
connect(m_query, &QNetworkReply::finished,
this, &ApplicationController::onFinishedGetCanvasList);
setStatus(Querying);
}
void ApplicationController::cancelQuery()
{
setStatus(Idle);
if (m_query) {
m_query->abort();
m_query->deleteLater();
}
m_query = nullptr;
m_canvases.clear();
emit canvasListChanged();
}
void ApplicationController::clearQuery()
{
cancelQuery();
}
void ApplicationController::restoreConfig(int index)
{
QString path = m_configs.at(index).toMap().value("path").toString();
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << Q_FUNC_INFO << "failed to open the file";
return;
}
restoreState(f.readAll());
}
void ApplicationController::deleteConfig(int index)
{
QString path = m_configs.at(index).toMap().value("path").toString();
QFile f(path);
if (!f.remove()) {
qWarning() << "failed to remove file";
return;
}
m_configs.removeAt(index);
emit configListChanged(m_configs);
}
void ApplicationController::saveConfigChanges(int index)
{
QString path = m_configs.at(index).toMap().value("path").toString();
QString name = m_configs.at(index).toMap().value("name").toString();
doSaveToFile(path, name);
}
void ApplicationController::doSaveToFile(QString path, QString configName)
{
QFile f(path);
f.open(QIODevice::WriteOnly | QIODevice::Truncate);
f.write(saveState(configName));
}
void ApplicationController::openCanvas(QString path)
{
CanvasConnection* cc = new CanvasConnection(this);
cc->setNetworkAccess(m_netAccess);
m_activeCanvases.append(cc);
cc->setRootPropertyPath(path.toUtf8());
cc->connectWebSocket(m_host.toUtf8(), m_port);
emit activeCanvasesChanged();
}
void ApplicationController::closeCanvas(CanvasConnection *canvas)
{
Q_ASSERT(m_activeCanvases.indexOf(canvas) >= 0);
m_activeCanvases.removeOne(canvas);
canvas->deleteLater();
emit activeCanvasesChanged();
}
QString ApplicationController::host() const
{
return m_host;
}
unsigned int ApplicationController::port() const
{
return m_port;
}
QVariantList ApplicationController::canvases() const
{
return m_canvases;
}
QQmlListProperty<CanvasConnection> ApplicationController::activeCanvases()
{
return QQmlListProperty<CanvasConnection>(this, m_activeCanvases);
}
QNetworkAccessManager *ApplicationController::netAccess() const
{
return m_netAccess;
}
bool ApplicationController::showUI() const
{
if (m_blockUIIdle)
return true;
return m_showUI;
}
void ApplicationController::setHost(QString host)
{
if (m_host == host)
return;
m_host = host;
emit hostChanged(m_host);
setStatus(Idle);
}
void ApplicationController::setPort(unsigned int port)
{
if (m_port == port)
return;
m_port = port;
emit portChanged(m_port);
setStatus(Idle);
}
QJsonObject jsonPropNodeFindChild(QJsonObject obj, QByteArray name)
{
Q_FOREACH (QJsonValue v, obj.value("children").toArray()) {
QJsonObject vo = v.toObject();
if (vo.value("name").toString() == name) {
return vo;
}
}
return QJsonObject();
}
void ApplicationController::onFinishedGetCanvasList()
{
m_canvases.clear();
QNetworkReply* reply = m_query;
m_query = nullptr;
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
setStatus(QueryFailed);
emit canvasListChanged();
return;
}
QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
QJsonArray canvasArray = json.object().value("children").toArray();
Q_FOREACH (QJsonValue canvasValue, canvasArray) {
QJsonObject canvas = canvasValue.toObject();
QString canvasName = jsonPropNodeFindChild(canvas, "name").value("value").toString();
QString propPath = canvas.value("path").toString();
QVariantMap info;
info["name"] = canvasName;
info["path"] = propPath;
m_canvases.append(info);
}
emit canvasListChanged();
setStatus(SuccessfulQuery);
}
void ApplicationController::onUIIdleTimeout()
{
m_showUI = false;
emit showUIChanged();
}
void ApplicationController::setStatus(ApplicationController::Status newStatus)
{
if (newStatus == m_status)
return;
m_status = newStatus;
emit statusChanged(m_status);
}
QByteArray ApplicationController::saveState(QString name) const
{
QJsonObject json;
json["configName"] = name;
QJsonArray canvases;
Q_FOREACH (auto canvas, m_activeCanvases) {
canvases.append(canvas->saveState());
}
json["canvases"] = canvases;
if (m_window) {
json["window-rect"] = rectToJsonArray(m_window->geometry());
json["window-state"] = static_cast<int>(m_window->windowState());
}
// background color?
QJsonDocument doc;
doc.setObject(json);
return doc.toJson();
}
void ApplicationController::restoreState(QByteArray bytes)
{
clearConnections();
QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes);
QJsonObject json = jsonDoc.object();
if (m_window) {
QRect r = jsonArrayToRect(json.value("window-rect").toArray());
if (r.isValid()) {
m_window->setGeometry(r);
}
// we have to cache the state becuase it seems to be ignored
// before show is called();
m_windowState = static_cast<Qt::WindowState>(json.value("window-state").toInt());
m_window->setWindowState(m_windowState);
}
// background color
for (auto c : json.value("canvases").toArray()) {
auto cc = new CanvasConnection(this);
if (m_daemonMode)
cc->setAutoReconnect();
cc->setNetworkAccess(m_netAccess);
m_activeCanvases.append(cc);
cc->restoreState(c.toObject());
cc->reconnect();
}
emit activeCanvasesChanged();
}
void ApplicationController::clearConnections()
{
Q_FOREACH(auto c, m_activeCanvases) {
c->deleteLater();
}
m_activeCanvases.clear();
emit activeCanvasesChanged();
}
QByteArray ApplicationController::createSnapshot(QString name) const
{
QByteArray bytes;
const int version = 1;
{
QDataStream ds(&bytes, QIODevice::WriteOnly);
ds << version << name;
ds << m_activeCanvases.size();
Q_FOREACH(auto c, m_activeCanvases) {
c->saveSnapshot(ds);
}
}
return bytes;
}
bool ApplicationController::eventFilter(QObject* obj, QEvent* event)
{
Q_UNUSED(obj);
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::TouchUpdate:
case QEvent::MouseMove:
case QEvent::TouchBegin:
case QEvent::KeyPress:
case QEvent::KeyRelease:
if (!m_showUI) {
m_showUI = true;
emit showUIChanged();
} else {
m_uiIdleTimer->start();
}
break;
default:
break;
}
return false; //process as normal
}