Merge branch 'main' into zshell-img-tools
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 21s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m4s

This commit is contained in:
2026-05-26 23:03:28 +02:00
9 changed files with 408 additions and 204 deletions
+55 -57
View File
@@ -179,6 +179,8 @@ Singleton {
property string appIcon property string appIcon
property string appName property string appName
property string body property string body
property string cachedImageSource: ""
property bool cachingImage: false
property bool closed property bool closed
readonly property Connections conn: Connections { readonly property Connections conn: Connections {
function onActionsChanged(): void { function onActionsChanged(): void {
@@ -214,9 +216,9 @@ Singleton {
} }
function onImageChanged(): void { function onImageChanged(): void {
notif.image = notif.notification.image; notif.imageSource = notif.notification.image || "";
if (notif.notification?.image) notif.image = notif.imageSource;
notif.dummyImageLoader.active = true; notif.cacheImageIfNeeded();
} }
function onResidentChanged(): void { function onResidentChanged(): void {
@@ -233,60 +235,12 @@ Singleton {
target: notif.notification target: notif.notification
} }
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
color: "transparent"
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
mask: Region {
}
Image {
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
anchors.fill: parent
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
opacity: 0
source: Qt.resolvedUrl(notif.image)
onHeightChanged: tryCache()
onStatusChanged: tryCache()
onWidthChanged: tryCache()
}
}
}
property real expireTimeout: 5 property real expireTimeout: 5
property bool hasActionIcons property bool hasActionIcons
property string id
property string image property string image
property string imageSource
property var locks: new Set() property var locks: new Set()
property string notifId
property Notification notification property Notification notification
property bool popup property bool popup
property bool resident property bool resident
@@ -329,6 +283,35 @@ Singleton {
} }
property int urgency: NotificationUrgency.Normal property int urgency: NotificationUrgency.Normal
function cacheImageIfNeeded(): void {
const source = imageSource;
if (!source || cachingImage)
return;
if (cachedImageSource === source)
return;
if (source.startsWith("file:")) {
cachedImageSource = source;
image = source;
return;
}
const hash = hashForString(source);
const cache = `${Paths.notifimagecache}/${hash}.png`;
const cacheUrl = Qt.resolvedUrl(cache);
cachingImage = true;
ZShellIo.saveImage(source, cacheUrl, () => {
cachedImageSource = source;
image = cache;
cachingImage = false;
}, () => {
cachingImage = false;
});
}
function close(): void { function close(): void {
closed = true; closed = true;
if (locks.size === 0 && root.list.includes(this)) { if (locks.size === 0 && root.list.includes(this)) {
@@ -338,6 +321,20 @@ Singleton {
} }
} }
function hashForString(s: string): string {
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < s.length; i++) {
ch = s.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return (h2 >>> 0).toString(16).padStart(8, "0") + (h1 >>> 0).toString(16).padStart(8, "0");
}
function lock(item: Item): void { function lock(item: Item): void {
locks.add(item); locks.add(item);
} }
@@ -352,14 +349,13 @@ Singleton {
if (!notification) if (!notification)
return; return;
id = notification.id; notifId = notification.id;
summary = notification.summary; summary = notification.summary;
body = notification.body; body = notification.body;
appIcon = notification.appIcon; appIcon = notification.appIcon;
appName = notification.appName; appName = notification.appName;
image = notification.image; imageSource = notification.image || "";
if (notification?.image) image = imageSource;
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout; expireTimeout = notification.expireTimeout;
urgency = notification.urgency; urgency = notification.urgency;
resident = notification.resident; resident = notification.resident;
@@ -369,6 +365,8 @@ Singleton {
text: a.text, text: a.text,
invoke: () => a.invoke() invoke: () => a.invoke()
})); }));
cacheImageIfNeeded();
} }
} }
} }
+1
View File
@@ -229,6 +229,7 @@ Variants {
id: notifsBg id: notifsBg
panel: panels.notifications panel: panels.notifications
radius: Appearance.rounding.normal
} }
PanelBg { PanelBg {
+1 -1
View File
@@ -20,7 +20,7 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open implicitWidth: content.implicitWidth || 854
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1 visible: offsetScale < 1
+5 -6
View File
@@ -9,18 +9,17 @@ Item {
id: root id: root
property int contentHeight property int contentHeight
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enable
required property PersistentProperties visibilities required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.dock
property real offsetScale: shouldBeActive ? 0 : 1
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400 implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -32,10 +31,10 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
asynchronous: true
active: root.shouldBeActive || root.visible
sourceComponent: Content { sourceComponent: Content {
panels: root.panels panels: root.panels
+2
View File
@@ -47,6 +47,7 @@ Item {
active: Qt.binding(() => root.shouldBeActive || root.visible) active: Qt.binding(() => root.shouldBeActive || root.visible)
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true
height: content.contentItem.height height: content.contentItem.height
opacity: root.expanded ? 0 : 1 opacity: root.expanded ? 0 : 1
@@ -65,6 +66,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
Behavior on opacity { Behavior on opacity {
+3 -45
View File
@@ -8,7 +8,7 @@ import QtQuick
Item { Item {
id: root id: root
readonly property int padding: 6 readonly property int padding: Appearance.padding.smaller
required property Item panels required property Item panels
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -54,7 +54,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.padding anchors.margins: root.padding
color: "transparent" color: "transparent"
radius: Appearance.rounding.smallest / 2 radius: Appearance.rounding.normal - root.padding
CustomListView { CustomListView {
id: list id: list
@@ -72,7 +72,7 @@ Item {
required property NotifServer.Notif modelData required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight readonly property alias nonAnimHeight: notif.nonAnimHeight
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8) implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Appearance.spacing.small)
implicitWidth: notif.implicitWidth implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start() ListView.onRemove: removeAnim.start()
@@ -151,48 +151,6 @@ Item {
property: "y" property: "y"
} }
} }
ExtraIndicator {
anchors.top: parent.top
extra: {
const count = list.count;
if (count === 0)
return 0;
const scrollY = list.contentY;
let height = 0;
for (let i = 0; i < count; i++) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
if (height - 8 >= scrollY)
return i;
}
return count;
}
}
ExtraIndicator {
anchors.bottom: parent.bottom
extra: {
const count = list.count;
if (count === 0)
return 0;
const scrollY = list.contentHeight - (list.contentY + list.height);
let height = 0;
for (let i = count - 1; i >= 0; i--) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
if (height - 8 >= scrollY)
return count - i - 1;
}
return 0;
}
}
} }
} }
+1 -1
View File
@@ -134,7 +134,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: searchBar.bottom anchors.top: searchBar.bottom
anchors.topMargin: Appearance.spacing.smaller anchors.topMargin: Appearance.spacing.smaller
color: DynamicColors.tPalette.m3surfaceContainerLowest color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
StackView { StackView {
+317 -79
View File
@@ -1,131 +1,369 @@
#include "writefile.hpp" #include "writefile.hpp"
#include <QtConcurrent/qtconcurrentrun.h> #include <QtConcurrent/qtconcurrentrun.h>
#include <QtQuick/qquickimageprovider.h>
#include <QtQuick/qquickitemgrabresult.h> #include <QtQuick/qquickitemgrabresult.h>
#include <QtQuick/qquickwindow.h> #include <QtQuick/qquickwindow.h>
#include <qdir.h> #include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h> #include <qfileinfo.h>
#include <qfuturewatcher.h> #include <qfuturewatcher.h>
#include <qimage.h>
#include <qjsengine.h>
#include <qqmlengine.h> #include <qqmlengine.h>
namespace ZShell { namespace ZShell {
// ============================================================
// saveItem
// ============================================================
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) { 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) { 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) { 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) { 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) { 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) { void ZShellIo::saveItem(
if (!target) { QQuickItem* target,
qWarning() << "ZShellIo::saveItem: a target is required"; const QUrl& path,
return; const QRect& rect,
} QJSValue onSaved,
QJSValue onFailed
) {
if (!target) {
qWarning() << "ZShellIo::saveItem: a target is required";
return;
}
if (!path.isLocalFile()) { if (!path.isLocalFile()) {
qWarning() << "ZShellIo::saveItem:" << path << "is not a local file"; qWarning() << "ZShellIo::saveItem:" << path << "is not a local file";
return; return;
} }
if (!target->window()) { if (!target->window()) {
qWarning() << "ZShellIo::saveItem: unable to save target" << target << "without a window"; qWarning() << "ZShellIo::saveItem: unable to save target"
return; << target
} << "without a window";
return;
}
auto scaledRect = rect; 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();
}
const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage(); const qreal scale = target->window()->devicePixelRatio();
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
[grabResult, scaledRect, path, onSaved, onFailed, this]() { scaledRect = QRectF(
const auto future = QtConcurrent::run([=]() { rect.left() * scale,
QImage image = grabResult->image(); rect.top() * scale,
rect.width() * scale,
rect.height() * scale
).toRect();
}
if (scaledRect.isValid()) { const QSharedPointer<const QQuickItemGrabResult> grabResult =
image = image.copy(scaledRect); target->grabToImage();
}
const QString file = path.toLocalFile(); QObject::connect(
const QString parent = QFileInfo(file).absolutePath(); grabResult.data(),
return QDir().mkpath(parent) && image.save(file); &QQuickItemGrabResult::ready,
}); this,
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
auto* watcher = new QFutureWatcher<bool>(this); QImage image = grabResult->image();
auto* engine = qmlEngine(this);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() { if (scaledRect.isValid()) {
if (watcher->result()) { image = image.copy(scaledRect);
if (onSaved.isCallable()) { }
onSaved.call(
{ QJSValue(path.toLocalFile()), engine->toScriptValue(QVariant::fromValue(path)) }); const auto future = QtConcurrent::run([image, path]() {
}
} else { const QString file = path.toLocalFile();
qWarning() << "ZShellIo::saveItem: failed to save" << path; const QString parent = QFileInfo(file).absolutePath();
if (onFailed.isCallable()) {
onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) }); return QDir().mkpath(parent) && image.save(file);
} });
}
watcher->deleteLater(); auto* watcher = new QFutureWatcher<bool>(this);
}); auto* engine = qmlEngine(this);
watcher->setFuture(future);
}); 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 { 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 (!source.isLocalFile()) {
if (!QFile::remove(target.toLocalFile())) { qWarning() << "ZShellIo::copyFile: source"
qWarning() << "ZShellIo::copyFile: overwrite was specified but failed to remove" << target.toLocalFile(); << source
return false; << "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 { 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 { 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 } // namespace ZShell
+23 -15
View File
@@ -1,31 +1,39 @@
#pragma once #pragma once
#include <QtQuick/qquickitem.h> #include <QtQuick/qquickitem.h>
#include <qjsvalue.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qurl.h>
namespace ZShell { namespace ZShell {
class ZShellIo : public QObject { class ZShellIo : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON QML_SINGLETON
public: public:
// clang-format off // clang-format off
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); 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, 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);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); 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);
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
// clang-format on
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const; Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target);
Q_INVOKABLE bool deleteFile(const QUrl& path) const; Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved);
Q_INVOKABLE QString toLocalFile(const QUrl& url) const; 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 } // namespace ZShell