Remote-canvas Image and font caches working again
Reconnecting and disconnecting also starting to work more smoothly
This commit is contained in:
parent
67ab1c7162
commit
4bae38f994
22 changed files with 290 additions and 112 deletions
|
@ -111,7 +111,9 @@ void ApplicationController::rebuildConfigData()
|
||||||
|
|
||||||
void ApplicationController::query()
|
void ApplicationController::query()
|
||||||
{
|
{
|
||||||
qWarning() << Q_FUNC_INFO << m_host << m_port;
|
if (m_query) {
|
||||||
|
cancelQuery();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_host.isEmpty() || (m_port == 0))
|
if (m_host.isEmpty() || (m_port == 0))
|
||||||
return;
|
return;
|
||||||
|
@ -123,13 +125,31 @@ void ApplicationController::query()
|
||||||
queryUrl.setPath("/json/canvas/by-index");
|
queryUrl.setPath("/json/canvas/by-index");
|
||||||
queryUrl.setQuery("d=2");
|
queryUrl.setQuery("d=2");
|
||||||
|
|
||||||
QNetworkReply* reply = m_netAccess->get(QNetworkRequest(queryUrl));
|
m_query = m_netAccess->get(QNetworkRequest(queryUrl));
|
||||||
connect(reply, &QNetworkReply::finished,
|
connect(m_query, &QNetworkReply::finished,
|
||||||
this, &ApplicationController::onFinishedGetCanvasList);
|
this, &ApplicationController::onFinishedGetCanvasList);
|
||||||
|
|
||||||
setStatus(Querying);
|
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)
|
void ApplicationController::restoreConfig(int index)
|
||||||
{
|
{
|
||||||
QString path = m_configs.at(index).toMap().value("path").toString();
|
QString path = m_configs.at(index).toMap().value("path").toString();
|
||||||
|
@ -215,7 +235,8 @@ QJsonObject jsonPropNodeFindChild(QJsonObject obj, QByteArray name)
|
||||||
void ApplicationController::onFinishedGetCanvasList()
|
void ApplicationController::onFinishedGetCanvasList()
|
||||||
{
|
{
|
||||||
m_canvases.clear();
|
m_canvases.clear();
|
||||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
QNetworkReply* reply = m_query;
|
||||||
|
m_query = nullptr;
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
@ -272,8 +293,7 @@ QByteArray ApplicationController::saveState(QString name) const
|
||||||
|
|
||||||
void ApplicationController::restoreState(QByteArray bytes)
|
void ApplicationController::restoreState(QByteArray bytes)
|
||||||
{
|
{
|
||||||
qDeleteAll(m_activeCanvases);
|
clearConnections();
|
||||||
m_activeCanvases.clear();
|
|
||||||
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(bytes);
|
||||||
QJsonObject json = jsonDoc.object();
|
QJsonObject json = jsonDoc.object();
|
||||||
|
@ -291,3 +311,12 @@ void ApplicationController::restoreState(QByteArray bytes)
|
||||||
|
|
||||||
emit activeCanvasesChanged();
|
emit activeCanvasesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplicationController::clearConnections()
|
||||||
|
{
|
||||||
|
Q_FOREACH(auto c, m_activeCanvases) {
|
||||||
|
c->deleteLater();
|
||||||
|
}
|
||||||
|
m_activeCanvases.clear();
|
||||||
|
emit activeCanvasesChanged();
|
||||||
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void save(QString configName);
|
Q_INVOKABLE void save(QString configName);
|
||||||
Q_INVOKABLE void query();
|
Q_INVOKABLE void query();
|
||||||
|
Q_INVOKABLE void cancelQuery();
|
||||||
|
Q_INVOKABLE void clearQuery();
|
||||||
|
|
||||||
Q_INVOKABLE void restoreConfig(int index);
|
Q_INVOKABLE void restoreConfig(int index);
|
||||||
|
|
||||||
|
@ -107,6 +109,7 @@ private:
|
||||||
void setStatus(Status newStatus);
|
void setStatus(Status newStatus);
|
||||||
|
|
||||||
void rebuildConfigData();
|
void rebuildConfigData();
|
||||||
|
void clearConnections();
|
||||||
|
|
||||||
QByteArray saveState(QString name) const;
|
QByteArray saveState(QString name) const;
|
||||||
void restoreState(QByteArray bytes);
|
void restoreState(QByteArray bytes);
|
||||||
|
@ -118,6 +121,7 @@ private:
|
||||||
QNetworkAccessManager* m_netAccess;
|
QNetworkAccessManager* m_netAccess;
|
||||||
Status m_status;
|
Status m_status;
|
||||||
QVariantList m_configs;
|
QVariantList m_configs;
|
||||||
|
QNetworkReply* m_query = nullptr;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
|
||||||
#include "localprop.h"
|
#include "localprop.h"
|
||||||
|
#include "fgqcanvasfontcache.h"
|
||||||
|
#include "fgqcanvasimageloader.h"
|
||||||
|
|
||||||
CanvasConnection::CanvasConnection(QObject *parent) : QObject(parent)
|
CanvasConnection::CanvasConnection(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
|
@ -41,6 +43,7 @@ CanvasConnection::CanvasConnection(QObject *parent) : QObject(parent)
|
||||||
|
|
||||||
CanvasConnection::~CanvasConnection()
|
CanvasConnection::~CanvasConnection()
|
||||||
{
|
{
|
||||||
|
qDebug() << Q_FUNC_INFO;
|
||||||
disconnect(&m_webSocket, &QWebSocket::disconnected,
|
disconnect(&m_webSocket, &QWebSocket::disconnected,
|
||||||
this, &CanvasConnection::onWebSocketClosed);
|
this, &CanvasConnection::onWebSocketClosed);
|
||||||
m_webSocket.close();
|
m_webSocket.close();
|
||||||
|
@ -145,19 +148,31 @@ LocalProp *CanvasConnection::propertyRoot() const
|
||||||
return m_localPropertyRoot.get();
|
return m_localPropertyRoot.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FGQCanvasImageLoader *CanvasConnection::imageLoader() const
|
||||||
|
{
|
||||||
|
if (!m_imageLoader) {
|
||||||
|
m_imageLoader = new FGQCanvasImageLoader(m_netAccess, const_cast<CanvasConnection*>(this));
|
||||||
|
m_imageLoader->setHost(m_webSocketUrl.host(),
|
||||||
|
m_webSocketUrl.port());
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_imageLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_fontCache;
|
||||||
|
}
|
||||||
|
|
||||||
void CanvasConnection::onWebSocketConnected()
|
void CanvasConnection::onWebSocketConnected()
|
||||||
{
|
{
|
||||||
m_localPropertyRoot.reset(new LocalProp{nullptr, NameIndexTuple("")});
|
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);
|
setStatus(Connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
class LocalProp;
|
class LocalProp;
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
|
class FGQCanvasImageLoader;
|
||||||
|
class FGQCanvasFontCache;
|
||||||
|
|
||||||
class CanvasConnection : public QObject
|
class CanvasConnection : public QObject
|
||||||
{
|
{
|
||||||
|
@ -85,6 +87,10 @@ public:
|
||||||
return QString::fromUtf8(m_rootPropertyPath);
|
return QString::fromUtf8(m_rootPropertyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FGQCanvasImageLoader* imageLoader() const;
|
||||||
|
|
||||||
|
FGQCanvasFontCache* fontCache() const;
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void reconnect();
|
void reconnect();
|
||||||
|
|
||||||
|
@ -126,6 +132,9 @@ private:
|
||||||
QHash<int, LocalProp*> idPropertyDict;
|
QHash<int, LocalProp*> idPropertyDict;
|
||||||
Status m_status = NotConnected;
|
Status m_status = NotConnected;
|
||||||
QString m_rootPath;
|
QString m_rootPath;
|
||||||
|
|
||||||
|
mutable FGQCanvasImageLoader* m_imageLoader = nullptr;
|
||||||
|
mutable FGQCanvasFontCache* m_fontCache = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CANVASCONNECTION_H
|
#endif // CANVASCONNECTION_H
|
||||||
|
|
|
@ -18,16 +18,17 @@
|
||||||
#include "canvasdisplay.h"
|
#include "canvasdisplay.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
#include "canvasconnection.h"
|
#include "canvasconnection.h"
|
||||||
#include "fgcanvasgroup.h"
|
#include "fgcanvasgroup.h"
|
||||||
#include "fgcanvaspaintcontext.h"
|
#include "fgcanvaspaintcontext.h"
|
||||||
#include "canvasitem.h"
|
#include "canvasitem.h"
|
||||||
|
#include "localprop.h"
|
||||||
|
|
||||||
CanvasDisplay::CanvasDisplay(QQuickItem* parent) :
|
CanvasDisplay::CanvasDisplay(QQuickItem* parent) :
|
||||||
QQuickItem(parent)
|
QQuickItem(parent)
|
||||||
{
|
{
|
||||||
setSize(QSizeF(400, 400));
|
|
||||||
qDebug() << "created a canvas display";
|
qDebug() << "created a canvas display";
|
||||||
|
|
||||||
setFlag(ItemHasContents);
|
setFlag(ItemHasContents);
|
||||||
|
@ -35,7 +36,8 @@ CanvasDisplay::CanvasDisplay(QQuickItem* parent) :
|
||||||
|
|
||||||
CanvasDisplay::~CanvasDisplay()
|
CanvasDisplay::~CanvasDisplay()
|
||||||
{
|
{
|
||||||
|
qDebug() << Q_FUNC_INFO << "connection is" << m_connection;
|
||||||
|
delete m_rootItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CanvasDisplay::updatePolish()
|
void CanvasDisplay::updatePolish()
|
||||||
|
@ -43,6 +45,12 @@ void CanvasDisplay::updatePolish()
|
||||||
m_rootElement->polish();
|
m_rootElement->polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CanvasDisplay::geometryChanged(const QRectF &newGeometry, const QRectF &)
|
||||||
|
{
|
||||||
|
Q_UNUSED(newGeometry);
|
||||||
|
recomputeScaling();
|
||||||
|
}
|
||||||
|
|
||||||
void CanvasDisplay::setCanvas(CanvasConnection *canvas)
|
void CanvasDisplay::setCanvas(CanvasConnection *canvas)
|
||||||
{
|
{
|
||||||
if (m_connection == canvas)
|
if (m_connection == canvas)
|
||||||
|
@ -50,31 +58,81 @@ void CanvasDisplay::setCanvas(CanvasConnection *canvas)
|
||||||
|
|
||||||
if (m_connection) {
|
if (m_connection) {
|
||||||
disconnect(m_connection, nullptr, this, nullptr);
|
disconnect(m_connection, nullptr, this, nullptr);
|
||||||
|
|
||||||
|
|
||||||
|
qDebug() << "deleting items";
|
||||||
|
delete m_rootItem;
|
||||||
|
|
||||||
|
qDebug() << "deleting elements";
|
||||||
|
m_rootElement.reset();
|
||||||
|
|
||||||
|
qDebug() << "done";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connection = canvas;
|
m_connection = canvas;
|
||||||
emit canvasChanged(m_connection);
|
emit canvasChanged(m_connection);
|
||||||
|
|
||||||
// delete existing children
|
if (m_connection) {
|
||||||
|
// delete existing children
|
||||||
|
|
||||||
connect(m_connection, &CanvasConnection::statusChanged,
|
connect(m_connection, &QObject::destroyed,
|
||||||
this, &CanvasDisplay::onConnectionStatusChanged);
|
this, &CanvasDisplay::onConnectionDestroyed);
|
||||||
connect(m_connection, &CanvasConnection::updated,
|
connect(m_connection, &CanvasConnection::statusChanged,
|
||||||
this, &CanvasDisplay::onConnectionUpdated);
|
this, &CanvasDisplay::onConnectionStatusChanged);
|
||||||
|
connect(m_connection, &CanvasConnection::updated,
|
||||||
|
this, &CanvasDisplay::onConnectionUpdated);
|
||||||
|
|
||||||
|
onConnectionStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CanvasDisplay::onConnectionDestroyed()
|
||||||
|
{
|
||||||
|
m_connection = nullptr;
|
||||||
|
emit canvasChanged(m_connection);
|
||||||
|
|
||||||
|
m_rootElement.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CanvasDisplay::onConnectionStatusChanged()
|
void CanvasDisplay::onConnectionStatusChanged()
|
||||||
{
|
{
|
||||||
if (m_connection->status() == CanvasConnection::Connected) {
|
if (m_connection->status() == CanvasConnection::Connected) {
|
||||||
m_rootElement.reset(new FGCanvasGroup(nullptr, m_connection->propertyRoot()));
|
m_rootElement.reset(new FGCanvasGroup(nullptr, m_connection->propertyRoot()));
|
||||||
auto qq = m_rootElement->createQuickItem(this);
|
// this is important to elements can discover their connection
|
||||||
qq->setSize(QSizeF(400, 400));
|
// by walking their parent chain
|
||||||
|
m_rootElement->setParent(m_connection);
|
||||||
|
|
||||||
|
connect(m_rootElement.get(), &FGCanvasGroup::canvasSizeChanged,
|
||||||
|
this, &CanvasDisplay::onCanvasSizeChanged);
|
||||||
|
|
||||||
|
m_rootItem = m_rootElement->createQuickItem(this);
|
||||||
|
onCanvasSizeChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CanvasDisplay::onConnectionUpdated()
|
void CanvasDisplay::onConnectionUpdated()
|
||||||
{
|
{
|
||||||
m_rootElement->polish();
|
if (m_rootElement) {
|
||||||
update();
|
m_rootElement->polish();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CanvasDisplay::onCanvasSizeChanged()
|
||||||
|
{
|
||||||
|
m_sourceSize = QSizeF(m_connection->propertyRoot()->value("size", 400).toDouble(),
|
||||||
|
m_connection->propertyRoot()->value("size[1]", 400).toDouble());
|
||||||
|
|
||||||
|
recomputeScaling();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CanvasDisplay::recomputeScaling()
|
||||||
|
{
|
||||||
|
double xScaleFactor = width() / m_sourceSize.width();
|
||||||
|
double yScaleFactor = height() / m_sourceSize.height();
|
||||||
|
|
||||||
|
double finalScaleFactor = std::min(xScaleFactor, yScaleFactor);
|
||||||
|
|
||||||
|
setScale(finalScaleFactor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
|
||||||
class CanvasConnection;
|
class CanvasConnection;
|
||||||
class FGCanvasElement;
|
class FGCanvasGroup;
|
||||||
|
class QQuickItem;
|
||||||
|
|
||||||
class CanvasDisplay : public QQuickItem
|
class CanvasDisplay : public QQuickItem
|
||||||
{
|
{
|
||||||
|
@ -51,14 +52,23 @@ void setCanvas(CanvasConnection* canvas);
|
||||||
protected:
|
protected:
|
||||||
void updatePolish() override;
|
void updatePolish() override;
|
||||||
|
|
||||||
|
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onConnectionStatusChanged();
|
void onConnectionStatusChanged();
|
||||||
|
|
||||||
void onConnectionUpdated();
|
void onConnectionUpdated();
|
||||||
|
|
||||||
|
void onCanvasSizeChanged();
|
||||||
|
void onConnectionDestroyed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void recomputeScaling();
|
||||||
|
|
||||||
CanvasConnection* m_connection = nullptr;
|
CanvasConnection* m_connection = nullptr;
|
||||||
std::unique_ptr<FGCanvasElement> m_rootElement;
|
std::unique_ptr<FGCanvasGroup> m_rootElement;
|
||||||
|
QQuickItem* m_rootItem = nullptr;
|
||||||
|
QSizeF m_sourceSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CANVASDISPLAY_H
|
#endif // CANVASDISPLAY_H
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "fgcanvaspaintcontext.h"
|
#include "fgcanvaspaintcontext.h"
|
||||||
#include "fgcanvasgroup.h"
|
#include "fgcanvasgroup.h"
|
||||||
#include "canvasitem.h"
|
#include "canvasitem.h"
|
||||||
|
#include "canvasconnection.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
@ -85,7 +86,10 @@ FGCanvasElement::FGCanvasElement(FGCanvasGroup* pr, LocalProp* prop) :
|
||||||
_parent(pr)
|
_parent(pr)
|
||||||
{
|
{
|
||||||
connect(prop->getOrCreateWithPath("visible"), &LocalProp::valueChanged,
|
connect(prop->getOrCreateWithPath("visible"), &LocalProp::valueChanged,
|
||||||
[this](QVariant val) { _visible = val.toBool(); });
|
[this](QVariant val) {
|
||||||
|
_visible = val.toBool();
|
||||||
|
requestPolish();
|
||||||
|
});
|
||||||
connect(prop, &LocalProp::childAdded, this, &FGCanvasElement::onChildAdded);
|
connect(prop, &LocalProp::childAdded, this, &FGCanvasElement::onChildAdded);
|
||||||
connect(prop, &LocalProp::childRemoved, this, &FGCanvasElement::onChildRemoved);
|
connect(prop, &LocalProp::childRemoved, this, &FGCanvasElement::onChildRemoved);
|
||||||
|
|
||||||
|
@ -104,7 +108,13 @@ void FGCanvasElement::requestPolish()
|
||||||
|
|
||||||
void FGCanvasElement::polish()
|
void FGCanvasElement::polish()
|
||||||
{
|
{
|
||||||
if (!isVisible()) {
|
bool vis = isVisible();
|
||||||
|
auto qq = quickItem();
|
||||||
|
if (qq && (qq->isVisible() != vis)) {
|
||||||
|
qq->setVisible(vis);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vis) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +123,8 @@ void FGCanvasElement::polish()
|
||||||
_clipDirty = false;
|
_clipDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quickItem()) {
|
if (qq) {
|
||||||
quickItem()->setTransform(combinedTransform());
|
qq->setTransform(combinedTransform());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_styleDirty) {
|
if (_styleDirty) {
|
||||||
|
@ -218,6 +228,13 @@ const FGCanvasGroup *FGCanvasElement::parentGroup() const
|
||||||
return _parent;
|
return _parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CanvasConnection *FGCanvasElement::connection() const
|
||||||
|
{
|
||||||
|
if (_parent)
|
||||||
|
return _parent->connection();
|
||||||
|
return qobject_cast<CanvasConnection*>(parent());
|
||||||
|
}
|
||||||
|
|
||||||
bool FGCanvasElement::onChildAdded(LocalProp *prop)
|
bool FGCanvasElement::onChildAdded(LocalProp *prop)
|
||||||
{
|
{
|
||||||
const QByteArray nm = prop->name();
|
const QByteArray nm = prop->name();
|
||||||
|
|
|
@ -30,6 +30,7 @@ class FGCanvasPaintContext;
|
||||||
class FGCanvasGroup;
|
class FGCanvasGroup;
|
||||||
class CanvasItem;
|
class CanvasItem;
|
||||||
class QQuickItem;
|
class QQuickItem;
|
||||||
|
class CanvasConnection;
|
||||||
|
|
||||||
class FGCanvasElement : public QObject
|
class FGCanvasElement : public QObject
|
||||||
{
|
{
|
||||||
|
@ -47,6 +48,8 @@ public:
|
||||||
|
|
||||||
const FGCanvasGroup* parentGroup() const;
|
const FGCanvasGroup* parentGroup() const;
|
||||||
|
|
||||||
|
CanvasConnection* connection() const;
|
||||||
|
|
||||||
static bool isStyleProperty(QByteArray name);
|
static bool isStyleProperty(QByteArray name);
|
||||||
|
|
||||||
LocalProp* property() const;
|
LocalProp* property() const;
|
||||||
|
|
|
@ -67,7 +67,6 @@ unsigned int FGCanvasGroup::indexOfChild(const FGCanvasElement *e) const
|
||||||
|
|
||||||
CanvasItem *FGCanvasGroup::createQuickItem(QQuickItem *parent)
|
CanvasItem *FGCanvasGroup::createQuickItem(QQuickItem *parent)
|
||||||
{
|
{
|
||||||
qDebug() << Q_FUNC_INFO;
|
|
||||||
_quick = new CanvasItem(parent);
|
_quick = new CanvasItem(parent);
|
||||||
|
|
||||||
for (auto e : _children) {
|
for (auto e : _children) {
|
||||||
|
@ -135,7 +134,12 @@ bool FGCanvasGroup::onChildAdded(LocalProp *prop)
|
||||||
|
|
||||||
if (isRootGroup) {
|
if (isRootGroup) {
|
||||||
// ignore all of these, handled by the enclosing canvas view
|
// ignore all of these, handled by the enclosing canvas view
|
||||||
if ((nm == "view") || (nm == "size") || nm.startsWith("status") || (nm == "name") || (nm == "mipmapping") || (nm == "placement")) {
|
if (nm == "size") {
|
||||||
|
connect(prop, &LocalProp::valueChanged, this, &FGCanvasGroup::canvasSizeChanged);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((nm == "view") || nm.startsWith("status") || (nm == "name") || (nm == "mipmapping") || (nm == "placement")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ signals:
|
||||||
void childAdded();
|
void childAdded();
|
||||||
void childRemoved(int index);
|
void childRemoved(int index);
|
||||||
|
|
||||||
|
void canvasSizeChanged();
|
||||||
protected:
|
protected:
|
||||||
virtual void doPaint(FGCanvasPaintContext* context) const override;
|
virtual void doPaint(FGCanvasPaintContext* context) const override;
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ public:
|
||||||
: CanvasItem(parent)
|
: CanvasItem(parent)
|
||||||
{
|
{
|
||||||
setFlag(ItemHasContents);
|
setFlag(ItemHasContents);
|
||||||
qDebug() << Q_FUNC_INFO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPath(QPainterPath pp)
|
void setPath(QPainterPath pp)
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "localprop.h"
|
#include "localprop.h"
|
||||||
#include "fgqcanvasfontcache.h"
|
#include "fgqcanvasfontcache.h"
|
||||||
#include "canvasitem.h"
|
#include "canvasitem.h"
|
||||||
|
#include "canvasconnection.h"
|
||||||
|
|
||||||
static QQmlComponent* static_textComponent = nullptr;
|
static QQmlComponent* static_textComponent = nullptr;
|
||||||
|
|
||||||
|
@ -33,10 +34,6 @@ FGCanvasText::FGCanvasText(FGCanvasGroup* pr, LocalProp* prop) :
|
||||||
FGCanvasElement(pr, prop),
|
FGCanvasElement(pr, prop),
|
||||||
_metrics(QFont())
|
_metrics(QFont())
|
||||||
{
|
{
|
||||||
// this signal fires infrequently enough, it's simpler just to have
|
|
||||||
// all texts watch it.
|
|
||||||
connect(FGQCanvasFontCache::instance(), &FGQCanvasFontCache::fontLoaded,
|
|
||||||
this, &FGCanvasText::onFontLoaded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CanvasItem *FGCanvasText::createQuickItem(QQuickItem *parent)
|
CanvasItem *FGCanvasText::createQuickItem(QQuickItem *parent)
|
||||||
|
@ -191,6 +188,8 @@ void FGCanvasText::onFontLoaded(QByteArray name)
|
||||||
return; // not our font
|
return; // not our font
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto fontCache = connection()->fontCache();
|
||||||
|
disconnect(fontCache, &FGQCanvasFontCache::fontLoaded, this, &FGCanvasText::onFontLoaded);
|
||||||
markFontDirty();
|
markFontDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,9 +197,12 @@ void FGCanvasText::rebuildFont() const
|
||||||
{
|
{
|
||||||
QByteArray fontName = getCascadedStyle("font", QString()).toByteArray();
|
QByteArray fontName = getCascadedStyle("font", QString()).toByteArray();
|
||||||
bool ok;
|
bool ok;
|
||||||
QFont f = FGQCanvasFontCache::instance()->fontForName(fontName, &ok);
|
auto fontCache = connection()->fontCache();
|
||||||
|
QFont f = fontCache->fontForName(fontName, &ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// wait for the correct font
|
// wait for the correct font
|
||||||
|
connect(fontCache, &FGQCanvasFontCache::fontLoaded, this, &FGCanvasText::onFontLoaded);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int pixelSize = getCascadedStyle("character-size", 16).toInt();
|
const int pixelSize = getCascadedStyle("character-size", 16).toInt();
|
||||||
|
|
|
@ -55,20 +55,6 @@ QFont FGQCanvasFontCache::fontForName(QByteArray name, bool* ok)
|
||||||
return QFont(); // default font
|
return QFont(); // default font
|
||||||
}
|
}
|
||||||
|
|
||||||
static FGQCanvasFontCache* s_instance = nullptr;
|
|
||||||
|
|
||||||
void FGQCanvasFontCache::initialise(QNetworkAccessManager *nam)
|
|
||||||
{
|
|
||||||
Q_ASSERT(s_instance == nullptr);
|
|
||||||
s_instance = new FGQCanvasFontCache(nam);
|
|
||||||
}
|
|
||||||
|
|
||||||
FGQCanvasFontCache *FGQCanvasFontCache::instance()
|
|
||||||
{
|
|
||||||
|
|
||||||
return s_instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FGQCanvasFontCache::setHost(QString hostName, int portNumber)
|
void FGQCanvasFontCache::setHost(QString hostName, int portNumber)
|
||||||
{
|
{
|
||||||
m_hostName = hostName;
|
m_hostName = hostName;
|
||||||
|
|
|
@ -34,9 +34,6 @@ public:
|
||||||
|
|
||||||
QFont fontForName(QByteArray name, bool* ok = nullptr);
|
QFont fontForName(QByteArray name, bool* ok = nullptr);
|
||||||
|
|
||||||
static void initialise(QNetworkAccessManager* nam);
|
|
||||||
static FGQCanvasFontCache* instance();
|
|
||||||
|
|
||||||
void setHost(QString hostName, int portNumber);
|
void setHost(QString hostName, int portNumber);
|
||||||
signals:
|
signals:
|
||||||
void fontLoaded(QByteArray name);
|
void fontLoaded(QByteArray name);
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "localprop.h"
|
#include "localprop.h"
|
||||||
#include "fgqcanvasimageloader.h"
|
#include "fgqcanvasimageloader.h"
|
||||||
#include "canvasitem.h"
|
#include "canvasitem.h"
|
||||||
|
#include "canvasconnection.h"
|
||||||
|
|
||||||
static QQmlComponent* static_imageComponent = nullptr;
|
static QQmlComponent* static_imageComponent = nullptr;
|
||||||
|
|
||||||
|
@ -37,10 +38,12 @@ FGQCanvasImage::FGQCanvasImage(FGCanvasGroup* pr, LocalProp* prop) :
|
||||||
void FGQCanvasImage::setEngine(QQmlEngine *engine)
|
void FGQCanvasImage::setEngine(QQmlEngine *engine)
|
||||||
{
|
{
|
||||||
static_imageComponent = new QQmlComponent(engine, QUrl("image.qml"));
|
static_imageComponent = new QQmlComponent(engine, QUrl("image.qml"));
|
||||||
qDebug() << static_imageComponent->errorString();
|
if (!static_imageComponent || !static_imageComponent->errors().empty()) {
|
||||||
|
qWarning() << static_imageComponent->errorString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FGQCanvasImage::doPaint(FGCanvasPaintContext *context) const
|
void FGQCanvasImage::doPolish()
|
||||||
{
|
{
|
||||||
if (_imageDirty) {
|
if (_imageDirty) {
|
||||||
rebuildImage();
|
rebuildImage();
|
||||||
|
@ -50,7 +53,10 @@ void FGQCanvasImage::doPaint(FGCanvasPaintContext *context) const
|
||||||
if (_sourceRectDirty) {
|
if (_sourceRectDirty) {
|
||||||
recomputeSourceRect();
|
recomputeSourceRect();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGQCanvasImage::doPaint(FGCanvasPaintContext *context) const
|
||||||
|
{
|
||||||
QRectF dstRect(0.0, 0.0, _destSize.width(), _destSize.height());
|
QRectF dstRect(0.0, 0.0, _destSize.width(), _destSize.height());
|
||||||
context->painter()->drawPixmap(dstRect, _image, _sourceRect);
|
context->painter()->drawPixmap(dstRect, _image, _sourceRect);
|
||||||
}
|
}
|
||||||
|
@ -75,18 +81,19 @@ bool FGQCanvasImage::onChildAdded(LocalProp *prop)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "image saw child:" << prop->name();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FGQCanvasImage::markImageDirty()
|
void FGQCanvasImage::markImageDirty()
|
||||||
{
|
{
|
||||||
_imageDirty = true;
|
_imageDirty = true;
|
||||||
|
requestPolish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FGQCanvasImage::markSourceDirty()
|
void FGQCanvasImage::markSourceDirty()
|
||||||
{
|
{
|
||||||
_sourceRectDirty = true;
|
_sourceRectDirty = true;
|
||||||
|
requestPolish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FGQCanvasImage::recomputeSourceRect() const
|
void FGQCanvasImage::recomputeSourceRect() const
|
||||||
|
@ -118,15 +125,16 @@ void FGQCanvasImage::recomputeSourceRect() const
|
||||||
void FGQCanvasImage::rebuildImage() const
|
void FGQCanvasImage::rebuildImage() const
|
||||||
{
|
{
|
||||||
QByteArray file = _propertyRoot->value("file", QByteArray()).toByteArray();
|
QByteArray file = _propertyRoot->value("file", QByteArray()).toByteArray();
|
||||||
|
auto loader = connection()->imageLoader();
|
||||||
if (!file.isEmpty()) {
|
if (!file.isEmpty()) {
|
||||||
_image = FGQCanvasImageLoader::instance()->getImage(file);
|
_image = loader->getImage(file);
|
||||||
|
|
||||||
|
|
||||||
if (_image.isNull()) {
|
if (_image.isNull()) {
|
||||||
// get notified when the image loads
|
// get notified when the image loads
|
||||||
FGQCanvasImageLoader::instance()->connectToImageLoaded(file,
|
loader->connectToImageLoaded(file,
|
||||||
const_cast<FGQCanvasImage*>(this),
|
const_cast<FGQCanvasImage*>(this),
|
||||||
SLOT(markImageDirty()));
|
SLOT(markImageDirty()));
|
||||||
} else {
|
} else {
|
||||||
// loaded image ok!
|
// loaded image ok!
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ protected:
|
||||||
virtual void markStyleDirty() override;
|
virtual void markStyleDirty() override;
|
||||||
|
|
||||||
|
|
||||||
|
void doPolish() override;
|
||||||
private slots:
|
private slots:
|
||||||
void markImageDirty();
|
void markImageDirty();
|
||||||
void markSourceDirty();
|
void markSourceDirty();
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
static FGQCanvasImageLoader* static_instance = nullptr;
|
|
||||||
|
|
||||||
class TransferSignalHolder : public QObject
|
class TransferSignalHolder : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -31,8 +29,9 @@ signals:
|
||||||
void trigger();
|
void trigger();
|
||||||
};
|
};
|
||||||
|
|
||||||
FGQCanvasImageLoader::FGQCanvasImageLoader(QNetworkAccessManager* dl)
|
FGQCanvasImageLoader::FGQCanvasImageLoader(QNetworkAccessManager* dl, QObject* pr)
|
||||||
: m_downloader(dl)
|
: QObject(pr)
|
||||||
|
, m_downloader(dl)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,17 +57,6 @@ void FGQCanvasImageLoader::onDownloadFinished()
|
||||||
reply->deleteLater();
|
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)
|
void FGQCanvasImageLoader::setHost(QString hostName, int portNumber)
|
||||||
{
|
{
|
||||||
m_hostName = hostName;
|
m_hostName = hostName;
|
||||||
|
|
|
@ -29,9 +29,8 @@ class FGQCanvasImageLoader : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static FGQCanvasImageLoader* instance();
|
FGQCanvasImageLoader(QNetworkAccessManager* dl, QObject* pr = nullptr);
|
||||||
|
|
||||||
static void initialise(QNetworkAccessManager* dl);
|
|
||||||
|
|
||||||
void setHost(QString hostName, int portNumber);
|
void setHost(QString hostName, int portNumber);
|
||||||
|
|
||||||
|
@ -49,7 +48,6 @@ signals:
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit FGQCanvasImageLoader(QNetworkAccessManager* dl);
|
|
||||||
|
|
||||||
void onDownloadFinished();
|
void onDownloadFinished();
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,6 @@
|
||||||
|
|
||||||
#include "fgcanvastext.h"
|
#include "fgcanvastext.h"
|
||||||
#include "fgqcanvasimage.h"
|
#include "fgqcanvasimage.h"
|
||||||
#include "fgqcanvasfontcache.h"
|
|
||||||
#include "fgqcanvasimageloader.h"
|
|
||||||
#include "canvasitem.h"
|
#include "canvasitem.h"
|
||||||
#include "applicationcontroller.h"
|
#include "applicationcontroller.h"
|
||||||
#include "canvasdisplay.h"
|
#include "canvasdisplay.h"
|
||||||
|
@ -40,9 +38,6 @@ int main(int argc, char *argv[])
|
||||||
ApplicationController appController;
|
ApplicationController appController;
|
||||||
// load saved history on the app controller?
|
// load saved history on the app controller?
|
||||||
|
|
||||||
FGQCanvasFontCache::initialise(appController.netAccess());
|
|
||||||
FGQCanvasImageLoader::initialise(appController.netAccess());
|
|
||||||
|
|
||||||
qmlRegisterType<CanvasItem>("FlightGear", 1, 0, "CanvasItem");
|
qmlRegisterType<CanvasItem>("FlightGear", 1, 0, "CanvasItem");
|
||||||
qmlRegisterType<CanvasDisplay>("FlightGear", 1, 0, "CanvasDisplay");
|
qmlRegisterType<CanvasDisplay>("FlightGear", 1, 0, "CanvasDisplay");
|
||||||
qmlRegisterUncreatableType<CanvasConnection>("FlightGear", 1, 0, "CanvasConnection", "Don't create me");
|
qmlRegisterUncreatableType<CanvasConnection>("FlightGear", 1, 0, "CanvasConnection", "Don't create me");
|
||||||
|
|
|
@ -61,6 +61,31 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: cancelButton
|
||||||
|
label: "Cancel"
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
visible: (_application.status == FG.Application.Querying)
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
_application.cancelQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: clearlButton
|
||||||
|
label: "Clear"
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
visible: (_application.status == FG.Application.SuccessfulQuery) |
|
||||||
|
(_application.status == FG.Application.QueryFailed)
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
_application.clearQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +101,7 @@ Item {
|
||||||
width: 300
|
width: 300
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: 8
|
anchors.bottomMargin: 8
|
||||||
|
visible: _application.canvases.length > 0
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: canvasList
|
id: canvasList
|
||||||
|
|
|
@ -8,11 +8,15 @@ Item {
|
||||||
|
|
||||||
readonly property var centerPoint: Qt.point(width / 2, height / 2)
|
readonly property var centerPoint: Qt.point(width / 2, height / 2)
|
||||||
|
|
||||||
|
clip: true;
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
width = canvas.size.width
|
if (canvas) {
|
||||||
height = canvas.size.height
|
width = canvas.size.width
|
||||||
x = canvas.center.x - (width / 2)
|
height = canvas.size.height
|
||||||
y = canvas.center.y - (height / 2)
|
x = canvas.center.x - (width / 2)
|
||||||
|
y = canvas.center.y - (height / 2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeometry()
|
function saveGeometry()
|
||||||
|
@ -26,11 +30,13 @@ Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
onCanvasChanged: {
|
onCanvasChanged: {
|
||||||
root.width = canvas.size.width
|
if (canvas) {
|
||||||
root.height = canvas.size.height
|
root.width = canvas.size.width
|
||||||
|
root.height = canvas.size.height
|
||||||
|
|
||||||
root.x = canvas.center.x - (root.width / 2)
|
root.x = canvas.center.x - (root.width / 2)
|
||||||
root.y = canvas.center.y - (root.height / 2)
|
root.y = canvas.center.y - (root.height / 2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +45,8 @@ Item {
|
||||||
border.color: "orange"
|
border.color: "orange"
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width + 2
|
width: parent.width
|
||||||
height: parent.height + 2
|
height: parent.height
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -110,7 +116,4 @@ Item {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,13 +60,43 @@ Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height:delegateFrame.height + 8
|
height:delegateFrame.height + 8
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: delegateBackFrame
|
||||||
|
color: "#1f1f1f"
|
||||||
|
width: delegateFrame.width
|
||||||
|
height: delegateFrame.height
|
||||||
|
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: deleteButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
label: "Delete"
|
||||||
|
onClicked: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: deleteButton.left
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
label: "Save"
|
||||||
|
onClicked: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: delegateFrame
|
id: delegateFrame
|
||||||
width: parent.width
|
width: parent.width
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
// anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
height: configLabel.implicitHeight + 20
|
height: configLabel.implicitHeight + 20
|
||||||
|
|
||||||
|
opacity: 1.0
|
||||||
color: "#3f3f3f"
|
color: "#3f3f3f"
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
@ -83,19 +113,14 @@ Item {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
_application.restoreConfig(model.index)
|
_application.restoreConfig(model.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drag.target: delegateFrame
|
||||||
|
drag.axis: Drag.XAxis
|
||||||
|
drag.minimumX: -delegateFrame.width
|
||||||
|
drag.maximumX: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
label: "X"
|
|
||||||
width: height
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // of visible rect
|
} // of visible rect
|
||||||
} // of delegate item
|
} // of delegate item
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue