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(); }