Merge tag 'upstream/4.0.0.6' into openkylin/yangtze
4.0.0.6-release
This commit is contained in:
commit
d51504c171
|
@ -0,0 +1,58 @@
|
|||
# C++ objects and libs
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.a
|
||||
*.la
|
||||
*.lai
|
||||
*.so
|
||||
*.so.*
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
# Qt-es
|
||||
object_script.*.Release
|
||||
object_script.*.Debug
|
||||
*_plugin_import.cpp
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
*.qbs.user.*
|
||||
*.moc
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
qrc_*.cpp
|
||||
ui_*.h
|
||||
*.qmlc
|
||||
*.jsc
|
||||
Makefile*
|
||||
*build-*
|
||||
*.qm
|
||||
*.prl
|
||||
|
||||
# Qt unit tests
|
||||
target_wrapper.*
|
||||
|
||||
# QtCreator
|
||||
*.autosave
|
||||
|
||||
# QtCreator Qml
|
||||
*.qmlproject.user
|
||||
*.qmlproject.user.*
|
||||
|
||||
# QtCreator CMake
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# QtCreator 4.8< compilation database
|
||||
compile_commands.json
|
||||
|
||||
# QtCreator local machine specific files for imported projects
|
||||
*creator.user*
|
||||
|
||||
*_qmlcache.qrc
|
||||
|
||||
*.idea
|
||||
build
|
||||
*-build-*
|
|
@ -79,9 +79,6 @@ set(UKUI_MENU_LIBRARY_NAME "ukui-menu-interface")
|
|||
set(PC_INSTALL_DIR "/usr/lib/pkgconfig")
|
||||
set(CMAKE_CONFIG_INSTALL_DIR "/usr/share/cmake/${UKUI_MENU_LIBRARY_NAME}")
|
||||
|
||||
# 子项目
|
||||
add_subdirectory(extension/recent-file)
|
||||
|
||||
# 宏定义
|
||||
add_compile_definitions(UKUI_MENU_TRANSLATION_DIR="${UKUI_MENU_TRANSLATION_DIR}"
|
||||
UKUI_MENU_DATA_DIR="${UKUI_MENU_DATA_DIR}"
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
project(recent-file-extension LANGUAGES CXX)
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(Qt5 COMPONENTS
|
||||
Core Gui Widgets Quick LinguistTools DBus
|
||||
REQUIRED)
|
||||
|
||||
include_directories(../../src/extension)
|
||||
set(SOURCE recent-file-extension.cpp recent-file-extension.h)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
set(EXTERNAL_LIBS "")
|
||||
set(PC_PKGS glib-2.0 gio-unix-2.0)
|
||||
|
||||
foreach(lib IN ITEMS ${PC_PKGS})
|
||||
pkg_check_modules(${lib} REQUIRED ${lib})
|
||||
if(${${lib}_FOUND})
|
||||
include_directories(${${lib}_INCLUDE_DIRS})
|
||||
link_directories(${${lib}_LIBRARY_DIRS})
|
||||
list(APPEND EXTERNAL_LIBS ${${lib}_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(QRC_FILES qml/qml.qrc)
|
||||
# 翻译文件
|
||||
file(GLOB TS_FILES "translations/*.ts")
|
||||
# 更新翻译并创建.qm文件
|
||||
qt5_create_translation(QM_FILES ${PROJECT_SOURCE_DIR} ${TS_FILES})
|
||||
|
||||
set(RECENT_FILE_TRANSLATION_DIR "${UKUI_MENU_TRANSLATION_DIR}/${PROJECT_NAME}")
|
||||
add_compile_definitions(RECENT_FILE_TRANSLATION_DIR="${RECENT_FILE_TRANSLATION_DIR}")
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCE} ${QM_FILES} ${QRC_FILES})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Quick Qt5::DBus
|
||||
${EXTERNAL_LIBS}
|
||||
${UKUI_MENU_LIBRARY_NAME}
|
||||
)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION "${UKUI_MENU_EXTENSION_DIR}")
|
||||
install(FILES ${QM_FILES} DESTINATION "${RECENT_FILE_TRANSLATION_DIR}")
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"Type": "UKUI_MENU_EXTENSION",
|
||||
"Version": "1.0.1"
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* 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 QtQuick.Controls 2.5
|
||||
import org.ukui.menu.extension 1.0
|
||||
import org.ukui.menu.core 1.0
|
||||
import AppControls2 1.0 as AppControls2
|
||||
|
||||
UkuiMenuExtension {
|
||||
Component.onCompleted: {
|
||||
recentFileView.model = extensionData.recentFilesModel
|
||||
extensionData.recentFilesModel.updateData()
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
scrollBar.visible = true
|
||||
}
|
||||
else {
|
||||
scrollBar.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
var data = {
|
||||
"action": "closeMenu"
|
||||
}
|
||||
send(data);
|
||||
}
|
||||
|
||||
AppControls2.StyleBackground {
|
||||
anchors.top: parent.top
|
||||
width: parent.width; height: 1
|
||||
useStyleTransparent: false
|
||||
alpha: 0.15
|
||||
paletteRole: Palette.Text
|
||||
visible: recentFileView.contentY > 0
|
||||
z: 1
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: recentFileView
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
spacing: 4
|
||||
focus: true
|
||||
highlightMoveDuration: 0
|
||||
onActiveFocusChanged: currentIndex = 0
|
||||
onCountChanged: currentIndex = 0
|
||||
keyNavigationWraps: true
|
||||
|
||||
ScrollBar.vertical: AppControls2.ScrollBar {
|
||||
id: scrollBar
|
||||
visible: false
|
||||
width: 14
|
||||
height: recentFileView.height
|
||||
}
|
||||
|
||||
delegate: AppControls2.StyleBackground {
|
||||
id: delegateItem
|
||||
width: ListView.view.width - 18
|
||||
height: 40
|
||||
useStyleTransparent: false
|
||||
alpha: itemArea.pressed ? 1 : itemArea.hovered ? 0.65 : 0
|
||||
radius: 8
|
||||
focus: true
|
||||
|
||||
states: State {
|
||||
when: delegateItem.activeFocus
|
||||
PropertyChanges {
|
||||
target: delegateItem
|
||||
alpha: 0.65
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 4
|
||||
spacing: 12
|
||||
|
||||
Image {
|
||||
width: 32
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: model.icon
|
||||
}
|
||||
|
||||
AppControls2.StyleText {
|
||||
id: fileText
|
||||
width: parent.width - x
|
||||
height: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
ToolTip {
|
||||
visible: itemArea.containsMouse
|
||||
delay: 500
|
||||
contentItem: Column {
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
Text {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: fileText.truncated
|
||||
wrapMode: Text.WrapAnywhere
|
||||
text: model.name
|
||||
Component.onCompleted: {
|
||||
if (contentWidth > recentFileView.width) {
|
||||
width = recentFileView.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Path: ") + model.path
|
||||
Component.onCompleted: {
|
||||
if (contentWidth > recentFileView.width) {
|
||||
width = recentFileView.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
property bool hovered
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
hovered = true
|
||||
}
|
||||
onExited: {
|
||||
hovered = false
|
||||
}
|
||||
onClicked: {
|
||||
var data = {
|
||||
"url": model.uri,
|
||||
"index": model.index
|
||||
}
|
||||
data["action"] = mouse.button === Qt.RightButton ? "right" : "openFile"
|
||||
send(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<RCC>
|
||||
<qresource prefix="/extensions">
|
||||
<file>RecentFileExtension.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -1,591 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022, 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QDBusReply>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
#include <QDesktopServices>
|
||||
#include <QMenu>
|
||||
#include <QDir>
|
||||
#include <QTranslator>
|
||||
#include <QCoreApplication>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusMessage>
|
||||
#include <QThread>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#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"
|
||||
#define FREEDESKTOP_FILEMANAGER_NAME "org.freedesktop.FileManager1"
|
||||
#define FREEDESKTOP_FILEMANAGER_PATH "/org/freedesktop/FileManager1"
|
||||
#define FREEDESKTOP_FILEMANAGER_INTERFACE "org.freedesktop.FileManager1"
|
||||
|
||||
namespace UkuiMenu {
|
||||
|
||||
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 &fileUrl);
|
||||
|
||||
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,
|
||||
PathRole = Qt::UserRole + 3
|
||||
};
|
||||
|
||||
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;
|
||||
QString getInfoId(const int &index);
|
||||
QStringList getAllInfoId();
|
||||
Q_INVOKABLE void updateData();
|
||||
|
||||
public Q_SLOT:
|
||||
void updateRecentFiles(QVector<RecentFile> recentFiles);
|
||||
|
||||
private:
|
||||
QVector<RecentFile> m_recentFileData;
|
||||
|
||||
Q_SIGNALS:
|
||||
void updateRecentData();
|
||||
};
|
||||
|
||||
// GVFS 最近文件获取工具
|
||||
class GVFSRecentFileData
|
||||
{
|
||||
public:
|
||||
static int s_queryFileNum;
|
||||
static GFile *s_recentFileRootDir;
|
||||
static GFileMonitor *s_recentFileMonitor;
|
||||
static void loadRecentFileASync(RecentFileProvider *p_recentFileProvider);
|
||||
static void fileMonitor(RecentFileProvider *p_recentFileProvider);
|
||||
static void removeRecentFileByInfoId(const QString &infoId);
|
||||
|
||||
private:
|
||||
static GAsyncReadyCallback enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
|
||||
static GAsyncReadyCallback parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
|
||||
static void fileChangedCallback(GFileMonitor *monitor,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type,
|
||||
RecentFileProvider *p_recentFileProvider);
|
||||
};
|
||||
|
||||
int GVFSRecentFileData::s_queryFileNum = 100;
|
||||
GFile *GVFSRecentFileData::s_recentFileRootDir = g_file_new_for_uri("recent:///");
|
||||
GFileMonitor *GVFSRecentFileData::s_recentFileMonitor = nullptr;
|
||||
|
||||
void GVFSRecentFileData::fileMonitor(RecentFileProvider *p_recentFileProvider)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
s_recentFileMonitor = g_file_monitor_directory(GVFSRecentFileData::s_recentFileRootDir,
|
||||
G_FILE_MONITOR_NONE,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
qWarning() << "recentFile monitor creat error";
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect(s_recentFileMonitor, "changed", G_CALLBACK(fileChangedCallback), p_recentFileProvider);
|
||||
}
|
||||
|
||||
void GVFSRecentFileData::fileChangedCallback(GFileMonitor *monitor,
|
||||
GFile *file,
|
||||
GFile *other_file,
|
||||
GFileMonitorEvent event_type,
|
||||
RecentFileProvider *p_recentFileProvider)
|
||||
{
|
||||
Q_UNUSED(monitor);
|
||||
Q_UNUSED(file);
|
||||
Q_UNUSED(other_file);
|
||||
|
||||
switch (event_type) {
|
||||
case G_FILE_MONITOR_EVENT_DELETED:
|
||||
case G_FILE_MONITOR_EVENT_CREATED: {
|
||||
loadRecentFileASync(p_recentFileProvider);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GVFSRecentFileData::removeRecentFileByInfoId(const QString &infoId)
|
||||
{
|
||||
GFile *file = g_file_new_for_uri(infoId.toUtf8().constData());
|
||||
GError *err = nullptr;
|
||||
|
||||
g_file_delete(file, nullptr, &err);
|
||||
|
||||
g_object_unref(file);
|
||||
if (err) {
|
||||
qWarning() << "Recentfile Delete Error";
|
||||
}
|
||||
}
|
||||
|
||||
void GVFSRecentFileData::loadRecentFileASync(RecentFileProvider *p_recentFileProvider)
|
||||
{
|
||||
if (!s_recentFileRootDir) {
|
||||
qWarning() << "Can not find 'recent:///' dir.";
|
||||
return;
|
||||
}
|
||||
|
||||
g_file_enumerate_children_async(s_recentFileRootDir,
|
||||
"*",
|
||||
G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
|
||||
nullptr, GAsyncReadyCallback(enumerateFinish),
|
||||
p_recentFileProvider);
|
||||
}
|
||||
|
||||
GAsyncReadyCallback
|
||||
GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
GFileEnumerator *enumerator = g_file_enumerate_children_finish(file, res, &error);
|
||||
if (error) {
|
||||
qWarning() << "GVFSRecentFileData::enumerateFinish Error:" << error->message;
|
||||
g_error_free(error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_file_enumerator_next_files_async(enumerator, s_queryFileNum, G_PRIORITY_DEFAULT,
|
||||
nullptr, GAsyncReadyCallback(parseRecentFiles), p_recentFileProvider);
|
||||
|
||||
g_object_unref(enumerator);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GAsyncReadyCallback
|
||||
GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res,
|
||||
RecentFileProvider *p_recentFileProvider)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
GList *fileList = g_file_enumerator_next_files_finish(enumerator, res, &error);
|
||||
if (error) {
|
||||
qWarning() << "GVFSRecentFileData::parseRecentFiles Error:" << error->message;
|
||||
g_error_free(error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QVector<RecentFile> recentFiles;
|
||||
if (!fileList) {
|
||||
p_recentFileProvider->dataProcess(recentFiles);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto listIterator = fileList;
|
||||
while (listIterator) {
|
||||
RecentFile recentFile;
|
||||
GFileInfo *info = static_cast<GFileInfo *>(listIterator->data);
|
||||
GFile *file = g_file_enumerator_get_child(enumerator, info);
|
||||
recentFile.infoId = g_file_get_uri(file);
|
||||
g_object_unref(file);
|
||||
|
||||
char *attribute = g_file_info_get_attribute_as_string(info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||
if (attribute) {
|
||||
recentFile.uri = attribute;
|
||||
g_free(attribute);
|
||||
}
|
||||
|
||||
const char *fileName = g_file_info_get_display_name(info);
|
||||
if (fileName) {
|
||||
recentFile.name = fileName;
|
||||
}
|
||||
|
||||
// in seconds since the UNIX epoch.
|
||||
recentFile.accessTime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_ACCESS);
|
||||
|
||||
GIcon *icon = g_file_info_get_icon(info);
|
||||
if (icon) {
|
||||
const gchar *const *iconNames = g_themed_icon_get_names(G_THEMED_ICON(icon));
|
||||
if (iconNames) {
|
||||
auto iconNameIterator = iconNames;
|
||||
while (*iconNameIterator) {
|
||||
if (QIcon::hasThemeIcon(*iconNameIterator)) {
|
||||
recentFile.icon = "image://appicon/" + QString(*iconNameIterator);
|
||||
break;
|
||||
} else {
|
||||
++iconNameIterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_object_unref(icon);
|
||||
}
|
||||
|
||||
if (recentFile.icon.isEmpty()) {
|
||||
recentFile.icon = "image://appicon/text-plain";
|
||||
}
|
||||
|
||||
recentFiles.append(recentFile);
|
||||
info = nullptr;
|
||||
listIterator = listIterator->next;
|
||||
}
|
||||
g_list_free(fileList);
|
||||
|
||||
p_recentFileProvider->dataProcess(recentFiles);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// RecentFileExtension
|
||||
RecentFileExtension::RecentFileExtension(QObject *parent) : MenuExtensionIFace(parent)
|
||||
{
|
||||
QString translationFile(QString(RECENT_FILE_TRANSLATION_DIR) + "/recent-file-extension_" + QLocale::system().name() + ".qm");
|
||||
if (QFile::exists(translationFile)) {
|
||||
QTranslator *translator = new QTranslator(this);
|
||||
translator->load(translationFile);
|
||||
QCoreApplication::installTranslator(translator);
|
||||
}
|
||||
|
||||
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 || !m_recentFilesModel || !m_recentFileProvider) {
|
||||
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));
|
||||
|
||||
GVFSRecentFileData::fileMonitor(m_recentFileProvider);
|
||||
Q_EMIT loadRecentFiles();
|
||||
}
|
||||
|
||||
RecentFileExtension::~RecentFileExtension()
|
||||
{
|
||||
if (m_recentFilesProviderThread) {
|
||||
m_recentFilesProviderThread->quit();
|
||||
m_recentFilesProviderThread->wait();
|
||||
}
|
||||
|
||||
if (m_recentFileProvider) {
|
||||
delete m_recentFileProvider;
|
||||
m_recentFileProvider = nullptr;
|
||||
}
|
||||
|
||||
if (GVFSRecentFileData::s_recentFileRootDir) {
|
||||
g_object_unref(GVFSRecentFileData::s_recentFileRootDir);
|
||||
}
|
||||
|
||||
if (GVFSRecentFileData::s_recentFileMonitor) {
|
||||
g_object_unref(GVFSRecentFileData::s_recentFileMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
int RecentFileExtension::index()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QString RecentFileExtension::name()
|
||||
{
|
||||
return tr("Recent Files");
|
||||
}
|
||||
|
||||
QUrl RecentFileExtension::url()
|
||||
{
|
||||
return {"qrc:///extensions/RecentFileExtension.qml"};
|
||||
}
|
||||
|
||||
QVariantMap RecentFileExtension::data()
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void RecentFileExtension::receive(QVariantMap data)
|
||||
{
|
||||
if (data.value("action").toString() == "right") {
|
||||
QString path = data.value("url").toString();
|
||||
int index = data.value("index").toInt();
|
||||
creatMenu(path, index);
|
||||
return;
|
||||
|
||||
} else if (m_contextMenu) {
|
||||
m_contextMenu.data()->close();
|
||||
}
|
||||
|
||||
if (data.value("action").toString() == "openFile") {
|
||||
QString path = data.value("url").toString();
|
||||
openFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
void RecentFileExtension::openFile(const QString &fileUrl)
|
||||
{
|
||||
QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "LaunchDefaultAppWithUrl");
|
||||
message << fileUrl;
|
||||
|
||||
auto watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [fileUrl, this] (QDBusPendingCallWatcher *self) {
|
||||
QDBusPendingReply<bool> reply = *self;
|
||||
if (reply.isError() || !reply.value()) {
|
||||
Q_EMIT openFileASync(fileUrl);
|
||||
}
|
||||
|
||||
self->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void RecentFileExtension::creatMenu(const QString &path, const int &index)
|
||||
{
|
||||
if (m_contextMenu) {
|
||||
m_contextMenu.data()->close();
|
||||
return;
|
||||
}
|
||||
|
||||
QMenu *menu = new QMenu;
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction *open = new QAction(QIcon::fromTheme("document-open-symbolic"), tr("Open"), menu);
|
||||
QAction *remove = new QAction(QIcon::fromTheme("edit-clear-symbolic"), tr("Remove from list"), menu);
|
||||
QAction *clear = new QAction(QIcon::fromTheme("edit-delete-symbolic"), tr("Clear list"), menu);
|
||||
QAction *directory = new QAction(tr("Open the directory where the file is located"), menu);
|
||||
|
||||
connect(open, &QAction::triggered, this, [this, path]() {
|
||||
openFile(path);
|
||||
});
|
||||
|
||||
connect(remove, &QAction::triggered, this, [this, index]() {
|
||||
GVFSRecentFileData::removeRecentFileByInfoId(m_recentFilesModel->getInfoId(index));
|
||||
});
|
||||
|
||||
connect(clear, &QAction::triggered, this, [this]() {
|
||||
QStringList infoIdList = m_recentFilesModel->getAllInfoId();
|
||||
for (const QString &infoId : infoIdList) {
|
||||
GVFSRecentFileData::removeRecentFileByInfoId(infoId);
|
||||
}
|
||||
});
|
||||
|
||||
connect(directory, &QAction::triggered, this, [this, path]() {
|
||||
QDBusMessage message = QDBusMessage::createMethodCall(FREEDESKTOP_FILEMANAGER_NAME,
|
||||
FREEDESKTOP_FILEMANAGER_PATH,
|
||||
FREEDESKTOP_FILEMANAGER_INTERFACE, "ShowFolders");
|
||||
message << QStringList(path) << "ukui-menu-recent-file";
|
||||
QDBusConnection::sessionBus().asyncCall(message);
|
||||
});
|
||||
|
||||
menu->addAction(open);
|
||||
menu->addSeparator();
|
||||
menu->addAction(remove);
|
||||
menu->addAction(clear);
|
||||
menu->addSeparator();
|
||||
menu->addAction(directory);
|
||||
|
||||
m_contextMenu = menu;
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
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;
|
||||
case PathRole: {
|
||||
QUrl baseUrl(m_recentFileData.at(row).uri);
|
||||
return baseUrl.adjusted(QUrl::RemoveFilename).path();
|
||||
}
|
||||
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");
|
||||
names.insert(PathRole, "path");
|
||||
return names;
|
||||
}
|
||||
|
||||
QString RecentFilesModel::getInfoId(const int &index)
|
||||
{
|
||||
return m_recentFileData.at(index).infoId;
|
||||
}
|
||||
|
||||
QStringList RecentFilesModel::getAllInfoId()
|
||||
{
|
||||
QStringList infoIdList;
|
||||
for (const RecentFile &data : m_recentFileData) {
|
||||
infoIdList.append(data.infoId);
|
||||
}
|
||||
return infoIdList;
|
||||
}
|
||||
|
||||
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 &fileUrl)
|
||||
{
|
||||
GFile *file = g_file_new_for_uri(fileUrl.toUtf8().constData());
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
GFileInfo *fileInfo = g_file_query_info(file, "standard::*," G_FILE_ATTRIBUTE_ID_FILE, G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
|
||||
if (!fileInfo) {
|
||||
g_object_unref(file);
|
||||
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 = g_app_info_get_default_for_type(mimeType.toUtf8().constData(), false);
|
||||
|
||||
} else {
|
||||
info = (GAppInfo *) g_desktop_app_info_new(desktopApp);
|
||||
g_free(desktopApp);
|
||||
}
|
||||
}
|
||||
|
||||
g_key_file_free(keyfile);
|
||||
|
||||
bool success = false;
|
||||
if (G_IS_APP_INFO(info)) {
|
||||
GList *files = g_list_alloc();
|
||||
g_list_append(files, file);
|
||||
|
||||
success = g_app_info_launch(info, files, nullptr, nullptr);
|
||||
|
||||
g_list_free(files);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
QDesktopServices::openUrl(fileUrl);
|
||||
}
|
||||
|
||||
g_object_unref(file);
|
||||
g_object_unref(info);
|
||||
}
|
||||
|
||||
} // UkuiMenu
|
||||
|
||||
#include "recent-file-extension.moc"
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022, 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef UKUI_MENU_RECENT_FILE_EXTENSION_H
|
||||
#define UKUI_MENU_RECENT_FILE_EXTENSION_H
|
||||
|
||||
#include <QMenu>
|
||||
#include <QPointer>
|
||||
|
||||
class QThread;
|
||||
|
||||
#include "menu-extension-iface.h"
|
||||
|
||||
namespace UkuiMenu {
|
||||
|
||||
class RecentFilesModel;
|
||||
class RecentFileProvider;
|
||||
|
||||
class RecentFile
|
||||
{
|
||||
public:
|
||||
quint64 accessTime{0};
|
||||
QString uri;
|
||||
QString name;
|
||||
QString icon;
|
||||
QString infoId;
|
||||
};
|
||||
|
||||
class RecentFileExtension : public MenuExtensionIFace
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID UKUI_MENU_EXTENSION_IFACE_IID FILE "metadata.json")
|
||||
Q_INTERFACES(UkuiMenu::MenuExtensionIFace)
|
||||
public:
|
||||
explicit RecentFileExtension(QObject *parent = nullptr);
|
||||
~RecentFileExtension() override;
|
||||
int index() override;
|
||||
QString name() override;
|
||||
QUrl url() override;
|
||||
QVariantMap data() override;
|
||||
void receive(QVariantMap data) override;
|
||||
|
||||
private:
|
||||
QPointer<QMenu> m_contextMenu;
|
||||
QVector<RecentFile> m_recentFile;
|
||||
QVariantMap m_data;
|
||||
RecentFilesModel *m_recentFilesModel = nullptr;
|
||||
QThread *m_recentFilesProviderThread = nullptr;
|
||||
RecentFileProvider *m_recentFileProvider = nullptr;
|
||||
|
||||
void openFile(const QString& fileUrl);
|
||||
void creatMenu(const QString &path, const int &index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void loadRecentFiles();
|
||||
void openFileASync(const QString &path);
|
||||
};
|
||||
|
||||
} // UkuiMenu
|
||||
|
||||
#endif //UKUI_MENU_RECENT_FILE_EXTENSION_H
|
|
@ -1,34 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="zh_CN">
|
||||
<context>
|
||||
<name>RecentFileExtension</name>
|
||||
<message>
|
||||
<source>Path: </source>
|
||||
<translation>路径: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>UkuiMenu::RecentFileExtension</name>
|
||||
<message>
|
||||
<source>Recent Files</source>
|
||||
<translation>最近</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation>打开</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove from list</source>
|
||||
<translation>从列表删除</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear list</source>
|
||||
<translation>清空全部</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open the directory where the file is located</source>
|
||||
<translation>打开文件所在目录</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
|
@ -48,7 +48,7 @@ Item {
|
|||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
// spacing: 5
|
||||
spacing: 4
|
||||
|
||||
ListView {
|
||||
id: extensionInfoList
|
||||
|
@ -78,6 +78,14 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: extensionMenuLoader
|
||||
visible: sourceComponent !== undefined
|
||||
Layout.preferredWidth: 34
|
||||
Layout.preferredHeight: 34
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
AppControls2.StyleBackground {
|
||||
id: fullScreenbutton
|
||||
Layout.preferredWidth: 34
|
||||
|
@ -125,10 +133,23 @@ Item {
|
|||
onLoaded: {
|
||||
item.send.connect(extensionInfoList.send);
|
||||
sidebarLayout.updateSidebarLayout(extensionInfoList.currentItem.extensionOptions);
|
||||
updateMenu();
|
||||
item.extensionMenuChanged.connect(updateMenu);
|
||||
}
|
||||
Keys.onTabPressed: {
|
||||
extensionInfoList.focus = true
|
||||
}
|
||||
|
||||
function updateMenu() {
|
||||
if (item === null) {
|
||||
return;
|
||||
}
|
||||
if (item.extensionMenu !== null) {
|
||||
extensionMenuLoader.sourceComponent = item.extensionMenu;
|
||||
} else {
|
||||
extensionMenuLoader.sourceComponent = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,6 @@ import QtQuick 2.0
|
|||
|
||||
Item {
|
||||
property var extensionData;
|
||||
property Component extensionMenu: null;
|
||||
signal send(var data);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue