diff --git a/components/mobileshell/CMakeLists.txt b/components/mobileshell/CMakeLists.txt index 9e3e6de0..fb559749 100644 --- a/components/mobileshell/CMakeLists.txt +++ b/components/mobileshell/CMakeLists.txt @@ -10,6 +10,7 @@ set(mobileshellplugin_SRCS components/swipearea.cpp notifications/notificationthumbnailer.cpp notifications/notificationfilemenu.cpp + notifications/notificationfileinfo.cpp ) target_include_directories(mobileshellplugin PRIVATE components) target_include_directories(mobileshellplugin PRIVATE notifications) diff --git a/components/mobileshell/notifications/notificationfileinfo.cpp b/components/mobileshell/notifications/notificationfileinfo.cpp new file mode 100644 index 00000000..ffb16941 --- /dev/null +++ b/components/mobileshell/notifications/notificationfileinfo.cpp @@ -0,0 +1,188 @@ +/* + SPDX-FileCopyrightText: 2021 Kai Uwe Broulik + + SPDX-License-Identifier: LGPL-2.1-or-later +*/ + +#include "notificationfileinfo.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +NotificationFileInfo::NotificationFileInfo(QObject *parent) + : QObject(parent) +{ +} + +NotificationFileInfo::~NotificationFileInfo() = default; + +QUrl NotificationFileInfo::url() const +{ + return m_url; +} + +void NotificationFileInfo::setUrl(const QUrl &url) +{ + if (m_url != url) { + m_url = url; + reload(); + Q_EMIT urlChanged(url); + } +} + +bool NotificationFileInfo::busy() const +{ + return m_busy; +} + +void NotificationFileInfo::setBusy(bool busy) +{ + if (m_busy != busy) { + m_busy = busy; + Q_EMIT busyChanged(busy); + } +} + +int NotificationFileInfo::error() const +{ + return m_error; +} + +void NotificationFileInfo::setError(int error) +{ + if (m_error != error) { + m_error = error; + Q_EMIT errorChanged(error); + } +} + +QString NotificationFileInfo::mimeType() const +{ + return m_mimeType; +} + +QString NotificationFileInfo::iconName() const +{ + return m_iconName; +} + +QAction *NotificationFileInfo::openAction() const +{ + return m_openAction; +} + +QString NotificationFileInfo::openActionIconName() const +{ + return m_openAction ? m_openAction->icon().name() : QString(); +} + +void NotificationFileInfo::reload() +{ + if (!m_url.isValid()) { + return; + } + + if (m_job) { + m_job->kill(); + } + + setError(0); + + // Do a quick guess by file name while we wait for the job to find the mime type + QString guessedMimeType; + + // NOTE using QUrl::path() for API that accepts local files is usually wrong + // but here we really only care about the file name and its extension. + const auto type = QMimeDatabase().mimeTypeForFile(m_url.path(), QMimeDatabase::MatchExtension); + if (!type.isDefault()) { + guessedMimeType = type.name(); + } + + mimeTypeFound(guessedMimeType); + + m_job = new KIO::MimeTypeFinderJob(m_url); + m_job->setAuthenticationPromptEnabled(false); + + const QUrl url = m_url; + connect(m_job, &KIO::MimeTypeFinderJob::result, this, [this, url] { + setError(m_job->error()); + if (m_job->error()) { + qWarning() << "Failed to determine mime type for" << url << m_job->errorString(); + } else { + mimeTypeFound(m_job->mimeType()); + } + setBusy(false); + }); + + setBusy(true); + m_job->start(); +} + +void NotificationFileInfo::mimeTypeFound(const QString &mimeType) +{ + if (m_mimeType == mimeType) { + return; + } + + const QString oldOpenActionIconName = openActionIconName(); + + bool emitOpenActionChanged = false; + if (!m_openAction) { + m_openAction = new QAction(this); + connect(m_openAction, &QAction::triggered, this, [this] { + auto *job = new KIO::ApplicationLauncherJob(m_preferredApplication); + if (m_preferredApplication) { + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled)); + } else { + // needs KIO::JobUiDelegate for open with handler + job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled, nullptr /*widget*/)); + } + job->setUrls({m_url}); + job->start(); + }); + emitOpenActionChanged = true; + } + + m_mimeType = mimeType; + + m_preferredApplication.reset(); + + if (!mimeType.isEmpty()) { + const auto type = QMimeDatabase().mimeTypeForName(mimeType); + m_iconName = type.iconName(); + + m_preferredApplication = KApplicationTrader::preferredService(mimeType); + } else { + m_iconName.clear(); + } + + if (m_preferredApplication) { + m_openAction->setText(i18n("Open with %1", m_preferredApplication->name())); + m_openAction->setIcon(QIcon::fromTheme(m_preferredApplication->icon())); + m_openAction->setEnabled(true); + } else { + m_openAction->setText(i18n("Open with…")); + m_openAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); + m_openAction->setEnabled(KAuthorized::authorizeAction(KAuthorized::OPEN_WITH)); + } + + Q_EMIT mimeTypeChanged(); + + if (emitOpenActionChanged) { + Q_EMIT openActionChanged(); + } + if (oldOpenActionIconName != openActionIconName()) { + Q_EMIT openActionIconNameChanged(); + } +} + +#include "moc_notificationfileinfo.cpp" diff --git a/components/mobileshell/notifications/notificationfileinfo.h b/components/mobileshell/notifications/notificationfileinfo.h new file mode 100644 index 00000000..d2678d2c --- /dev/null +++ b/components/mobileshell/notifications/notificationfileinfo.h @@ -0,0 +1,84 @@ +/* + SPDX-FileCopyrightText: 2021 Kai Uwe Broulik + + SPDX-License-Identifier: LGPL-2.1-or-later +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +class QAction; + +namespace KIO +{ +class MimeTypeFinderJob; +} + +class NotificationFileInfo : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) + + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + Q_PROPERTY(int error READ error NOTIFY errorChanged) + + Q_PROPERTY(QString mimeType READ mimeType NOTIFY mimeTypeChanged) + Q_PROPERTY(QString iconName READ iconName NOTIFY mimeTypeChanged) + + Q_PROPERTY(QAction *openAction READ openAction NOTIFY openActionChanged) + // QML can't deal with QIcon... + Q_PROPERTY(QString openActionIconName READ openActionIconName NOTIFY openActionIconNameChanged) + +public: + explicit NotificationFileInfo(QObject *parent = nullptr); + ~NotificationFileInfo() override; + + QUrl url() const; + void setUrl(const QUrl &url); + Q_SIGNAL void urlChanged(const QUrl &url); + + bool busy() const; + Q_SIGNAL void busyChanged(bool busy); + + int error() const; + Q_SIGNAL void errorChanged(bool error); + + QString mimeType() const; + Q_SIGNAL void mimeTypeChanged(); + + QString iconName() const; + Q_SIGNAL void iconNameChanged(const QString &iconName); + + QAction *openAction() const; + Q_SIGNAL void openActionChanged(); + + QString openActionIconName() const; + Q_SIGNAL void openActionIconNameChanged(); + +private: + void reload(); + void mimeTypeFound(const QString &mimeType); + + void setBusy(bool busy); + void setError(int error); + + QUrl m_url; + + QPointer m_job; + bool m_busy = false; + int m_error = 0; + + QString m_mimeType; + QString m_iconName; + + KService::Ptr m_preferredApplication; + QAction *m_openAction = nullptr; +}; diff --git a/components/mobileshell/qml/widgets/notifications/NotificationJobItem.qml b/components/mobileshell/qml/widgets/notifications/NotificationJobItem.qml index 61647702..138f049c 100644 --- a/components/mobileshell/qml/widgets/notifications/NotificationJobItem.qml +++ b/components/mobileshell/qml/widgets/notifications/NotificationJobItem.qml @@ -9,11 +9,10 @@ import QtQml 2.15 import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.private.mobileshell as MobileShell import org.kde.notificationmanager as NotificationManager -import org.kde.plasma.private.notifications 2.0 as Notifications - ColumnLayout { id: jobItem @@ -59,7 +58,7 @@ ColumnLayout { spacing: Kirigami.Units.smallSpacing - Notifications.FileInfo { + MobileShell.NotificationFileInfo { id: fileInfo url: jobItem.totalFiles === 1 && jobItem.url ? jobItem.url : "" } @@ -196,7 +195,7 @@ ColumnLayout { enabled: parent.text === "" } - Notifications.FileMenu { + MobileShell.NotificationFileMenu { id: otherFileActionsMenu url: jobItem.url || "" onActionTriggered: jobItem.fileActionInvoked(action)