diff --git a/utils/fgqcanvas/fgqcanvasfontcache.cpp b/utils/fgqcanvas/fgqcanvasfontcache.cpp index 8ab4e9f59..e70c8caeb 100644 --- a/utils/fgqcanvas/fgqcanvasfontcache.cpp +++ b/utils/fgqcanvas/fgqcanvasfontcache.cpp @@ -137,8 +137,8 @@ void FGQCanvasFontCache::lookupFile(QByteArray name) reply->setProperty("font", name); connect(reply, &QNetworkReply::finished, this, &FGQCanvasFontCache::onFontDownloadFinished); - // connect(reply, &QNetworkReply::error, this, &FGQCanvasFontCache::onFontDownloadError); - + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(onFontDownloadError(QNetworkReply::NetworkError))); m_transfers.append(reply); } diff --git a/utils/fgqcanvas/fgqcanvasimageloader.cpp b/utils/fgqcanvas/fgqcanvasimageloader.cpp index eabfc7508..2d430f608 100644 --- a/utils/fgqcanvas/fgqcanvasimageloader.cpp +++ b/utils/fgqcanvas/fgqcanvasimageloader.cpp @@ -19,6 +19,10 @@ #include #include +#include +#include +#include +#include class TransferSignalHolder : public QObject { @@ -43,8 +47,11 @@ void FGQCanvasImageLoader::onDownloadFinished() 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); + QByteArray imagePath = reply->property("image").toByteArray(); + m_cache.insert(imagePath, pm); + + // cache on disk also, so snapshots work + writeToDiskCache(imagePath, reply); TransferSignalHolder* signalHolder = reply->findChild("holder"); if (signalHolder) { @@ -57,6 +64,23 @@ void FGQCanvasImageLoader::onDownloadFinished() reply->deleteLater(); } +void FGQCanvasImageLoader::writeToDiskCache(QByteArray imagePath, QNetworkReply* reply) +{ + QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + QString absPath = cacheDir.absoluteFilePath(imagePath); + + QFileInfo finfo(imagePath); + cacheDir.mkpath(finfo.dir().path()); + + QFile f(absPath); + if (!f.open(QIODevice::WriteOnly)) { + qWarning() << "failed to open cache file" << f.fileName(); + } + + f.write(reply->readAll()); + f.close(); +} + void FGQCanvasImageLoader::setHost(QString hostName, int portNumber) { m_hostName = hostName; @@ -70,6 +94,15 @@ QPixmap FGQCanvasImageLoader::getImage(const QByteArray &imagePath) return m_cache.value(imagePath); } + QString diskCachePath = QStandardPaths::locate(QStandardPaths::CacheLocation, imagePath); + if (!diskCachePath.isEmpty()) { + QPixmap pix; + pix.load(diskCachePath); + m_cache.insert(imagePath, pix); + qDebug() << "loaded from on-disk cache:" << imagePath; + return pix; + } + QUrl url; url.setScheme("http"); url.setHost(m_hostName); @@ -82,7 +115,6 @@ QPixmap FGQCanvasImageLoader::getImage(const QByteArray &imagePath) } } - qDebug() << "requesting image" << url; QNetworkReply* reply = m_downloader->get(QNetworkRequest(url)); reply->setProperty("image", imagePath); diff --git a/utils/fgqcanvas/fgqcanvasimageloader.h b/utils/fgqcanvas/fgqcanvasimageloader.h index 9a6bcec4e..fee6be210 100644 --- a/utils/fgqcanvas/fgqcanvasimageloader.h +++ b/utils/fgqcanvas/fgqcanvasimageloader.h @@ -50,6 +50,7 @@ signals: private: void onDownloadFinished(); + void writeToDiskCache(QByteArray imagePath, QNetworkReply *reply); private: QNetworkAccessManager* m_downloader; diff --git a/utils/fgqcanvas/localprop.cpp b/utils/fgqcanvas/localprop.cpp index 651528973..9e83df14d 100644 --- a/utils/fgqcanvas/localprop.cpp +++ b/utils/fgqcanvas/localprop.cpp @@ -20,6 +20,18 @@ #include #include +QDataStream& operator<<(QDataStream& stream, const NameIndexTuple& nameIndex) +{ + stream << nameIndex.name << nameIndex.index; + return stream; +} + +QDataStream& operator>>(QDataStream& stream, NameIndexTuple& nameIndex) +{ + stream >> nameIndex.name >> nameIndex.index; + return stream; +} + LocalProp *LocalProp::getOrCreateWithPath(const QByteArray &path) { if (path.isEmpty()) { @@ -119,6 +131,41 @@ void LocalProp::changeValue(const char *path, QVariant value) p->valueChanged(value); } +void LocalProp::saveToStream(QDataStream &stream) const +{ + stream << _id << _position << _value; + stream << static_cast(_children.size()); + for (auto child : _children) { + child->saveToStream(stream); + } +} + +LocalProp* LocalProp::restoreFromStream(QDataStream &stream, LocalProp* parent) +{ + NameIndexTuple id; + stream >> id; + LocalProp* prop = new LocalProp(parent, id); + stream >> prop->_position >> prop->_value; + int childCount; + stream >> childCount; + for (int c=0; c< childCount; ++c) { + prop->_children.push_back(restoreFromStream(stream, prop)); + } + + return prop; +} + +void LocalProp::recursiveNotifyRestored() +{ + emit valueChanged(_value); + for (auto child : _children) { + emit childAdded(child); + } + + for (auto child : _children) { + child->recursiveNotifyRestored(); + } +} LocalProp *LocalProp::getOrCreateChildWithNameAndIndex(const NameIndexTuple& ni) { diff --git a/utils/fgqcanvas/localprop.h b/utils/fgqcanvas/localprop.h index 9f28e42bd..a2c9e06e3 100644 --- a/utils/fgqcanvas/localprop.h +++ b/utils/fgqcanvas/localprop.h @@ -22,12 +22,16 @@ #include #include #include +#include struct NameIndexTuple { QByteArray name; unsigned int index = 0; + NameIndexTuple() + {} + NameIndexTuple(const char* nm, unsigned int idx) : name(nm), index(idx) @@ -66,8 +70,12 @@ struct NameIndexTuple return name < other.name; } + }; +QDataStream& operator<<(QDataStream& stream, const NameIndexTuple& nameIndex); +QDataStream& operator>>(QDataStream& stream, NameIndexTuple& nameIndex); + class LocalProp : public QObject { Q_OBJECT @@ -126,6 +134,12 @@ public: bool hasChild(const char* name) const; void changeValue(const char* path, QVariant value); + + void saveToStream(QDataStream& stream) const; + + static LocalProp* restoreFromStream(QDataStream& stream, LocalProp *parent); + + void recursiveNotifyRestored(); signals: void valueChanged(QVariant val); diff --git a/utils/fgqcanvas/temporarywidget.cpp b/utils/fgqcanvas/temporarywidget.cpp index 2addab46d..0536c91c4 100644 --- a/utils/fgqcanvas/temporarywidget.cpp +++ b/utils/fgqcanvas/temporarywidget.cpp @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include "fgqcanvasfontcache.h" #include "fgqcanvasimageloader.h" @@ -49,6 +52,16 @@ TemporaryWidget::TemporaryWidget(QWidget *parent) : connect(ui->canvasSelectCombo, SIGNAL(activated(int)), this, SLOT(onCanvasSelected(int))); ui->canvasSelectCombo->hide(); + + QAction* saveTreeAction = new QAction("Save", this); + saveTreeAction->setShortcut(Qt::CTRL | Qt::Key_S); + connect(saveTreeAction, &QAction::triggered, this, &TemporaryWidget::onSave); + addAction(saveTreeAction); + + QAction* openSnapshotAction = new QAction("Open snapshot", this); + openSnapshotAction->setShortcut(Qt::CTRL | Qt::Key_O); + connect(openSnapshotAction, &QAction::triggered, this, &TemporaryWidget::onLoadSnapshot); + addAction(openSnapshotAction); } TemporaryWidget::~TemporaryWidget() @@ -117,13 +130,59 @@ void TemporaryWidget::onFinishedGetCanvasList() } } -void TemporaryWidget::onWebSocketConnected() +void TemporaryWidget::onSave() { - connect(&m_webSocket, &QWebSocket::textMessageReceived, - this, &TemporaryWidget::onTextMessageReceived); + qDebug() << "should save"; + if (!m_localPropertyRoot) { + return; + } - m_localPropertyRoot = new LocalProp(nullptr, NameIndexTuple("")); + QString path = QFileDialog::getSaveFileName(this, tr("Choose name to save")); + if (path.isEmpty()) { + return; + } + QFile f(path); + f.open(QIODevice::ReadWrite); + + { + QDataStream ds(&f); + m_localPropertyRoot->saveToStream(ds); + } + + f.close(); +} + +void TemporaryWidget::onLoadSnapshot() +{ + qDebug() << "should load"; + + QString path = QFileDialog::getOpenFileName(this, tr("Select a saved snapshot")); + if (path.isEmpty()) { + return; + } + + QFile f(path); + f.open(QIODevice::ReadOnly); + + { + QDataStream ds(&f); + m_localPropertyRoot = LocalProp::restoreFromStream(ds, nullptr); + + createdRootProperty(); + + m_localPropertyRoot->recursiveNotifyRestored(); + + // this is needed for either QtQuick or QPainter drawing + ui->canvas->rootElement()->update(); + + // this is the widget re-draw request (QtQuick draws all the time) + ui->canvas->update(); + } +} + +void TemporaryWidget::createdRootProperty() +{ ui->canvas->setRootProperty(m_localPropertyRoot); ui->stack->setCurrentIndex(1); @@ -136,6 +195,16 @@ void TemporaryWidget::onWebSocketConnected() ui->elementData->setModel(m_elementModel); connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TemporaryWidget::onTreeCurrentChanged); +} + +void TemporaryWidget::onWebSocketConnected() +{ + connect(&m_webSocket, &QWebSocket::textMessageReceived, + this, &TemporaryWidget::onTextMessageReceived); + + m_localPropertyRoot = new LocalProp(nullptr, NameIndexTuple("")); + + createdRootProperty(); FGQCanvasFontCache::instance()->setHost(ui->hostName->text(), ui->portEdit->text().toInt()); diff --git a/utils/fgqcanvas/temporarywidget.h b/utils/fgqcanvas/temporarywidget.h index 402b6ded0..0673ff78b 100644 --- a/utils/fgqcanvas/temporarywidget.h +++ b/utils/fgqcanvas/temporarywidget.h @@ -52,12 +52,17 @@ private Q_SLOTS: void onCanvasSelected(int index); void onFinishedGetCanvasList(); + + void onSave(); + void onLoadSnapshot(); private: void saveSettings(); void restoreSettings(); LocalProp* propertyFromPath(QByteArray path) const; + void createdRootProperty(); + QNetworkAccessManager* m_netAccess; QWebSocket m_webSocket; Ui::TemporaryWidget *ui;