添加最近文件功能

This commit is contained in:
gjq 2023-01-17 10:53:00 +08:00 committed by hewenfei
parent 2efc02d651
commit f276cbe74b
6 changed files with 401 additions and 15 deletions

View File

@ -1,2 +1,4 @@
module AppControls2 module AppControls2
AppTest 1.0 App.qml AppTest 1.0 App.qml
StyleBackground 1.0 StyleBackground.qml
StyleText 1.0 StyleText.qml

View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
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)
}
}
}
}
}

View File

@ -15,5 +15,6 @@
<file>AppControls2/StyleText.qml</file> <file>AppControls2/StyleText.qml</file>
<file>AppControls2/IconLabel.qml</file> <file>AppControls2/IconLabel.qml</file>
<file>extensions/FolderExtension.qml</file> <file>extensions/FolderExtension.qml</file>
<file>extensions/RecentFileExtension.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -20,9 +20,17 @@
#include <QIcon> #include <QIcon>
#include <QDebug> #include <QDebug>
#include <QDateTime> #include <QDateTime>
#include <QDBusReply>
#include <QStandardPaths>
#include <gio/gdesktopappinfo.h>
#include <QDesktopServices>
#include "recent-file-extension.h" #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 { namespace UkuiMenu {
// GVFS 最近文件获取工具 // GVFS 最近文件获取工具
@ -31,19 +39,19 @@ class GVFSRecentFileData
public: public:
static int s_queryFileNum; static int s_queryFileNum;
static GCancellable *s_cancellable; static GCancellable *s_cancellable;
static void loadRecentFileASync(RecentFileExtension *p_extension); static void loadRecentFileASync(RecentFileProvider *p_recentFileProvider);
private: private:
static GFile *s_recentFileRootDir; static GFile *s_recentFileRootDir;
static GAsyncReadyCallback enumerateFinish(GFile *file, GAsyncResult *res, RecentFileExtension *p_extension); static GAsyncReadyCallback enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
static GAsyncReadyCallback parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileExtension *p_extension); static GAsyncReadyCallback parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
}; };
int GVFSRecentFileData::s_queryFileNum = 100; int GVFSRecentFileData::s_queryFileNum = 100;
GCancellable *GVFSRecentFileData::s_cancellable = g_cancellable_new(); GCancellable *GVFSRecentFileData::s_cancellable = g_cancellable_new();
GFile *GVFSRecentFileData::s_recentFileRootDir = g_file_new_for_uri("recent:///"); 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) { if (!s_recentFileRootDir) {
qWarning() << "Can not find 'recent:///' dir."; qWarning() << "Can not find 'recent:///' dir.";
@ -54,11 +62,11 @@ void GVFSRecentFileData::loadRecentFileASync(RecentFileExtension *p_extension)
"*", "*",
G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
s_cancellable, GAsyncReadyCallback(enumerateFinish), s_cancellable, GAsyncReadyCallback(enumerateFinish),
p_extension); p_recentFileProvider);
} }
GAsyncReadyCallback GAsyncReadyCallback
GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileExtension *p_extension) GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
{ {
GError *error = nullptr; GError *error = nullptr;
GFileEnumerator *enumerator = g_file_enumerate_children_finish(file, res, &error); 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, 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); g_object_unref(enumerator);
@ -77,7 +85,7 @@ GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileEx
} }
GAsyncReadyCallback GAsyncReadyCallback
GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileExtension *p_extension) GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
{ {
GError *error = nullptr; GError *error = nullptr;
GList *fileList = g_file_enumerator_next_files_finish(enumerator, res, &error); GList *fileList = g_file_enumerator_next_files_finish(enumerator, res, &error);
@ -118,7 +126,7 @@ GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *
auto iconNameIterator = iconNames; auto iconNameIterator = iconNames;
while(*iconNameIterator) { while(*iconNameIterator) {
if(QIcon::hasThemeIcon(*iconNameIterator)) { if(QIcon::hasThemeIcon(*iconNameIterator)) {
recentFile.icon = *iconNameIterator; recentFile.icon = "image://appicon/" + QString(*iconNameIterator);
break; break;
} else { } else {
++iconNameIterator; ++iconNameIterator;
@ -129,48 +137,264 @@ GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *
} }
if (recentFile.icon.isEmpty()) { if (recentFile.icon.isEmpty()) {
recentFile.icon = "text-plain"; recentFile.icon = "image://appicon/text-plain";
} }
recentFiles.append(recentFile); recentFiles.append(recentFile);
g_object_unref(info); g_object_unref(info);
listIterator = listIterator->next; listIterator = listIterator->next;
} }
g_list_free(fileList); g_list_free(fileList);
p_recentFileProvider->dataProcess(recentFiles);
return nullptr; return nullptr;
} }
// RecentFileExtension // RecentFileExtension
RecentFileExtension::RecentFileExtension(QObject *parent) : MenuExtensionIFace(parent) RecentFileExtension::RecentFileExtension(QObject *parent) : MenuExtensionIFace(parent)
{ {
qRegisterMetaType<UkuiMenu::RecentFilesModel*>("RecentFilesModel*");
qRegisterMetaType<QVector<RecentFile> >("QVector<RecentFile>");
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() int RecentFileExtension::index()
{ {
return 0; return 1;
} }
QString RecentFileExtension::name() QString RecentFileExtension::name()
{ {
return {}; return tr("Recent Files");
} }
QUrl RecentFileExtension::url() QUrl RecentFileExtension::url()
{ {
return {}; return {"qrc:///qml/extensions/RecentFileExtension.qml"};
} }
QVariantMap RecentFileExtension::data() QVariantMap RecentFileExtension::data()
{ {
return {}; return m_data;
} }
void RecentFileExtension::receive(QVariantMap 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<bool> 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<int, QByteArray> RecentFilesModel::roleNames() const
{
QHash<int, QByteArray> 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<RecentFile> recentFiles)
{
beginResetModel();
m_recentFileData.swap(recentFiles);
endResetModel();
}
RecentFileProvider::RecentFileProvider(QObject *parent) : QObject(parent)
{
}
void RecentFileProvider::dataProcess(QVector<RecentFile> &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<bool> 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 } // UkuiMenu

View File

@ -19,12 +19,26 @@
#ifndef UKUI_MENU_RECENT_FILE_EXTENSION_H #ifndef UKUI_MENU_RECENT_FILE_EXTENSION_H
#define UKUI_MENU_RECENT_FILE_EXTENSION_H #define UKUI_MENU_RECENT_FILE_EXTENSION_H
#include <QThread>
#include <QDBusInterface>
#include <QAbstractListModel>
#include "../menu-extension-iface.h" #include "../menu-extension-iface.h"
namespace UkuiMenu { namespace UkuiMenu {
class RecentFile 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: public:
quint64 accessTime{0}; quint64 accessTime{0};
QString uri; QString uri;
@ -32,16 +46,76 @@ public:
QString icon; QString icon;
}; };
class RecentFileProvider : public QObject
{
Q_OBJECT
public:
explicit RecentFileProvider(QObject *parent = nullptr);
void dataProcess(QVector<RecentFile> &recentFiles);
public Q_SLOT:
void getRecentData();
void openFileByGFile(const QString &path);
Q_SIGNALS:
void dataLoadCompleted(QVector<RecentFile> 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<int, QByteArray> roleNames() const override;
Q_INVOKABLE void updateData();
public Q_SLOT:
void updateRecentFiles(QVector<RecentFile> recentFiles);
private:
QVector<RecentFile> m_recentFileData;
Q_SIGNALS:
void updateRecentData();
};
class RecentFileExtension : public MenuExtensionIFace class RecentFileExtension : public MenuExtensionIFace
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit RecentFileExtension(QObject *parent = nullptr); explicit RecentFileExtension(QObject *parent = nullptr);
~RecentFileExtension();
int index() override; int index() override;
QString name() override; QString name() override;
QUrl url() override; QUrl url() override;
QVariantMap data() override; QVariantMap data() override;
void receive(QVariantMap data) override; void receive(QVariantMap data) override;
private:
QVector<RecentFile> 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 } // UkuiMenu

View File

@ -18,6 +18,7 @@
#include "menu-extension.h" #include "menu-extension.h"
#include "extensions/folder-extension.h" #include "extensions/folder-extension.h"
#include "extensions/recent-file-extension.h"
#include <utility> #include <utility>
#include <QDebug> #include <QDebug>
@ -38,6 +39,7 @@ MenuExtension::MenuExtension()
// register extension. // register extension.
registerExtension(new FolderExtension(this)); registerExtension(new FolderExtension(this));
registerExtension(new RecentFileExtension(this));
initModel(); initModel();
} }