diff --git a/utils/fgqcanvas/CMakeLists.txt b/utils/fgqcanvas/CMakeLists.txt index e2438eb3e..888da12bc 100644 --- a/utils/fgqcanvas/CMakeLists.txt +++ b/utils/fgqcanvas/CMakeLists.txt @@ -32,6 +32,8 @@ set(SOURCES canvastreemodel.h fgqcanvasfontcache.cpp fgqcanvasfontcache.h + fgqcanvasimageloader.cpp + fgqcanvasimageloader.h ) qt5_wrap_ui(uic_sources temporarywidget.ui) diff --git a/utils/fgqcanvas/fgqcanvasfontcache.cpp b/utils/fgqcanvas/fgqcanvasfontcache.cpp index c120ec019..53b9bcf5e 100644 --- a/utils/fgqcanvas/fgqcanvasfontcache.cpp +++ b/utils/fgqcanvas/fgqcanvasfontcache.cpp @@ -52,6 +52,12 @@ FGQCanvasFontCache *FGQCanvasFontCache::instance() return s_instance; } +void FGQCanvasFontCache::setHost(QString hostName, int portNumber) +{ + m_hostName = hostName; + m_port = portNumber; +} + void FGQCanvasFontCache::onFontDownloadFinished() { QByteArray fontPath = sender()->property("font").toByteArray(); @@ -90,7 +96,11 @@ void FGQCanvasFontCache::lookupFile(QByteArray name) { QString path = QStandardPaths::locate(QStandardPaths::CacheLocation, name); if (path.isEmpty()) { - QUrl url = QUrl("http://localhost:8080/Fonts/" + name); + 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) { diff --git a/utils/fgqcanvas/fgqcanvasfontcache.h b/utils/fgqcanvas/fgqcanvasfontcache.h index c8fd39a60..7975f7c97 100644 --- a/utils/fgqcanvas/fgqcanvasfontcache.h +++ b/utils/fgqcanvas/fgqcanvasfontcache.h @@ -20,6 +20,7 @@ public: static void initialise(QNetworkAccessManager* nam); static FGQCanvasFontCache* instance(); + void setHost(QString hostName, int portNumber); signals: void fontLoaded(QByteArray name); @@ -30,6 +31,8 @@ private slots: private: QNetworkAccessManager* m_downloader; QHash m_cache; + QString m_hostName; + int m_port; void lookupFile(QByteArray name); diff --git a/utils/fgqcanvas/fgqcanvasimage.cpp b/utils/fgqcanvas/fgqcanvasimage.cpp index 6e028f5fb..b6e3daf79 100644 --- a/utils/fgqcanvas/fgqcanvasimage.cpp +++ b/utils/fgqcanvas/fgqcanvasimage.cpp @@ -5,6 +5,7 @@ #include "fgcanvaspaintcontext.h" #include "localprop.h" +#include "fgqcanvasimageloader.h" FGQCanvasImage::FGQCanvasImage(FGCanvasGroup* pr, LocalProp* prop) : FGCanvasElement(pr, prop) @@ -45,10 +46,31 @@ void FGQCanvasImage::markImageDirty() void FGQCanvasImage::rebuildImage() const { - qDebug() << "source" << _propertyRoot->value("source", QString()); - qDebug() << "src" << _propertyRoot->value("src", QString()); - qDebug() << "file" << _propertyRoot->value("file", QString()); + QByteArray file = _propertyRoot->value("file", QByteArray()).toByteArray(); + if (!file.isEmpty()) { + _image = FGQCanvasImageLoader::instance()->getImage(file); + + + if (_image.isNull()) { + // get notified when the image loads + FGQCanvasImageLoader::instance()->connectToImageLoaded(file, + const_cast(this), + SLOT(markImageDirty())); + } else { + qDebug() << "have image" << _image.size(); + } + + } else { + qDebug() << "source" << _propertyRoot->value("source", QString()); + qDebug() << "src" << _propertyRoot->value("src", QString()); + } + + _destSize = QSizeF(_propertyRoot->value("size[0]", 0.0).toFloat(), + _propertyRoot->value("size[1]", 0.0).toFloat()); + qDebug() << "dest-size" << _destSize; + + _imageDirty = false; } void FGQCanvasImage::markStyleDirty() diff --git a/utils/fgqcanvas/fgqcanvasimage.h b/utils/fgqcanvas/fgqcanvasimage.h index f52f617ef..fe3471bfe 100644 --- a/utils/fgqcanvas/fgqcanvasimage.h +++ b/utils/fgqcanvas/fgqcanvasimage.h @@ -7,6 +7,7 @@ class FGQCanvasImage : public FGCanvasElement { + Q_OBJECT public: FGQCanvasImage(FGCanvasGroup* pr, LocalProp* prop); @@ -15,12 +16,15 @@ protected: virtual void markStyleDirty() override; +private slots: + void markImageDirty(); + private: bool onChildAdded(LocalProp *prop) override; void rebuildImage() const; - void markImageDirty(); + private: mutable bool _imageDirty; mutable QPixmap _image; diff --git a/utils/fgqcanvas/fgqcanvasimageloader.cpp b/utils/fgqcanvas/fgqcanvasimageloader.cpp new file mode 100644 index 000000000..0fce49b9f --- /dev/null +++ b/utils/fgqcanvas/fgqcanvasimageloader.cpp @@ -0,0 +1,108 @@ +#include "fgqcanvasimageloader.h" + +#include +#include + +static FGQCanvasImageLoader* static_instance = nullptr; + +class TransferSignalHolder : public QObject +{ + Q_OBJECT +public: + TransferSignalHolder(QObject* pr) : QObject(pr) { } +signals: + void trigger(); +}; + +FGQCanvasImageLoader::FGQCanvasImageLoader(QNetworkAccessManager* dl) + : m_downloader(dl) +{ +} + +void FGQCanvasImageLoader::onDownloadFinished() +{ + QNetworkReply* reply = qobject_cast(sender()); + + QPixmap pm; + if (!pm.loadFromData(reply->readAll())) { + qWarning() << "image loading failed"; + } else { + qDebug() << "did download:" << reply->property("image").toByteArray(); + m_cache.insert(reply->property("image").toByteArray(), pm); + + TransferSignalHolder* signalHolder = reply->findChild("holder"); + if (signalHolder) { + qDebug() << "triggering image updates"; + signalHolder->trigger(); + } + } + + m_transfers.removeOne(reply); + reply->deleteLater(); +} + +FGQCanvasImageLoader *FGQCanvasImageLoader::instance() +{ + return static_instance; +} + +void FGQCanvasImageLoader::initialise(QNetworkAccessManager *dl) +{ + Q_ASSERT(static_instance == nullptr); + static_instance = new FGQCanvasImageLoader(dl); +} + +void FGQCanvasImageLoader::setHost(QString hostName, int portNumber) +{ + m_hostName = hostName; + m_port = portNumber; +} + +QPixmap FGQCanvasImageLoader::getImage(const QByteArray &imagePath) +{ + if (m_cache.contains(imagePath)) { + // cached, easy + return m_cache.value(imagePath); + } + + QUrl url; + url.setScheme("http"); + url.setHost(m_hostName); + url.setPort(m_port); + url.setPath("/aircraft-dir/" + imagePath); + + Q_FOREACH (QNetworkReply* transfer, m_transfers) { + if (transfer->url() == url) { + return QPixmap(); // transfer already active + } + } + + qDebug() << "requesting image" << url; + QNetworkReply* reply = m_downloader->get(QNetworkRequest(url)); + reply->setProperty("image", imagePath); + + connect(reply, &QNetworkReply::finished, this, &FGQCanvasImageLoader::onDownloadFinished); + m_transfers.append(reply); + + return QPixmap(); +} + +void FGQCanvasImageLoader::connectToImageLoaded(const QByteArray &imagePath, QObject *receiver, const char *slot) +{ + Q_FOREACH (QNetworkReply* transfer, m_transfers) { + if (transfer->property("image").toByteArray() == imagePath) { + QObject* signalHolder = transfer->findChild("holder"); + if (!signalHolder) { + signalHolder = new TransferSignalHolder(transfer); + signalHolder->setObjectName("holder"); + } + + connect(signalHolder, SIGNAL(trigger()), receiver, slot); + return; + } + } + + qWarning() << "no transfer active for" << imagePath; +} + +#include "fgqcanvasimageloader.moc" diff --git a/utils/fgqcanvas/fgqcanvasimageloader.h b/utils/fgqcanvas/fgqcanvasimageloader.h new file mode 100644 index 000000000..1ffa7d5b4 --- /dev/null +++ b/utils/fgqcanvas/fgqcanvasimageloader.h @@ -0,0 +1,49 @@ +#ifndef FGQCANVASIMAGELOADER_H +#define FGQCANVASIMAGELOADER_H + +#include +#include +#include +#include + +class QNetworkAccessManager; + +class FGQCanvasImageLoader : public QObject +{ + Q_OBJECT +public: + static FGQCanvasImageLoader* instance(); + + static void initialise(QNetworkAccessManager* dl); + + void setHost(QString hostName, int portNumber); + + QPixmap getImage(const QByteArray& imagePath); + + /** + * @brief connectToImageLoaded - allow images to discover when they are loaded + * @param imagePath - FGFS host relative path as found in the canvas (will be resolved + * against aircraft-data, and potentially other places) + * @param receiver - normal connect() receiver object + * @param slot - slot macro + */ + void connectToImageLoaded(const QByteArray& imagePath, QObject* receiver, const char* slot); +signals: + + +private: + explicit FGQCanvasImageLoader(QNetworkAccessManager* dl); + + void onDownloadFinished(); + +private: + QNetworkAccessManager* m_downloader; + QString m_hostName; + int m_port; + + QMap m_cache; + QList m_transfers; + +}; + +#endif // FGQCANVASIMAGELOADER_H diff --git a/utils/fgqcanvas/main.cpp b/utils/fgqcanvas/main.cpp index c8e5e52bc..fdfb1b1c8 100644 --- a/utils/fgqcanvas/main.cpp +++ b/utils/fgqcanvas/main.cpp @@ -2,8 +2,11 @@ #include #include +#include +#include #include "fgqcanvasfontcache.h" +#include "fgqcanvasimageloader.h" int main(int argc, char *argv[]) { @@ -15,10 +18,18 @@ int main(int argc, char *argv[]) QNetworkAccessManager* downloader = new QNetworkAccessManager; + QNetworkDiskCache* cache = new QNetworkDiskCache; + cache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + downloader->setCache(cache); // takes ownership + FGQCanvasFontCache::initialise(downloader); + FGQCanvasImageLoader::initialise(downloader); TemporaryWidget w; w.show(); - return a.exec(); + int result = a.exec(); + delete downloader; + + return result; } diff --git a/utils/fgqcanvas/temporarywidget.cpp b/utils/fgqcanvas/temporarywidget.cpp index ec2ec27f3..8b5c0917e 100644 --- a/utils/fgqcanvas/temporarywidget.cpp +++ b/utils/fgqcanvas/temporarywidget.cpp @@ -7,8 +7,8 @@ #include #include -// ws://localhost:8080/PropertyTreeMirror/canvas/by-index/texture[3] - +#include "fgqcanvasfontcache.h" +#include "fgqcanvasimageloader.h" #include "canvastreemodel.h" #include "localprop.h" @@ -24,24 +24,21 @@ TemporaryWidget::TemporaryWidget(QWidget *parent) : TemporaryWidget::~TemporaryWidget() { + disconnect(&m_webSocket, &QWebSocket::disconnected, this, &TemporaryWidget::onSocketClosed); + m_webSocket.close(); delete ui; } void TemporaryWidget::onStartConnect() { - QString wsUrl = ui->socketURL->text(); - if (!wsUrl.startsWith("ws")) { - qWarning() << "Not a web-socket URL" << wsUrl; - return; - } + QUrl wsUrl; + wsUrl.setScheme("ws"); + wsUrl.setHost(ui->hostName->text()); + wsUrl.setPort(ui->portEdit->text().toInt()); // string clean up, to ensure our root path has a leading slash but // no trailing slash QString propPath = ui->propertyPath->text(); - if (wsUrl.endsWith('/')) { - wsUrl.truncate(wsUrl.size() - 1); - } - QString rootPath = propPath; if (!propPath.startsWith('/')) { rootPath = '/' + propPath; @@ -51,7 +48,7 @@ void TemporaryWidget::onStartConnect() rootPath.chop(1); } - QUrl url = QUrl(wsUrl + rootPath); + wsUrl.setPath("/PropertyTreeMirror" + rootPath); rootPropertyPath = rootPath.toUtf8(); connect(&m_webSocket, &QWebSocket::connected, this, &TemporaryWidget::onConnected); @@ -59,9 +56,8 @@ void TemporaryWidget::onStartConnect() saveSettings(); - qDebug() << "starting connection to:" << url; - m_webSocket.open(url); - m_webSocket.sendTextMessage("nothing"); // forces Mongoose to respond quicker + qDebug() << "starting connection to:" << wsUrl; + m_webSocket.open(wsUrl); } void TemporaryWidget::onConnected() @@ -78,6 +74,12 @@ void TemporaryWidget::onConnected() m_canvasModel = new CanvasTreeModel(ui->canvas->rootElement()); ui->treeView->setModel(m_canvasModel); + + FGQCanvasFontCache::instance()->setHost(ui->hostName->text(), + ui->portEdit->text().toInt()); + FGQCanvasImageLoader::instance()->setHost(ui->hostName->text(), + ui->portEdit->text().toInt()); + m_webSocket.sendTextMessage("nonsense"); } void TemporaryWidget::onTextMessageReceived(QString message) @@ -158,14 +160,16 @@ void TemporaryWidget::onSocketClosed() void TemporaryWidget::saveSettings() { QSettings settings; - settings.setValue("ws-host", ui->socketURL->text()); + settings.setValue("ws-host", ui->hostName->text()); + settings.setValue("ws-port", ui->portEdit->text()); settings.setValue("prop-path", ui->propertyPath->text()); } void TemporaryWidget::restoreSettings() { QSettings settings; - ui->socketURL->setText(settings.value("ws-host").toString()); + ui->hostName->setText(settings.value("ws-host").toString()); + ui->portEdit->setText(settings.value("ws-port").toString()); ui->propertyPath->setText(settings.value("prop-path").toString()); } diff --git a/utils/fgqcanvas/temporarywidget.ui b/utils/fgqcanvas/temporarywidget.ui index cf8d1d701..36886c9ed 100644 --- a/utils/fgqcanvas/temporarywidget.ui +++ b/utils/fgqcanvas/temporarywidget.ui @@ -23,7 +23,7 @@ Connect - + @@ -34,15 +34,12 @@ - Enter a web-socket URL: (eg 'ws://localhost:8080/PropertyTreeMirror/') + Enter FlightGear host-name and port - - - - + @@ -51,9 +48,15 @@ - + + + + + + +