diff --git a/utils/fgqcanvas/CMakeLists.txt b/utils/fgqcanvas/CMakeLists.txt index b1e81c324..ce9e348c8 100644 --- a/utils/fgqcanvas/CMakeLists.txt +++ b/utils/fgqcanvas/CMakeLists.txt @@ -6,17 +6,17 @@ include(GNUInstallDirs) project(FGQCanvas) -find_package(Qt5 5.4 COMPONENTS Widgets WebSockets Gui Quick QuickWidgets) +find_package(Qt5 5.4 COMPONENTS Widgets WebSockets Gui Quick) -if (NOT Qt5WebSockets_FOUND OR NOT Qt5QuickWidgets_FOUND) - message(WARNING "FGQCanvas utility requested, but QtWebSockets/QtQuickWidgets not found") - message(STATUS "Check you have the development package for Qt5 WebSockets/QuickWidgets installed") +if (NOT Qt5WebSockets_FOUND OR NOT Qt5Quick_FOUND) + message(WARNING "FGQCanvas utility requested, but QtWebSockets not found") + message(STATUS "Check you have the development package for Qt5 WebSockets installed") return() endif() set(SOURCES - temporarywidget.cpp - temporarywidget.h + #temporarywidget.cpp + #temporarywidget.h main.cpp localprop.cpp localprop.h @@ -46,14 +46,22 @@ set(SOURCES elementdatamodel.h canvasitem.cpp canvasitem.h + canvasconnection.cpp + canvasconnection.h + applicationcontroller.cpp + applicationcontroller.h + canvasdisplay.cpp + canvasdisplay.h ) -qt5_wrap_ui(uic_sources temporarywidget.ui) +qt5_add_resources(qrc_sources fgqcanvas_resources.qrc) -add_executable(fgqcanvas ${SOURCES} ${uic_sources}) +#qt5_wrap_ui(uic_sources temporarywidget.ui) + +add_executable(fgqcanvas ${SOURCES} ${qrc_sources}) set_property(TARGET fgqcanvas PROPERTY AUTOMOC ON) -target_link_libraries(fgqcanvas Qt5::Core Qt5::Widgets Qt5::WebSockets Qt5::Quick Qt5::QuickWidgets) +target_link_libraries(fgqcanvas Qt5::Core Qt5::Widgets Qt5::WebSockets Qt5::Quick) target_include_directories(fgqcanvas PRIVATE ${PROJECT_SOURCE_DIR}) diff --git a/utils/fgqcanvas/applicationcontroller.cpp b/utils/fgqcanvas/applicationcontroller.cpp new file mode 100644 index 000000000..9a5e90dd3 --- /dev/null +++ b/utils/fgqcanvas/applicationcontroller.cpp @@ -0,0 +1,293 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(); +} + +ApplicationController::~ApplicationController() +{ + delete m_netAccess; +} + +void ApplicationController::save(QString configName) +{ + QDir d(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + if (!d.exists()) { + qWarning() << "creating" << d; + d.mkpath("."); + } + + // convert spaces to underscores + QString filesystemCleanName = configName.replace(QRegularExpression("[\\s-\\\"/]"), "_"); + qDebug() << Q_FUNC_INFO << "FS clean name is " << filesystemCleanName; + + QFile f(d.filePath(configName + ".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::query() +{ + qWarning() << Q_FUNC_INFO << m_host << m_port; + + 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"); + + QNetworkReply* reply = m_netAccess->get(QNetworkRequest(queryUrl)); + connect(reply, &QNetworkReply::finished, + this, &ApplicationController::onFinishedGetCanvasList); + + setStatus(Querying); +} + +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::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(); +} + +QString ApplicationController::host() const +{ + return m_host; +} + +unsigned int ApplicationController::port() const +{ + return m_port; +} + +QVariantList ApplicationController::canvases() const +{ + return m_canvases; +} + +QQmlListProperty ApplicationController::activeCanvases() +{ + return QQmlListProperty(this, m_activeCanvases); +} + +QNetworkAccessManager *ApplicationController::netAccess() const +{ + return m_netAccess; +} + +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 = qobject_cast(sender()); + 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::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; + // background color? + // window geometry and state? + + QJsonDocument doc; + doc.setObject(json); + return doc.toJson(); +} + +void ApplicationController::restoreState(QByteArray bytes) +{ + qDeleteAll(m_activeCanvases); + m_activeCanvases.clear(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes); + QJsonObject json = jsonDoc.object(); + + // window size + // background color + + for (auto c : json.value("canvases").toArray()) { + auto cc = new CanvasConnection(this); + cc->setNetworkAccess(m_netAccess); + m_activeCanvases.append(cc); + cc->restoreState(c.toObject()); + cc->reconnect(); + } + + emit activeCanvasesChanged(); +} diff --git a/utils/fgqcanvas/applicationcontroller.h b/utils/fgqcanvas/applicationcontroller.h new file mode 100644 index 000000000..2bd50bedc --- /dev/null +++ b/utils/fgqcanvas/applicationcontroller.h @@ -0,0 +1,124 @@ +// +// 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. + +#ifndef APPLICATIONCONTROLLER_H +#define APPLICATIONCONTROLLER_H + +#include +#include +#include +#include +#include + +class CanvasConnection; + +class ApplicationController : public QObject +{ + Q_OBJECT + + + Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged) + Q_PROPERTY(unsigned int port READ port WRITE setPort NOTIFY portChanged) + + Q_PROPERTY(QVariantList canvases READ canvases NOTIFY canvasListChanged) + Q_PROPERTY(QVariantList configs READ configs NOTIFY configListChanged) + + + Q_PROPERTY(QQmlListProperty activeCanvases READ activeCanvases NOTIFY activeCanvasesChanged) + + Q_ENUMS(Status) + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) +public: + explicit ApplicationController(QObject *parent = nullptr); + ~ApplicationController(); + + Q_INVOKABLE void save(QString configName); + Q_INVOKABLE void query(); + + Q_INVOKABLE void restoreConfig(int index); + + Q_INVOKABLE void openCanvas(QString path); + +// void restore(); + QString host() const; + + unsigned int port() const; + + QVariantList canvases() const; + + QQmlListProperty activeCanvases(); + + QNetworkAccessManager* netAccess() const; + + enum Status { + Idle, + Querying, + SuccessfulQuery, + QueryFailed + }; + + Status status() const + { + return m_status; + } + + QVariantList configs() const + { + return m_configs; + } + +signals: + + void hostChanged(QString host); + + void portChanged(unsigned int port); + + void activeCanvasesChanged(); + + void canvasListChanged(); + void statusChanged(Status status); + + void configListChanged(QVariantList configs); + +public slots: + void setHost(QString host); + + void setPort(unsigned int port); + +private slots: + void onFinishedGetCanvasList(); + +private: + void setStatus(Status newStatus); + + void rebuildConfigData(); + + QByteArray saveState(QString name) const; + void restoreState(QByteArray bytes); + + QString m_host; + unsigned int m_port; + QVariantList m_canvases; + QList m_activeCanvases; + QNetworkAccessManager* m_netAccess; + Status m_status; + QVariantList m_configs; + +}; + +#endif // APPLICATIONCONTROLLER_H diff --git a/utils/fgqcanvas/canvasconnection.cpp b/utils/fgqcanvas/canvasconnection.cpp new file mode 100644 index 000000000..182b466bd --- /dev/null +++ b/utils/fgqcanvas/canvasconnection.cpp @@ -0,0 +1,256 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "localprop.h" + +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); +} + +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(); +} + +QJsonObject CanvasConnection::saveState() const +{ + QJsonObject json; + json["url"] = m_webSocketUrl.toString(); + json["path"] = QString::fromUtf8(m_rootPropertyPath); + json["rect"] = QJsonArray{m_destRect.x(), m_destRect.y(), m_destRect.width(), m_destRect.height()}; + return json; +} + +bool CanvasConnection::restoreState(QJsonObject state) +{ + m_webSocketUrl = state.value("url").toString(); + m_rootPropertyPath = state.value("path").toString().toUtf8(); + QJsonArray rect = state.value("rect").toArray(); + if (rect.size() < 4) { + return false; + } + + m_destRect = QRectF(rect[0].toDouble(), rect[1].toDouble(), + rect[2].toDouble(), rect[3].toDouble()); + + emit sizeChanged(m_destRect.size()); + emit centerChanged(m_destRect.center()); + emit rootPathChanged(); + emit webSocketUrlChanged(); + + return true; +} + +void CanvasConnection::reconnect() +{ + m_webSocket.open(m_webSocketUrl); + setStatus(Connecting); +} + +void CanvasConnection::showDebugTree() +{ + qWarning() << Q_FUNC_INFO << "implement me!"; +} + +void CanvasConnection::setCenter(QPointF c) +{ + if (center() == c) + return; + + m_destRect.moveCenter(c); + emit centerChanged(c); +} + +void CanvasConnection::setSize(QSizeF sz) +{ + if (size() == sz) + return; + + m_destRect.setSize(sz); + emit sizeChanged(sz); +} + +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); +} + +QPointF CanvasConnection::center() const +{ + return m_destRect.center(); +} + +QSizeF CanvasConnection::size() const +{ + return m_destRect.size(); +} + +LocalProp *CanvasConnection::propertyRoot() const +{ + return m_localPropertyRoot.get(); +} + +void CanvasConnection::onWebSocketConnected() +{ + m_localPropertyRoot.reset(new LocalProp{nullptr, NameIndexTuple("")}); + + // ui->canvas->setRootProperty(m_localPropertyRoot); + +#if 0 + FGQCanvasFontCache::instance()->setHost(ui->hostName->text(), + ui->portEdit->text().toInt()); + FGQCanvasImageLoader::instance()->setHost(ui->hostName->text(), + ui->portEdit->text().toInt()); +#endif + + 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(); + if (idPropertyDict.contains(propId)) { + LocalProp* prop = idPropertyDict.value(propId); + idPropertyDict.remove(propId); + prop->parent()->removeChild(prop); + } + } + + // 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); + lp->processChange(change.at(1)); + } + } + + emit updated(); +} + +void CanvasConnection::onWebSocketClosed() +{ + qDebug() << "saw web-socket closed"; + m_localPropertyRoot.reset(); + idPropertyDict.clear(); + + setStatus(Closed); + + // if we're in automatic mode, start reconnection timer +// update state + + // ui->stack->setCurrentIndex(0); +} + +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); +} + diff --git a/utils/fgqcanvas/canvasconnection.h b/utils/fgqcanvas/canvasconnection.h new file mode 100644 index 000000000..ddee6d60b --- /dev/null +++ b/utils/fgqcanvas/canvasconnection.h @@ -0,0 +1,129 @@ +// +// 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. + +#ifndef CANVASCONNECTION_H +#define CANVASCONNECTION_H + +#include +#include +#include +#include +#include + +class LocalProp; +class QNetworkAccessManager; + +class CanvasConnection : public QObject +{ + Q_OBJECT + + Q_ENUMS(Status) + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + +// QML exposed versions of the destination rect + Q_PROPERTY(QPointF center READ center WRITE setCenter NOTIFY centerChanged) + Q_PROPERTY(QSizeF size READ size WRITE setSize NOTIFY sizeChanged) + + Q_PROPERTY(QUrl webSocketUrl READ webSocketUrl NOTIFY webSocketUrlChanged) + Q_PROPERTY(QString rootPath READ rootPath NOTIFY rootPathChanged) +public: + explicit CanvasConnection(QObject *parent = nullptr); + ~CanvasConnection(); + + void setNetworkAccess(QNetworkAccessManager *dl); + void setRootPropertyPath(QByteArray path); + + enum Status + { + NotConnected, + Connecting, + Connected, + Closed, + Reconnecting, + Error + }; + + Status status() const + { + return m_status; + } + + QJsonObject saveState() const; + bool restoreState(QJsonObject state); + + void connectWebSocket(QByteArray hostName, int port); + QPointF center() const; + + QSizeF size() const; + + LocalProp* propertyRoot() const; + + QUrl webSocketUrl() const + { + return m_webSocketUrl; + } + + QString rootPath() const + { + return QString::fromUtf8(m_rootPropertyPath); + } + +public Q_SLOTS: + void reconnect(); + + // not on iOS / Android - requires widgets + void showDebugTree(); + + void setCenter(QPointF center); + + void setSize(QSizeF size); + +signals: + void statusChanged(Status status); + + void centerChanged(QPointF center); + + void sizeChanged(QSizeF size); + + void rootPathChanged(); + + void webSocketUrlChanged(); + + void updated(); +private Q_SLOTS: + void onWebSocketConnected(); + void onTextMessageReceived(QString message); + void onWebSocketClosed(); + +private: + void setStatus(Status newStatus); + LocalProp *propertyFromPath(QByteArray path) const; + + QUrl m_webSocketUrl; + QByteArray m_rootPropertyPath; + QRectF m_destRect; + + QWebSocket m_webSocket; + QNetworkAccessManager* m_netAccess = nullptr; + std::unique_ptr m_localPropertyRoot; + QHash idPropertyDict; + Status m_status = NotConnected; + QString m_rootPath; +}; + +#endif // CANVASCONNECTION_H diff --git a/utils/fgqcanvas/canvasdisplay.cpp b/utils/fgqcanvas/canvasdisplay.cpp new file mode 100644 index 000000000..5d35f5c93 --- /dev/null +++ b/utils/fgqcanvas/canvasdisplay.cpp @@ -0,0 +1,80 @@ +// +// 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 "canvasdisplay.h" + +#include + +#include "canvasconnection.h" +#include "fgcanvasgroup.h" +#include "fgcanvaspaintcontext.h" +#include "canvasitem.h" + +CanvasDisplay::CanvasDisplay(QQuickItem* parent) : + QQuickItem(parent) +{ + setSize(QSizeF(400, 400)); + qDebug() << "created a canvas display"; + + setFlag(ItemHasContents); +} + +CanvasDisplay::~CanvasDisplay() +{ + +} + +void CanvasDisplay::updatePolish() +{ + m_rootElement->polish(); +} + +void CanvasDisplay::setCanvas(CanvasConnection *canvas) +{ + if (m_connection == canvas) + return; + + if (m_connection) { + disconnect(m_connection, nullptr, this, nullptr); + } + + m_connection = canvas; + emit canvasChanged(m_connection); + + // delete existing children + + connect(m_connection, &CanvasConnection::statusChanged, + this, &CanvasDisplay::onConnectionStatusChanged); + connect(m_connection, &CanvasConnection::updated, + this, &CanvasDisplay::onConnectionUpdated); + +} + +void CanvasDisplay::onConnectionStatusChanged() +{ + if (m_connection->status() == CanvasConnection::Connected) { + m_rootElement.reset(new FGCanvasGroup(nullptr, m_connection->propertyRoot())); + auto qq = m_rootElement->createQuickItem(this); + qq->setSize(QSizeF(400, 400)); + } +} + +void CanvasDisplay::onConnectionUpdated() +{ + m_rootElement->polish(); + update(); +} diff --git a/utils/fgqcanvas/canvasdisplay.h b/utils/fgqcanvas/canvasdisplay.h new file mode 100644 index 000000000..31c309e7a --- /dev/null +++ b/utils/fgqcanvas/canvasdisplay.h @@ -0,0 +1,62 @@ +// +// 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. + +#ifndef CANVASDISPLAY_H +#define CANVASDISPLAY_H + +#include + +class CanvasConnection; +class FGCanvasElement; + +class CanvasDisplay : public QQuickItem +{ + Q_OBJECT + + Q_PROPERTY(CanvasConnection* canvas READ canvas WRITE setCanvas NOTIFY canvasChanged) + +public: + CanvasDisplay(QQuickItem* parent = nullptr); + ~CanvasDisplay(); + + CanvasConnection* canvas() const + { + return m_connection; + } + +signals: + + void canvasChanged(CanvasConnection* canvas); + +public slots: + +void setCanvas(CanvasConnection* canvas); + +protected: + void updatePolish() override; + +private slots: + void onConnectionStatusChanged(); + + void onConnectionUpdated(); + +private: + CanvasConnection* m_connection = nullptr; + std::unique_ptr m_rootElement; +}; + +#endif // CANVASDISPLAY_H diff --git a/utils/fgqcanvas/fgcanvas.pro b/utils/fgqcanvas/fgcanvas.pro index 379e9c4ee..68268ce6c 100644 --- a/utils/fgqcanvas/fgcanvas.pro +++ b/utils/fgqcanvas/fgcanvas.pro @@ -1,51 +1,67 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2016-11-30T22:34:11 -# -#------------------------------------------------- -QT += core gui websockets +QT += core gui gui-private quick websockets CONFIG += c++11 -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +!ios:!android +{ + greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + + SOURCES += canvastreemodel.cpp \ + elementdatamodel.cpp + + HEADERS += canvastreemodel.h \ + elementdatamodel.h + +} TARGET = fgqcanvas TEMPLATE = app - SOURCES += main.cpp\ - temporarywidget.cpp \ fgcanvasgroup.cpp \ fgcanvaselement.cpp \ fgcanvaspaintcontext.cpp \ localprop.cpp \ - fgcanvaswidget.cpp \ fgcanvaspath.cpp \ fgcanvastext.cpp \ fgqcanvasmap.cpp \ - canvastreemodel.cpp \ - fgqcanvasimage.cpp + fgqcanvasimage.cpp \ + fgqcanvasfontcache.cpp \ + fgqcanvasimageloader.cpp \ + canvasitem.cpp \ + canvasconnection.cpp \ + applicationcontroller.cpp \ + canvasdisplay.cpp -HEADERS += temporarywidget.h \ + +HEADERS += \ fgcanvasgroup.h \ fgcanvaselement.h \ fgcanvaspaintcontext.h \ localprop.h \ - fgcanvaswidget.h \ fgcanvaspath.h \ fgcanvastext.h \ fgqcanvasmap.h \ - canvastreemodel.h \ - fgqcanvasimage.h + fgqcanvasimage.h \ + canvasconnection.h \ + applicationcontroller.h \ + canvasdisplay.h \ + canvasitem.h \ + fgqcanvasfontcache.h \ + fgqcanvasimageloader.h -FORMS += temporarywidget.ui +RESOURCES += \ + fgqcanvas_resources.qrc + + +OTHER_FILES += \ + qml/* Q_XCODE_DEVELOPMENT_TEAM.name = DEVELOPMENT_TEAM Q_XCODE_DEVELOPMENT_TEAM.value = "James Turner" QMAKE_MAC_XCODE_SETTINGS += Q_XCODE_DEVELOPMENT_TEAM - ios { QMAKE_INFO_PLIST = ios/Info.plist } diff --git a/utils/fgqcanvas/fgcanvaselement.cpp b/utils/fgqcanvas/fgcanvaselement.cpp index d5b912d6e..ae1025884 100644 --- a/utils/fgqcanvas/fgcanvaselement.cpp +++ b/utils/fgqcanvas/fgcanvaselement.cpp @@ -92,6 +92,38 @@ FGCanvasElement::FGCanvasElement(FGCanvasGroup* pr, LocalProp* prop) : if (pr) { pr->markChildZIndicesDirty(); } + + requestPolish(); +} + + +void FGCanvasElement::requestPolish() +{ + _polishRequired = true; +} + +void FGCanvasElement::polish() +{ + if (!isVisible()) { + return; + } + + if (_clipDirty) { + parseCSSClip(_propertyRoot->value("clip", QVariant()).toByteArray()); + _clipDirty = false; + } + + if (quickItem()) { + quickItem()->setTransform(combinedTransform()); + } + + if (_styleDirty) { + _fillColor = parseColorValue(getCascadedStyle("fill")); + _styleDirty = false; + } + + doPolish(); + _polishRequired = false; } void FGCanvasElement::paint(FGCanvasPaintContext *context) const @@ -101,12 +133,6 @@ void FGCanvasElement::paint(FGCanvasPaintContext *context) const } QPainter* p = context->painter(); - - if (_clipDirty) { - parseCSSClip(_propertyRoot->value("clip", QVariant()).toByteArray()); - _clipDirty = false; - } - p->save(); @@ -131,14 +157,6 @@ void FGCanvasElement::paint(FGCanvasPaintContext *context) const QTransform combined = combinedTransform(); p->setTransform(combined, true /* combine */); - if (quickItem()) { - quickItem()->setTransform(combined); - } - - if (_styleDirty) { - _fillColor = parseColorValue(getCascadedStyle("fill")); - _styleDirty = false; - } if (!_fillColor.isValid()) { p->setBrush(Qt::NoBrush); @@ -160,6 +178,10 @@ void FGCanvasElement::doPaint(FGCanvasPaintContext* context) const Q_UNUSED(context); } +void FGCanvasElement::doPolish() +{ +} + QTransform FGCanvasElement::combinedTransform() const { if (_transformsDirty) { @@ -277,16 +299,20 @@ void FGCanvasElement::onCenterChanged(QVariant value) } else { _center.setY(value.toFloat()); } + + requestPolish(); } void FGCanvasElement::markTransformsDirty() { _transformsDirty = true; + requestPolish(); } void FGCanvasElement::markClipDirty() { _clipDirty = true; + requestPolish(); } float FGCanvasElement::parseCSSValue(QByteArray value) const @@ -376,6 +402,7 @@ QColor FGCanvasElement::parseColorValue(QVariant value) const void FGCanvasElement::markStyleDirty() { _styleDirty = true; + requestPolish(); // group will cascade } @@ -402,9 +429,10 @@ void FGCanvasElement::markZIndexDirty(QVariant value) void FGCanvasElement::onVisibleChanged(QVariant value) { _visible = value.toBool(); + requestPolish(); } -void FGCanvasElement::parseCSSClip(QByteArray value) const +void FGCanvasElement::parseCSSClip(QByteArray value) { if (value.isEmpty()) { _hasClip = false; @@ -432,4 +460,6 @@ void FGCanvasElement::parseCSSClip(QByteArray value) const _clipRect = QRectF(left, top, right - left, bottom - top); qDebug() << "final clip rect:" << _clipRect << "from" << value; _hasClip = true; + + requestPolish(); } diff --git a/utils/fgqcanvas/fgcanvaselement.h b/utils/fgqcanvas/fgcanvaselement.h index dee6164a1..a5f5bc699 100644 --- a/utils/fgqcanvas/fgcanvaselement.h +++ b/utils/fgqcanvas/fgcanvaselement.h @@ -57,8 +57,12 @@ public: virtual CanvasItem* createQuickItem(QQuickItem* parent); virtual CanvasItem* quickItem() const; + void requestPolish(); + + void polish(); protected: virtual void doPaint(FGCanvasPaintContext* context) const; + virtual void doPolish(); virtual bool onChildAdded(LocalProp* prop); virtual bool onChildRemoved(LocalProp* prop); @@ -88,6 +92,7 @@ private: private: friend class FGCanvasGroup; + bool _polishRequired = false; bool _visible = true; bool _highlighted = false; @@ -106,7 +111,7 @@ private: mutable bool _hasClip = false; mutable QRectF _clipRect; - void parseCSSClip(QByteArray value) const; + void parseCSSClip(QByteArray value); float parseCSSValue(QByteArray value) const; }; diff --git a/utils/fgqcanvas/fgcanvasgroup.cpp b/utils/fgqcanvas/fgcanvasgroup.cpp index 88a4c35b8..96f491030 100644 --- a/utils/fgqcanvas/fgcanvasgroup.cpp +++ b/utils/fgqcanvas/fgcanvasgroup.cpp @@ -79,18 +79,27 @@ CanvasItem *FGCanvasGroup::createQuickItem(QQuickItem *parent) void FGCanvasGroup::doPaint(FGCanvasPaintContext *context) const { - if (_zIndicesDirty) { - std::sort(_children.begin(), _children.end(), ChildOrderingFunction()); - _zIndicesDirty = false; + for (FGCanvasElement* element : _children) { + element->paint(context); } +} +void FGCanvasGroup::doPolish() +{ if (_cachedSymbolDirty) { qDebug() << _propertyRoot->path() << "should use symbol cache:" << _propertyRoot->value("symbol-type", QVariant()).toByteArray(); _cachedSymbolDirty = false; } + if (_zIndicesDirty) { + std::sort(_children.begin(), _children.end(), ChildOrderingFunction()); + _zIndicesDirty = false; + + qWarning() << Q_FUNC_INFO << "adjust z order of quick items"; + } + for (FGCanvasElement* element : _children) { - element->paint(context); + element->polish(); } } @@ -135,7 +144,6 @@ bool FGCanvasGroup::onChildAdded(LocalProp *prop) markChildZIndicesDirty(); if (_quick) { - qDebug() << "creating quick item for child"; _children.back()->createQuickItem(_quick); } diff --git a/utils/fgqcanvas/fgcanvasgroup.h b/utils/fgqcanvas/fgcanvasgroup.h index ca0953048..6a5211877 100644 --- a/utils/fgqcanvas/fgcanvasgroup.h +++ b/utils/fgqcanvas/fgcanvasgroup.h @@ -32,6 +32,8 @@ signals: protected: virtual void doPaint(FGCanvasPaintContext* context) const override; + void doPolish() override; + bool onChildAdded(LocalProp *prop) override; bool onChildRemoved(LocalProp *prop) override; @@ -45,6 +47,7 @@ private: mutable bool _zIndicesDirty = false; mutable bool _cachedSymbolDirty = false; + CanvasItem* _quick = nullptr; }; diff --git a/utils/fgqcanvas/fgcanvaspath.cpp b/utils/fgqcanvas/fgcanvaspath.cpp index 275f5a3d4..ab71db70e 100644 --- a/utils/fgqcanvas/fgcanvaspath.cpp +++ b/utils/fgqcanvas/fgcanvaspath.cpp @@ -49,6 +49,7 @@ public: : CanvasItem(parent) { setFlag(ItemHasContents); + qDebug() << Q_FUNC_INFO; } void setPath(QPainterPath pp) @@ -374,22 +375,6 @@ FGCanvasPath::FGCanvasPath(FGCanvasGroup* pr, LocalProp* prop) : void FGCanvasPath::doPaint(FGCanvasPaintContext *context) const { - if (_pathDirty) { - rebuildPath(); - if (_quickPath) { - _quickPath->setPath(_painterPath); - } - _pathDirty = false; - } - - if (_penDirty) { - rebuildPen(); - if (_quickPath) { - _quickPath->setStroke(_stroke); - } - _penDirty = false; - } - context->painter()->setPen(_stroke); switch (_paintType) { @@ -422,6 +407,26 @@ void FGCanvasPath::doPaint(FGCanvasPaintContext *context) const } +void FGCanvasPath::doPolish() +{ + if (_pathDirty) { + rebuildPath(); + if (_quickPath) { + _quickPath->setPath(_painterPath); + } + _pathDirty = false; + } + + if (_penDirty) { + rebuildPen(); + if (_quickPath) { + _quickPath->setStroke(_stroke); + } + _penDirty = false; + } + +} + void FGCanvasPath::markStyleDirty() { _penDirty = true; @@ -429,7 +434,6 @@ void FGCanvasPath::markStyleDirty() CanvasItem *FGCanvasPath::createQuickItem(QQuickItem *parent) { - qDebug() << Q_FUNC_INFO; _quickPath = new PathQuickItem(parent); _quickPath->setPath(_painterPath); _quickPath->setStroke(_stroke); @@ -444,11 +448,13 @@ CanvasItem *FGCanvasPath::quickItem() const void FGCanvasPath::markPathDirty() { _pathDirty = true; + requestPolish(); } void FGCanvasPath::markStrokeDirty() { _penDirty = true; + requestPolish(); } bool FGCanvasPath::onChildAdded(LocalProp *prop) diff --git a/utils/fgqcanvas/fgcanvaspath.h b/utils/fgqcanvas/fgcanvaspath.h index d2099f035..ea8d0ac30 100644 --- a/utils/fgqcanvas/fgcanvaspath.h +++ b/utils/fgqcanvas/fgcanvaspath.h @@ -33,6 +33,8 @@ public: protected: virtual void doPaint(FGCanvasPaintContext* context) const override; + void doPolish() override; + virtual void markStyleDirty() override; CanvasItem* createQuickItem(QQuickItem *parent) override; diff --git a/utils/fgqcanvas/fgcanvastext.cpp b/utils/fgqcanvas/fgcanvastext.cpp index 8d6c56a64..84b3678b9 100644 --- a/utils/fgqcanvas/fgcanvastext.cpp +++ b/utils/fgqcanvas/fgcanvastext.cpp @@ -55,17 +55,12 @@ CanvasItem *FGCanvasText::quickItem() const void FGCanvasText::setEngine(QQmlEngine *engine) { - static_textComponent = new QQmlComponent(engine, QUrl("text.qml")); + static_textComponent = new QQmlComponent(engine, QUrl("qrc:///qml/text.qml")); qDebug() << static_textComponent->errorString(); } void FGCanvasText::doPaint(FGCanvasPaintContext *context) const { - if (_fontDirty) { - rebuildFont(); - _fontDirty = false; - } - context->painter()->setFont(_font); context->painter()->setPen(fillColor()); context->painter()->setBrush(Qt::NoBrush); @@ -92,6 +87,14 @@ void FGCanvasText::doPaint(FGCanvasPaintContext *context) const context->painter()->drawText(rect, _alignment, _text); } +void FGCanvasText::doPolish() +{ + if (_fontDirty) { + rebuildFont(); + _fontDirty = false; + } +} + void FGCanvasText::markStyleDirty() { markFontDirty(); diff --git a/utils/fgqcanvas/fgcanvastext.h b/utils/fgqcanvas/fgcanvastext.h index 179f8177f..b55fa03ee 100644 --- a/utils/fgqcanvas/fgcanvastext.h +++ b/utils/fgqcanvas/fgcanvastext.h @@ -35,6 +35,7 @@ public: static void setEngine(QQmlEngine* engine); protected: virtual void doPaint(FGCanvasPaintContext* context) const override; + void doPolish() override; virtual void markStyleDirty() override; private: diff --git a/utils/fgqcanvas/fgqcanvas_resources.qrc b/utils/fgqcanvas/fgqcanvas_resources.qrc new file mode 100644 index 000000000..db3d38786 --- /dev/null +++ b/utils/fgqcanvas/fgqcanvas_resources.qrc @@ -0,0 +1,13 @@ + + + qml/mainMenu.qml + qml/Button.qml + qml/InputLine.qml + qml/checkerboard.png + qml/CanvasFrame.qml + qml/BrowsePanel.qml + qml/LoadSavePanel.qml + qml/image.qml + qml/text.qml + + diff --git a/utils/fgqcanvas/fgqcanvasfontcache.cpp b/utils/fgqcanvas/fgqcanvasfontcache.cpp index 85e6812d8..464dda70c 100644 --- a/utils/fgqcanvas/fgqcanvasfontcache.cpp +++ b/utils/fgqcanvas/fgqcanvasfontcache.cpp @@ -112,38 +112,48 @@ void FGQCanvasFontCache::onFontDownloadError(QNetworkReply::NetworkError) void FGQCanvasFontCache::lookupFile(QByteArray name) { QString path = QStandardPaths::locate(QStandardPaths::CacheLocation, name); - if (path.isEmpty()) { - QUrl url; - url.setScheme("http"); - url.setHost(m_hostName); - url.setPort(m_port); - url.setPath("/Fonts/" + name); - - Q_FOREACH (QNetworkReply* transfer, m_transfers) { - if (transfer->url() == url) { - return; // transfer already active - } - } - - qDebug() << "reqeusting font" << url; - QNetworkReply* reply = m_downloader->get(QNetworkRequest(url)); - reply->setProperty("font", name); - - connect(reply, &QNetworkReply::finished, this, &FGQCanvasFontCache::onFontDownloadFinished); - // connect(reply, &QNetworkReply::error, this, &FGQCanvasFontCache::onFontDownloadError); - - m_transfers.append(reply); - } else { + if (!path.isEmpty()) { qDebug() << "found font" << name << "at path" << path; int fontFamilyId = QFontDatabase::addApplicationFont(path); - QStringList families = QFontDatabase::applicationFontFamilies(fontFamilyId); - qDebug() << "families are:" << families; + if (fontFamilyId >= 0) { + QStringList families = QFontDatabase::applicationFontFamilies(fontFamilyId); + qDebug() << "families are:" << families; - // compute a QFont and cache - QFont font(families.front()); - m_cache.insert(name, font); + // compute a QFont and cache + QFont font(families.front()); + m_cache.insert(name, font); + return; + } else { + qWarning() << "Failed to load font into QFontDatabase:" << path; + } } + + if (m_hostName.isEmpty()) { + qWarning() << "host name not specified"; + return; + } + + QUrl url; + url.setScheme("http"); + url.setHost(m_hostName); + url.setPort(m_port); + url.setPath("/Fonts/" + name); + + Q_FOREACH (QNetworkReply* transfer, m_transfers) { + if (transfer->url() == url) { + return; // transfer already active + } + } + + qDebug() << "reqeusting font" << url; + QNetworkReply* reply = m_downloader->get(QNetworkRequest(url)); + reply->setProperty("font", name); + + connect(reply, &QNetworkReply::finished, this, &FGQCanvasFontCache::onFontDownloadFinished); + // connect(reply, &QNetworkReply::error, this, &FGQCanvasFontCache::onFontDownloadError); + + m_transfers.append(reply); } diff --git a/utils/fgqcanvas/main.cpp b/utils/fgqcanvas/main.cpp index d884d7499..6a79d5298 100644 --- a/utils/fgqcanvas/main.cpp +++ b/utils/fgqcanvas/main.cpp @@ -15,45 +15,52 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -#include "temporarywidget.h" - #include -#include -#include -#include #include +#include +#include +#include "fgcanvastext.h" +#include "fgqcanvasimage.h" #include "fgqcanvasfontcache.h" #include "fgqcanvasimageloader.h" #include "canvasitem.h" +#include "applicationcontroller.h" +#include "canvasdisplay.h" +#include "canvasconnection.h" int main(int argc, char *argv[]) { - QApplication a(argc, argv); a.setApplicationName("FGCanvas"); a.setOrganizationDomain("flightgear.org"); a.setOrganizationName("FlightGear"); - QNetworkAccessManager* downloader = new QNetworkAccessManager; + ApplicationController appController; + // load saved history on the app controller? - QNetworkDiskCache* cache = new QNetworkDiskCache; - cache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - downloader->setCache(cache); // takes ownership - - FGQCanvasFontCache::initialise(downloader); - FGQCanvasImageLoader::initialise(downloader); + FGQCanvasFontCache::initialise(appController.netAccess()); + FGQCanvasImageLoader::initialise(appController.netAccess()); qmlRegisterType("FlightGear", 1, 0, "CanvasItem"); + qmlRegisterType("FlightGear", 1, 0, "CanvasDisplay"); + qmlRegisterUncreatableType("FlightGear", 1, 0, "CanvasConnection", "Don't create me"); + qmlRegisterUncreatableType("FlightGear", 1, 0, "Application", "Can't create"); + QQuickView quickView; + quickView.resize(1024, 768); - TemporaryWidget w; - w.setNetworkAccess(downloader); - w.show(); + quickView.rootContext()->setContextProperty("_application", &appController); + + FGCanvasText::setEngine(quickView.engine()); + FGQCanvasImage::setEngine(quickView.engine()); + + quickView.setSource(QUrl("qrc:///qml/mainMenu.qml")); + quickView.setResizeMode(QQuickView::SizeRootObjectToView); + quickView.show(); int result = a.exec(); - delete downloader; return result; } diff --git a/utils/fgqcanvas/qml/BrowsePanel.qml b/utils/fgqcanvas/qml/BrowsePanel.qml new file mode 100644 index 000000000..d4e91c501 --- /dev/null +++ b/utils/fgqcanvas/qml/BrowsePanel.qml @@ -0,0 +1,114 @@ +import QtQuick 2.0 +import FlightGear 1.0 as FG + +Item { + Rectangle { + id: hostPanel + height: hostPanelContent.childrenRect.height + 16 + width: 300 + + anchors.top: parent.top + anchors.topMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + + border.color: "#9f9f9f" + border.width: 1 + color: "#5f5f5f" + opacity: 0.8 + + Column { + spacing: 8 + + id: hostPanelContent + width: parent.width - 30 + anchors.top: parent.top + anchors.topMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + + InputLine { + id: hostInput + width: parent.width + label: "Hostname" + text: _application.host + onEditingFinished: { + _application.host = text + portInput.forceActiveFocus(); + } + + KeyNavigation.tab: portInput + } + + InputLine { + id: portInput + width: parent.width + label: "Port" + text: _application.port + KeyNavigation.tab: queryButton + + onEditingFinished: { + _application.port = text + } + } + + Button { + id: queryButton + label: "Query" + enabled: _application.host != "" + visible: (_application.status == FG.Application.Idle) + + onClicked: { + _application.query(); + } + } + + } + } + + Rectangle { + id: canvasListPanel + border.color: "#9f9f9f" + border.width: 1 + color: "#5f5f5f" + opacity: 0.8 + anchors.top: hostPanel.bottom + anchors.topMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + width: 300 + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + + ListView { + id: canvasList + model: _application.canvases + visible: (_application.status == FG.Application.SuccessfulQuery) + width: parent.width - 30 + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + + delegate: Rectangle { + width: canvasLabel.implicitWidth + height: canvasLabel.implicitHeight + 20 + + color: "#3f3f3f" + + Text { + id: canvasLabel + text: modelData['name'] + + // todo - different color if we already have a connection? + color: "white" + anchors.verticalCenter: parent.verticalCenter + + + } + + MouseArea { + anchors.fill: parent + onClicked: { + _application.openCanvas(modelData['path']); + } + } + } + } + } +} diff --git a/utils/fgqcanvas/qml/Button.qml b/utils/fgqcanvas/qml/Button.qml new file mode 100644 index 000000000..c57146cd5 --- /dev/null +++ b/utils/fgqcanvas/qml/Button.qml @@ -0,0 +1,32 @@ +import QtQuick 2.0 + +Rectangle { + id: root + + property alias label: labelText.text + property bool enabled: true + + signal clicked + + border.width: 2 + border.color: enabled ? "orange" : "9f9f9f" + + color: "#3f3f3f" + implicitWidth: 100 + implicitHeight: 30 + + Text { + id: labelText + anchors.centerIn: parent + color: enabled ? "white" : "9f9f9f" + } + + MouseArea { + anchors.fill: parent + enabled: root.enabled + + onClicked: { + root.clicked(); + } + } +} diff --git a/utils/fgqcanvas/qml/CanvasFrame.qml b/utils/fgqcanvas/qml/CanvasFrame.qml new file mode 100644 index 000000000..272232c2f --- /dev/null +++ b/utils/fgqcanvas/qml/CanvasFrame.qml @@ -0,0 +1,116 @@ +import QtQuick 2.0 +import FlightGear 1.0 as FG + +Item { + id: root + property bool showDecorations: true + property alias canvas: canvasDisplay.canvas + + readonly property var centerPoint: Qt.point(width / 2, height / 2) + + Component.onCompleted: { + width = canvas.size.width + height = canvas.size.height + x = canvas.center.x - (width / 2) + y = canvas.center.y - (height / 2) + } + + function saveGeometry() + { + canvas.center = Qt.point(x + (width / 2), y + (height / 2)) + canvas.size = Qt.size(root.width / 2, root.height / 2); + } + + FG.CanvasDisplay { + id: canvasDisplay + anchors.fill: parent + + onCanvasChanged: { + root.width = canvas.size.width + root.height = canvas.size.height + + root.x = canvas.center.x - (root.width / 2) + root.y = canvas.center.y - (root.height / 2) + } + } + + Rectangle { + border.width: 1 + border.color: "orange" + color: "transparent" + anchors.centerIn: parent + width: parent.width + 2 + height: parent.height + 2 + + MouseArea { + anchors.fill: parent + + drag.target: root + + onReleased: { + root.saveGeometry(); + } + } + + Rectangle { + width: 32 + height: 32 + color: "orange" + opacity: 0.5 + anchors.right: parent.right + anchors.bottom: parent.bottom + + MouseArea { + anchors.fill: parent + + // resizing + + onPositionChanged: { + var rootPos = mapToItem(root, mouse.x, mouse.y); + var rootDiff = Qt.point(rootPos.x - root.centerPoint.x, + rootPos.y - root.centerPoint.y); + + root.width = rootDiff.x * 2; + root.height = rootDiff.y * 2; + root.x = canvas.center.x - (root.width / 2) + root.y = canvas.center.y - (root.height / 2) + } + + onReleased: { + saveGeometry(); + } + } + } + + Text { + color: "orange" + anchors.centerIn: parent + text: "Canvas" + } + + Text { + anchors.fill: parent + verticalAlignment: Text.AlignBottom + + function statusAsString(status) + { + switch (status) { + case FG.CanvasConnection.NotConnected: return "Not connected"; + case FG.CanvasConnection.Connecting: return "Connecting"; + case FG.CanvasConnection.Connected: return "Connected"; + case FG.CanvasConnection.Reconnecting: return "Re-connecting"; + case FG.CanvasConnection.Error: return "Error"; + } + } + + text: "WS: " + canvas.webSocketUrl + "\n" + + "Root:" + canvas.rootPath + "\n" + + "Status:" + statusAsString(canvas.status); + color: "white" + + } + } + + + +} diff --git a/utils/fgqcanvas/qml/InputLine.qml b/utils/fgqcanvas/qml/InputLine.qml new file mode 100644 index 000000000..b2a90b7b8 --- /dev/null +++ b/utils/fgqcanvas/qml/InputLine.qml @@ -0,0 +1,52 @@ +import QtQuick 2.2 + +Item { + id: root + + property alias text: input.text + property alias label: labelText.text + + signal editingFinished + + implicitHeight: 30 + implicitWidth: 200 + + Text { + id: labelText + + anchors.left: parent.left + anchors.right: inputFrame.left + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + id: inputFrame + border.width: 2 + border.color: input.focus ? "orange" : "#9f9f9f" + color: "#3f3f3f" + width: parent.width * 0.5 + anchors.right: parent.right + height: root.height + + TextInput { + id: input + + anchors { + left: parent.left + leftMargin: 8 + right: parent.right + rightMargin: 8 + verticalCenter: parent.verticalCenter + } + + + verticalAlignment: Text.AlignVCenter + + onEditingFinished: { + root.editingFinished(); + } + + color: "#9f9f9f" + } + } +} diff --git a/utils/fgqcanvas/qml/LoadSavePanel.qml b/utils/fgqcanvas/qml/LoadSavePanel.qml new file mode 100644 index 000000000..b6e02d640 --- /dev/null +++ b/utils/fgqcanvas/qml/LoadSavePanel.qml @@ -0,0 +1,104 @@ +import QtQuick 2.0 +import FlightGear 1.0 as FG + +Item { + + Rectangle { + id: savePanel + width: parent.width - 8 + + anchors.top: parent.top + anchors.topMargin: 8 + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + + anchors.horizontalCenter: parent.horizontalCenter + + border.color: "#9f9f9f" + border.width: 1 + color: "#5f5f5f" + opacity: 0.8 + + Column { + spacing: 8 + + id: savePanelContent + width: parent.width - 30 + anchors.top: parent.top + anchors.topMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + + InputLine { + id: saveTitleInput + width: parent.width + label: "Title" + + } + + Button { + id: saveButton + label: "Save" + enabled: (saveTitleInput.text != "") + + onClicked: { + _application.save(saveTitleInput.text); + } + } + } + + ListView { + id: savedList + model: _application.configs + width: parent.width - 30 + anchors.top: savePanelContent.bottom + anchors.topMargin: 8 + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + + delegate: Item { + width: parent.width + height:delegateFrame.height + 8 + + Rectangle { + id: delegateFrame + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + + height: configLabel.implicitHeight + 20 + + color: "#3f3f3f" + + Text { + id: configLabel + text: modelData['name'] + color: "white" + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 8 + } + + MouseArea { + anchors.fill: parent + onClicked: { + _application.restoreConfig(model.index) + } + } + + Button { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 8 + label: "X" + width: height + + onClicked: { + + } + } + } // of visible rect + } // of delegate item + + } + } // of frame rect +} diff --git a/utils/fgqcanvas/qml/checkerboard.png b/utils/fgqcanvas/qml/checkerboard.png new file mode 100644 index 000000000..679b04bcf Binary files /dev/null and b/utils/fgqcanvas/qml/checkerboard.png differ diff --git a/utils/fgqcanvas/image.qml b/utils/fgqcanvas/qml/image.qml similarity index 100% rename from utils/fgqcanvas/image.qml rename to utils/fgqcanvas/qml/image.qml diff --git a/utils/fgqcanvas/qml/mainMenu.qml b/utils/fgqcanvas/qml/mainMenu.qml new file mode 100644 index 000000000..a4de82d73 --- /dev/null +++ b/utils/fgqcanvas/qml/mainMenu.qml @@ -0,0 +1,58 @@ +import QtQuick 2.0 +import FlightGear 1.0 as FG + +Rectangle { + property bool __uiVisible: true + width: 1024 + height: 768 + color: "black" + + Image { + opacity: visible ? 0.5 : 0.0 + source: "qrc:///images/checkerboard" + fillMode: Image.Tile + anchors.fill: parent + visible: __uiVisible + Behavior on opacity { + NumberAnimation { duration: 400 } + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + __uiVisible = !__uiVisible; + } + } + + Repeater { + model: _application.activeCanvases + delegate: CanvasFrame { + id: display + canvas: modelData + } + } + + BrowsePanel { + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 400 + visible: __uiVisible + opacity: visible ? 1.0: 0.0 + Behavior on opacity { + NumberAnimation { duration: 400 } + } + } + + LoadSavePanel { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 400 + visible: __uiVisible + opacity: visible ? 1.0: 0.0 + Behavior on opacity { + NumberAnimation { duration: 400 } + } + } +} diff --git a/utils/fgqcanvas/text.qml b/utils/fgqcanvas/qml/text.qml similarity index 100% rename from utils/fgqcanvas/text.qml rename to utils/fgqcanvas/qml/text.qml