optimize notification icon caching by copying image rather than item
This commit is contained in:
+317
-79
@@ -1,131 +1,369 @@
|
||||
#include "writefile.hpp"
|
||||
|
||||
#include <QtConcurrent/qtconcurrentrun.h>
|
||||
#include <QtQuick/qquickimageprovider.h>
|
||||
#include <QtQuick/qquickitemgrabresult.h>
|
||||
#include <QtQuick/qquickwindow.h>
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qfile.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qfuturewatcher.h>
|
||||
#include <qimage.h>
|
||||
#include <qjsengine.h>
|
||||
#include <qqmlengine.h>
|
||||
|
||||
namespace ZShell {
|
||||
|
||||
// ============================================================
|
||||
// saveItem
|
||||
// ============================================================
|
||||
|
||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) {
|
||||
this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
|
||||
this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) {
|
||||
this->saveItem(target, path, rect, QJSValue(), QJSValue());
|
||||
this->saveItem(target, path, rect, QJSValue(), QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) {
|
||||
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
||||
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
||||
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
||||
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
||||
}
|
||||
|
||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
||||
this->saveItem(target, path, rect, onSaved, QJSValue());
|
||||
this->saveItem(target, path, rect, onSaved, QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed) {
|
||||
if (!target) {
|
||||
qWarning() << "ZShellIo::saveItem: a target is required";
|
||||
return;
|
||||
}
|
||||
void ZShellIo::saveItem(
|
||||
QQuickItem* target,
|
||||
const QUrl& path,
|
||||
const QRect& rect,
|
||||
QJSValue onSaved,
|
||||
QJSValue onFailed
|
||||
) {
|
||||
if (!target) {
|
||||
qWarning() << "ZShellIo::saveItem: a target is required";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!path.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::saveItem:" << path << "is not a local file";
|
||||
return;
|
||||
}
|
||||
if (!path.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::saveItem:" << path << "is not a local file";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target->window()) {
|
||||
qWarning() << "ZShellIo::saveItem: unable to save target" << target << "without a window";
|
||||
return;
|
||||
}
|
||||
if (!target->window()) {
|
||||
qWarning() << "ZShellIo::saveItem: unable to save target"
|
||||
<< target
|
||||
<< "without a window";
|
||||
return;
|
||||
}
|
||||
|
||||
auto scaledRect = rect;
|
||||
const qreal scale = target->window()->devicePixelRatio();
|
||||
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
||||
scaledRect =
|
||||
QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect();
|
||||
}
|
||||
auto scaledRect = rect;
|
||||
|
||||
const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage();
|
||||
const qreal scale = target->window()->devicePixelRatio();
|
||||
|
||||
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this,
|
||||
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
||||
const auto future = QtConcurrent::run([=]() {
|
||||
QImage image = grabResult->image();
|
||||
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
||||
scaledRect = QRectF(
|
||||
rect.left() * scale,
|
||||
rect.top() * scale,
|
||||
rect.width() * scale,
|
||||
rect.height() * scale
|
||||
).toRect();
|
||||
}
|
||||
|
||||
if (scaledRect.isValid()) {
|
||||
image = image.copy(scaledRect);
|
||||
}
|
||||
const QSharedPointer<const QQuickItemGrabResult> grabResult =
|
||||
target->grabToImage();
|
||||
|
||||
const QString file = path.toLocalFile();
|
||||
const QString parent = QFileInfo(file).absolutePath();
|
||||
return QDir().mkpath(parent) && image.save(file);
|
||||
});
|
||||
QObject::connect(
|
||||
grabResult.data(),
|
||||
&QQuickItemGrabResult::ready,
|
||||
this,
|
||||
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
||||
|
||||
auto* watcher = new QFutureWatcher<bool>(this);
|
||||
auto* engine = qmlEngine(this);
|
||||
QImage image = grabResult->image();
|
||||
|
||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
||||
if (watcher->result()) {
|
||||
if (onSaved.isCallable()) {
|
||||
onSaved.call(
|
||||
{ QJSValue(path.toLocalFile()), engine->toScriptValue(QVariant::fromValue(path)) });
|
||||
}
|
||||
} else {
|
||||
qWarning() << "ZShellIo::saveItem: failed to save" << path;
|
||||
if (onFailed.isCallable()) {
|
||||
onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) });
|
||||
}
|
||||
}
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
});
|
||||
if (scaledRect.isValid()) {
|
||||
image = image.copy(scaledRect);
|
||||
}
|
||||
|
||||
const auto future = QtConcurrent::run([image, path]() {
|
||||
|
||||
const QString file = path.toLocalFile();
|
||||
const QString parent = QFileInfo(file).absolutePath();
|
||||
|
||||
return QDir().mkpath(parent) && image.save(file);
|
||||
});
|
||||
|
||||
auto* watcher = new QFutureWatcher<bool>(this);
|
||||
auto* engine = qmlEngine(this);
|
||||
|
||||
QObject::connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[=]() {
|
||||
|
||||
if (watcher->result()) {
|
||||
|
||||
if (onSaved.isCallable()) {
|
||||
onSaved.call({
|
||||
QJSValue(path.toLocalFile()),
|
||||
engine->toScriptValue(QVariant::fromValue(path))
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
qWarning() << "ZShellIo::saveItem: failed to save"
|
||||
<< path;
|
||||
|
||||
if (onFailed.isCallable()) {
|
||||
onFailed.call({
|
||||
engine->toScriptValue(QVariant::fromValue(path))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
}
|
||||
);
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// saveImage
|
||||
// ============================================================
|
||||
|
||||
void ZShellIo::saveImage(const QUrl& source, const QUrl& target) {
|
||||
this->saveImage(source, target, QJSValue(), QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved) {
|
||||
this->saveImage(source, target, onSaved, QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveImage(
|
||||
const QUrl& source,
|
||||
const QUrl& target,
|
||||
QJSValue onSaved,
|
||||
QJSValue onFailed
|
||||
) {
|
||||
auto* engine = qmlEngine(this);
|
||||
|
||||
const auto future = QtConcurrent::run([this, source, target]() {
|
||||
return this->saveImageInternal(source, target);
|
||||
});
|
||||
|
||||
auto* watcher = new QFutureWatcher<bool>(this);
|
||||
|
||||
QObject::connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[=]() {
|
||||
|
||||
if (watcher->result()) {
|
||||
|
||||
if (onSaved.isCallable()) {
|
||||
onSaved.call({
|
||||
QJSValue(target.toLocalFile()),
|
||||
engine->toScriptValue(QVariant::fromValue(target))
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
qWarning() << "ZShellIo::saveImage: failed to save"
|
||||
<< source
|
||||
<< "to"
|
||||
<< target;
|
||||
|
||||
if (onFailed.isCallable()) {
|
||||
onFailed.call({
|
||||
engine->toScriptValue(QVariant::fromValue(target))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
}
|
||||
);
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const {
|
||||
|
||||
if (!target.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::saveImage: target"
|
||||
<< target
|
||||
<< "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString targetFile = target.toLocalFile();
|
||||
|
||||
if (!QDir().mkpath(QFileInfo(targetFile).absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Local file path
|
||||
// ========================================================
|
||||
|
||||
if (source.isLocalFile()) {
|
||||
|
||||
QFile::remove(targetFile);
|
||||
|
||||
return QFile::copy(
|
||||
source.toLocalFile(),
|
||||
targetFile
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// image:// provider path
|
||||
// ========================================================
|
||||
|
||||
if (source.scheme() == "image") {
|
||||
|
||||
auto* engine = qmlEngine(const_cast<ZShellIo*>(this));
|
||||
|
||||
if (!engine) {
|
||||
qWarning() << "ZShellIo::saveImage: no QQmlEngine";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString providerId = source.host();
|
||||
|
||||
const QString imageId =
|
||||
source.path().startsWith('/')
|
||||
? source.path().mid(1)
|
||||
: source.path();
|
||||
|
||||
auto* providerBase =
|
||||
engine->imageProvider(providerId);
|
||||
|
||||
if (!providerBase) {
|
||||
qWarning() << "ZShellIo::saveImage: provider not found"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* provider =
|
||||
dynamic_cast<QQuickImageProvider*>(providerBase);
|
||||
|
||||
if (!provider) {
|
||||
qWarning() << "ZShellIo::saveImage: provider is not a QQuickImageProvider"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
qWarning() << "ZShellIo::saveImage: provider not found"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
QSize size;
|
||||
QImage image;
|
||||
|
||||
switch (provider->imageType()) {
|
||||
|
||||
case QQuickImageProvider::Image:
|
||||
image = provider->requestImage(
|
||||
imageId,
|
||||
&size,
|
||||
QSize()
|
||||
);
|
||||
break;
|
||||
|
||||
case QQuickImageProvider::Pixmap:
|
||||
image = provider->requestPixmap(
|
||||
imageId,
|
||||
&size,
|
||||
QSize()
|
||||
).toImage();
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning() << "ZShellIo::saveImage: unsupported provider type";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (image.isNull()) {
|
||||
qWarning() << "ZShellIo::saveImage: provider returned null image";
|
||||
return false;
|
||||
}
|
||||
|
||||
return image.save(targetFile);
|
||||
}
|
||||
|
||||
qWarning() << "ZShellIo::saveImage: unsupported source"
|
||||
<< source;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// File ops
|
||||
// ============================================================
|
||||
|
||||
bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
||||
if (!source.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file";
|
||||
return false;
|
||||
}
|
||||
if (!target.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (overwrite) {
|
||||
if (!QFile::remove(target.toLocalFile())) {
|
||||
qWarning() << "ZShellIo::copyFile: overwrite was specified but failed to remove" << target.toLocalFile();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!source.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::copyFile: source"
|
||||
<< source
|
||||
<< "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
||||
if (!target.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::copyFile: target"
|
||||
<< target
|
||||
<< "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (overwrite) {
|
||||
QFile::remove(target.toLocalFile());
|
||||
}
|
||||
|
||||
return QFile::copy(
|
||||
source.toLocalFile(),
|
||||
target.toLocalFile()
|
||||
);
|
||||
}
|
||||
|
||||
bool ZShellIo::deleteFile(const QUrl& path) const {
|
||||
if (!path.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
return QFile::remove(path.toLocalFile());
|
||||
if (!path.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::deleteFile: path"
|
||||
<< path
|
||||
<< "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
return QFile::remove(path.toLocalFile());
|
||||
}
|
||||
|
||||
QString ZShellIo::toLocalFile(const QUrl& url) const {
|
||||
if (!url.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url;
|
||||
return QString();
|
||||
}
|
||||
|
||||
return url.toLocalFile();
|
||||
if (!url.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::toLocalFile: given url is not a local file"
|
||||
<< url;
|
||||
return QString();
|
||||
}
|
||||
|
||||
return url.toLocalFile();
|
||||
}
|
||||
|
||||
} // namespace ZShell
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtQuick/qquickitem.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qurl.h>
|
||||
|
||||
namespace ZShell {
|
||||
|
||||
class ZShellIo : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
// clang-format off
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||
// clang-format on
|
||||
// clang-format off
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||
|
||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
||||
Q_INVOKABLE bool deleteFile(const QUrl& path) const;
|
||||
Q_INVOKABLE QString toLocalFile(const QUrl& url) const;
|
||||
Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target);
|
||||
Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved, QJSValue onFailed);
|
||||
// clang-format on
|
||||
|
||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
||||
Q_INVOKABLE bool deleteFile(const QUrl& path) const;
|
||||
Q_INVOKABLE QString toLocalFile(const QUrl& url) const;
|
||||
|
||||
private:
|
||||
bool saveImageInternal(const QUrl& source, const QUrl& target) const;
|
||||
};
|
||||
|
||||
|
||||
} // namespace ZShell
|
||||
|
||||
Reference in New Issue
Block a user