diff --git a/qml/AppControls2/qmldir b/qml/AppControls2/qmldir
index 372186a..5570478 100644
--- a/qml/AppControls2/qmldir
+++ b/qml/AppControls2/qmldir
@@ -1,2 +1,4 @@
module AppControls2
AppTest 1.0 App.qml
+StyleBackground 1.0 StyleBackground.qml
+StyleText 1.0 StyleText.qml
diff --git a/qml/extensions/RecentFileExtension.qml b/qml/extensions/RecentFileExtension.qml
new file mode 100644
index 0000000..cc82c86
--- /dev/null
+++ b/qml/extensions/RecentFileExtension.qml
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023, KylinSoft Co., Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+import QtQuick 2.0
+import org.ukui.menu.extension 1.0
+import AppControls2 1.0 as AppControls2
+
+UkuiMenuExtension {
+ Component.onCompleted: {
+ recentFileView.model = extensionData.recentFilesModel
+ extensionData.recentFilesModel.updateData()
+ }
+
+ ListView {
+ id: recentFileView
+ anchors.fill: parent
+ anchors.leftMargin: 12
+ anchors.rightMargin: 6
+ spacing: 4
+
+ delegate: AppControls2.StyleBackground {
+ width: parent.width
+ height: 40
+ useStyleTransparent: false
+ alpha: itemArea.pressed ? 1 : itemArea.hovered ? 0.4 : 0
+
+ Row {
+ anchors.fill: parent
+ anchors.leftMargin: 4
+ spacing: 12
+
+ Image {
+ width: 32
+ height: 32
+ anchors.verticalCenter: parent.verticalCenter
+ source: model.icon
+ }
+
+ AppControls2.StyleText {
+ width: parent.width - x
+ height: 20
+ anchors.verticalCenter: parent.verticalCenter
+ verticalAlignment: Text.AlignVCenter
+ text: model.name
+ }
+ }
+
+ MouseArea {
+ id: itemArea
+ property bool hovered
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: {
+ hovered = true
+ }
+ onExited: {
+ hovered = false
+ }
+ onClicked: {
+ var data = {
+ "url": model.uri
+ }
+ send(data)
+ }
+ }
+ }
+ }
+}
diff --git a/qml/qml.qrc b/qml/qml.qrc
index 49f290d..51b51d4 100644
--- a/qml/qml.qrc
+++ b/qml/qml.qrc
@@ -15,5 +15,6 @@
AppControls2/StyleText.qml
AppControls2/IconLabel.qml
extensions/FolderExtension.qml
+ extensions/RecentFileExtension.qml
diff --git a/src/extension/extensions/recent-file-extension.cpp b/src/extension/extensions/recent-file-extension.cpp
index a6954b9..aaca486 100644
--- a/src/extension/extensions/recent-file-extension.cpp
+++ b/src/extension/extensions/recent-file-extension.cpp
@@ -20,9 +20,17 @@
#include
#include
#include
+#include
+#include
+#include
+#include
#include "recent-file-extension.h"
+#define KYLIN_APP_MANAGER_NAME "com.kylin.AppManager"
+#define KYLIN_APP_MANAGER_PATH "/com/kylin/AppManager"
+#define KYLIN_APP_MANAGER_INTERFACE "com.kylin.AppManager"
+
namespace UkuiMenu {
// GVFS 最近文件获取工具
@@ -31,19 +39,19 @@ class GVFSRecentFileData
public:
static int s_queryFileNum;
static GCancellable *s_cancellable;
- static void loadRecentFileASync(RecentFileExtension *p_extension);
+ static void loadRecentFileASync(RecentFileProvider *p_recentFileProvider);
private:
static GFile *s_recentFileRootDir;
- static GAsyncReadyCallback enumerateFinish(GFile *file, GAsyncResult *res, RecentFileExtension *p_extension);
- static GAsyncReadyCallback parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileExtension *p_extension);
+ static GAsyncReadyCallback enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
+ static GAsyncReadyCallback parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
};
int GVFSRecentFileData::s_queryFileNum = 100;
GCancellable *GVFSRecentFileData::s_cancellable = g_cancellable_new();
GFile *GVFSRecentFileData::s_recentFileRootDir = g_file_new_for_uri("recent:///");
-void GVFSRecentFileData::loadRecentFileASync(RecentFileExtension *p_extension)
+void GVFSRecentFileData::loadRecentFileASync(RecentFileProvider *p_recentFileProvider)
{
if (!s_recentFileRootDir) {
qWarning() << "Can not find 'recent:///' dir.";
@@ -54,11 +62,11 @@ void GVFSRecentFileData::loadRecentFileASync(RecentFileExtension *p_extension)
"*",
G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
s_cancellable, GAsyncReadyCallback(enumerateFinish),
- p_extension);
+ p_recentFileProvider);
}
GAsyncReadyCallback
-GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileExtension *p_extension)
+GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
{
GError *error = nullptr;
GFileEnumerator *enumerator = g_file_enumerate_children_finish(file, res, &error);
@@ -69,7 +77,7 @@ GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileEx
}
g_file_enumerator_next_files_async(enumerator, s_queryFileNum, G_PRIORITY_DEFAULT,
- s_cancellable, GAsyncReadyCallback(parseRecentFiles), p_extension);
+ s_cancellable, GAsyncReadyCallback(parseRecentFiles), p_recentFileProvider);
g_object_unref(enumerator);
@@ -77,7 +85,7 @@ GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileEx
}
GAsyncReadyCallback
-GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileExtension *p_extension)
+GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
{
GError *error = nullptr;
GList *fileList = g_file_enumerator_next_files_finish(enumerator, res, &error);
@@ -118,7 +126,7 @@ GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *
auto iconNameIterator = iconNames;
while(*iconNameIterator) {
if(QIcon::hasThemeIcon(*iconNameIterator)) {
- recentFile.icon = *iconNameIterator;
+ recentFile.icon = "image://appicon/" + QString(*iconNameIterator);
break;
} else {
++iconNameIterator;
@@ -129,48 +137,264 @@ GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *
}
if (recentFile.icon.isEmpty()) {
- recentFile.icon = "text-plain";
+ recentFile.icon = "image://appicon/text-plain";
}
recentFiles.append(recentFile);
g_object_unref(info);
listIterator = listIterator->next;
}
-
g_list_free(fileList);
+ p_recentFileProvider->dataProcess(recentFiles);
+
return nullptr;
}
// RecentFileExtension
RecentFileExtension::RecentFileExtension(QObject *parent) : MenuExtensionIFace(parent)
{
+ qRegisterMetaType("RecentFilesModel*");
+ qRegisterMetaType >("QVector");
+ m_recentFilesProviderThread = new QThread(this);
+ m_recentFilesModel = new RecentFilesModel(this);
+ m_recentFileProvider = new RecentFileProvider();
+
+ if ((m_recentFilesProviderThread == nullptr) || (m_recentFilesModel == nullptr) || (m_recentFileProvider == nullptr)) {
+ qWarning() << "recentfile construction error" ;
+ return;
+ }
+
+ m_recentFilesProviderThread->start();
+ m_recentFileProvider->moveToThread(m_recentFilesProviderThread);
+
+ connect(this, &RecentFileExtension::loadRecentFiles, m_recentFileProvider, &RecentFileProvider::getRecentData);
+ connect(m_recentFilesModel, &RecentFilesModel::updateRecentData, m_recentFileProvider, &RecentFileProvider::getRecentData);
+ connect(m_recentFileProvider, &RecentFileProvider::dataLoadCompleted, m_recentFilesModel, &RecentFilesModel::updateRecentFiles);
+ connect(this, &RecentFileExtension::openFileASync, m_recentFileProvider, &RecentFileProvider::openFileByGFile);
+
+ m_data.insert("recentFilesModel", QVariant::fromValue(m_recentFilesModel));
+ initFileDbus();
+
+ Q_EMIT loadRecentFiles();
+}
+
+RecentFileExtension::~RecentFileExtension()
+{
+ if (m_recentFilesProviderThread) {
+ m_recentFilesProviderThread->quit();
+ m_recentFilesProviderThread->wait();
+ }
+
+ if (m_recentFileProvider) {
+ delete m_recentFileProvider;
+ m_recentFileProvider = nullptr;
+ }
}
int RecentFileExtension::index()
{
- return 0;
+ return 1;
}
QString RecentFileExtension::name()
{
- return {};
+ return tr("Recent Files");
}
QUrl RecentFileExtension::url()
{
- return {};
+ return {"qrc:///qml/extensions/RecentFileExtension.qml"};
}
QVariantMap RecentFileExtension::data()
{
- return {};
+ return m_data;
}
void RecentFileExtension::receive(QVariantMap data)
+{
+ QString path = data.value("url").toString();
+ if (!openFile(path)) {
+ Q_EMIT openFileASync(path);
+ }
+}
+
+void RecentFileExtension::initFileDbus()
+{
+ m_appManagerDbusInterface = new QDBusInterface(KYLIN_APP_MANAGER_NAME,
+ KYLIN_APP_MANAGER_PATH,
+ KYLIN_APP_MANAGER_INTERFACE,
+ QDBusConnection::sessionBus());
+
+ if (!m_appManagerDbusInterface) {
+ qWarning() << "recentfile open failed: appmanager dbus does not exists.";
+ }
+}
+
+bool RecentFileExtension::openFile(const QString &desktopFile)
+{
+ if (m_appManagerDbusInterface != nullptr) {
+ QDBusReply status = m_appManagerDbusInterface->call("LaunchApp", desktopFile);
+ return status;
+ } else {
+ qWarning()<<"LaunchApp is failed,return false";
+ return false;
+ }
+}
+
+RecentFilesModel::RecentFilesModel(QObject *parent) : QAbstractListModel(parent)
{
}
+int RecentFilesModel::rowCount(const QModelIndex &parent) const
+{
+ return m_recentFileData.count();
+}
+
+QVariant RecentFilesModel::data(const QModelIndex &index, int role) const
+{
+ int row = index.row();
+ if (row < 0 || row >= m_recentFileData.count()) {
+ return {};
+ }
+
+ switch (role) {
+ case UriRole:
+ return m_recentFileData.at(row).uri;
+ case NameRole:
+ return m_recentFileData.at(row).name;
+ case IconRole:
+ return m_recentFileData.at(row).icon;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+QHash RecentFilesModel::roleNames() const
+{
+ QHash names;
+ names.insert(UriRole, "uri");
+ names.insert(NameRole, "name");
+ names.insert(IconRole, "icon");
+ return names;
+}
+
+void RecentFilesModel::updateData()
+{
+ Q_EMIT updateRecentData();
+}
+
+void RecentFilesModel::updateRecentFiles(QVector recentFiles)
+{
+ beginResetModel();
+ m_recentFileData.swap(recentFiles);
+ endResetModel();
+}
+
+RecentFileProvider::RecentFileProvider(QObject *parent) : QObject(parent)
+{
+
+}
+
+void RecentFileProvider::dataProcess(QVector &recentFiles)
+{
+ std::sort(recentFiles.begin(), recentFiles.end(), [](const RecentFile &a, const RecentFile &b) {
+ return a.accessTime > b.accessTime;
+ });
+
+ Q_EMIT dataLoadCompleted(recentFiles);
+}
+
+void RecentFileProvider::getRecentData()
+{
+ GVFSRecentFileData::loadRecentFileASync(this);
+}
+
+void RecentFileProvider::openFileByGFile(const QString &path)
+{
+ GFile *file = g_file_new_for_uri(path.toUtf8().constData());
+ if (!file) {
+ return;
+ }
+
+ GFileInfo *fileInfo = g_file_query_info(file,
+ "standard::*," "time::*," "access::*," "mountable::*," "metadata::*," "trash::*," G_FILE_ATTRIBUTE_ID_FILE,
+ G_FILE_QUERY_INFO_NONE,
+ nullptr,
+ nullptr);
+ if (!fileInfo) {
+ return;
+ }
+
+ QString mimeType(g_file_info_get_content_type(fileInfo));
+ if (mimeType.isEmpty()) {
+ if (g_file_info_has_attribute(fileInfo, "standard::fast-content-type")) {
+ mimeType = g_file_info_get_attribute_string(fileInfo, "standard::fast-content-type");
+ }
+ }
+
+ GError *error = NULL;
+ GAppInfo *info = NULL;
+
+ QString mimeAppsListPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/mimeapps.list";
+ GKeyFile *keyfile = g_key_file_new();
+ gboolean ret = g_key_file_load_from_file(keyfile, mimeAppsListPath.toUtf8(), G_KEY_FILE_NONE, &error);
+
+ if (!ret) {
+ qWarning()<< "load mimeapps list error msg" << error->message;
+ info = g_app_info_get_default_for_type(mimeType.toUtf8().constData(), false);
+ g_error_free(error);
+ }
+ else {
+ gchar *desktopApp = g_key_file_get_string(keyfile, "Default Applications", mimeType.toUtf8(), &error);
+
+ if (NULL != desktopApp) {
+ info = (GAppInfo*)g_desktop_app_info_new(desktopApp);
+ g_free (desktopApp);
+ }
+ else {
+ info = g_app_info_get_default_for_type(mimeType.toUtf8().constData(), false);
+ }
+ }
+
+ g_key_file_free (keyfile);
+
+ if(G_IS_APP_INFO(info)) {
+ bool isSuccess(false);
+ QDBusInterface * appLaunchInterface = new QDBusInterface(KYLIN_APP_MANAGER_NAME,
+ KYLIN_APP_MANAGER_PATH,
+ KYLIN_APP_MANAGER_INTERFACE,
+ QDBusConnection::sessionBus());
+ if(!appLaunchInterface->isValid()) {
+ qWarning() << qPrintable(QDBusConnection::sessionBus().lastError().message());
+ isSuccess = false;
+ }
+ else {
+ appLaunchInterface->setTimeout(10000);
+ QDBusReply reply = appLaunchInterface->call("LaunchDefaultAppWithUrl", path);
+ if(reply.isValid()) {
+ isSuccess = reply;
+ }
+ else {
+ qWarning() << "recentfile used appmanager dbus called failed!";
+ isSuccess = false;
+ }
+ }
+ if(appLaunchInterface) {
+ delete appLaunchInterface;
+ }
+ appLaunchInterface = NULL;
+ if (!isSuccess){
+ QDesktopServices::openUrl(path);
+ }
+ }
+
+ g_object_unref(info);
+}
+
} // UkuiMenu
diff --git a/src/extension/extensions/recent-file-extension.h b/src/extension/extensions/recent-file-extension.h
index f78f8f0..8c46e4b 100644
--- a/src/extension/extensions/recent-file-extension.h
+++ b/src/extension/extensions/recent-file-extension.h
@@ -19,12 +19,26 @@
#ifndef UKUI_MENU_RECENT_FILE_EXTENSION_H
#define UKUI_MENU_RECENT_FILE_EXTENSION_H
+#include
+#include
+#include
+
#include "../menu-extension-iface.h"
namespace UkuiMenu {
class RecentFile
{
+ Q_GADGET
+ Q_PROPERTY(QString uri READ getUri)
+ Q_PROPERTY(QString name READ getName)
+ Q_PROPERTY(QString icon READ getIcon)
+
+public:
+ QString getUri() { return uri; }
+ QString getName() { return name; }
+ QString getIcon() { return icon; }
+
public:
quint64 accessTime{0};
QString uri;
@@ -32,16 +46,76 @@ public:
QString icon;
};
+class RecentFileProvider : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RecentFileProvider(QObject *parent = nullptr);
+ void dataProcess(QVector &recentFiles);
+
+public Q_SLOT:
+ void getRecentData();
+ void openFileByGFile(const QString &path);
+
+Q_SIGNALS:
+ void dataLoadCompleted(QVector recentFiles);
+};
+
+
+class RecentFilesModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ enum RoleMessage {
+ UriRole = Qt::UserRole,
+ NameRole = Qt::UserRole + 1,
+ IconRole = Qt::UserRole + 2,
+ };
+
+ explicit RecentFilesModel(QObject *parent = nullptr);
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ QHash roleNames() const override;
+
+ Q_INVOKABLE void updateData();
+
+public Q_SLOT:
+ void updateRecentFiles(QVector recentFiles);
+
+private:
+ QVector m_recentFileData;
+
+Q_SIGNALS:
+ void updateRecentData();
+};
+
+
class RecentFileExtension : public MenuExtensionIFace
{
Q_OBJECT
public:
explicit RecentFileExtension(QObject *parent = nullptr);
+ ~RecentFileExtension();
int index() override;
QString name() override;
QUrl url() override;
QVariantMap data() override;
void receive(QVariantMap data) override;
+
+private:
+ QVector m_recentFile;
+ QVariantMap m_data;
+ QDBusInterface *m_appManagerDbusInterface = nullptr;
+ RecentFilesModel *m_recentFilesModel = nullptr;
+ QThread *m_recentFilesProviderThread = nullptr;
+ RecentFileProvider *m_recentFileProvider = nullptr;
+
+ void initFileDbus();
+ bool openFile(const QString& desktopFile);
+
+Q_SIGNALS:
+ void loadRecentFiles();
+ void openFileASync(const QString &path);
};
} // UkuiMenu
diff --git a/src/extension/menu-extension.cpp b/src/extension/menu-extension.cpp
index 25c4ab6..0eb3057 100644
--- a/src/extension/menu-extension.cpp
+++ b/src/extension/menu-extension.cpp
@@ -18,6 +18,7 @@
#include "menu-extension.h"
#include "extensions/folder-extension.h"
+#include "extensions/recent-file-extension.h"
#include
#include
@@ -38,6 +39,7 @@ MenuExtension::MenuExtension()
// register extension.
registerExtension(new FolderExtension(this));
+ registerExtension(new RecentFileExtension(this));
initModel();
}