Merge branch 'main' into zshell-img-tools
This commit is contained in:
+55
-57
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ Variants {
|
|||||||
id: notifsBg
|
id: notifsBg
|
||||||
|
|
||||||
panel: panels.notifications
|
panel: panels.notifications
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelBg {
|
PanelBg {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user