From 8a8faa9b1160994688944d80a24019fc545f2fc5 Mon Sep 17 00:00:00 2001 From: wjyrich Date: Fri, 14 Aug 2026 16:07:34 +0800 Subject: [PATCH] fix: update trash icon dynamically based on trash state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add TrashMonitor class that uses GIO to monitor trash directory changes 2. Connect trash monitor signals to update trash app icon in real-time 3. Switch icon between "user-trash" and "user-trash-full" based on trash emptiness 4. Handle dataChanged signal to refresh icon when app item properties change 5. Add GLIB2/GIO dependency and Qt Concurrent library to build configuration Log: Trash icon now updates dynamically when trash is emptied or filled Influence: 1. Test trash icon displays correctly when trash is empty (user-trash icon) 2. Delete a file and verify icon changes to full state (user-trash- full icon) 3. Empty the trash and verify icon changes back to empty state 4. Test multiple files being deleted simultaneously 5. Verify trash icon updates when files are restored from trash 6. Test with slow file systems to ensure no UI lag during updates 7. Verify no memory leaks during extended monitoring fix: 根据回收站状态动态更新回收站图标 1. 添加 TrashMonitor 类,使用 GIO 监控回收站目录变化 2. 连接回收站监控信号,实时更新回收站应用图标 3. 根据回收站是否为空在 "user-trash" 和 "user-trash-full" 图标间切换 4. 处理 dataChanged 信号,在应用项属性变化时刷新图标 5. 在构建配置中添加 GLIB2/GIO 依赖和 Qt Concurrent 库 Log: 回收站图标会随回收站清空或填充状态动态更新 Influence: 1. 测试回收站为空时图标显示正常(user-trash 图标) 2. 删除文件后验证图标是否变为满状态(user-trash-full 图标) 3. 清空回收站后验证图标是否恢复为空状态 4. 测试同时删除多个文件的情况 5. 验证从回收站恢复文件后图标是否正确更新 6. 在慢速文件系统上测试,确保更新过程中无 UI 卡顿 7. 验证长时间监控过程中无内存泄漏 PMS: BUG-285725 --- applets/dde-apps/CMakeLists.txt | 5 ++ applets/dde-apps/amappitemmodel.cpp | 30 ++++++++++ applets/dde-apps/amappitemmodel.h | 6 +- applets/dde-apps/trashmonitor.cpp | 90 +++++++++++++++++++++++++++++ applets/dde-apps/trashmonitor.h | 40 +++++++++++++ 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 applets/dde-apps/trashmonitor.cpp create mode 100644 applets/dde-apps/trashmonitor.h diff --git a/applets/dde-apps/CMakeLists.txt b/applets/dde-apps/CMakeLists.txt index bf67ebc8a..56fdac274 100644 --- a/applets/dde-apps/CMakeLists.txt +++ b/applets/dde-apps/CMakeLists.txt @@ -4,6 +4,7 @@ find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS DBus) find_package(DDEApplicationManager REQUIRED) +find_package(GLIB2 REQUIRED COMPONENTS GIO) find_package(yaml-cpp REQUIRED) set_source_files_properties( @@ -55,10 +56,14 @@ add_library(dde-apps SHARED ${DBUS_INTERFACES} categoryutils.h itemspage.cpp itemspage.h + trashmonitor.cpp + trashmonitor.h ) target_link_libraries(dde-apps PRIVATE dde-shell-frame + Qt${QT_VERSION_MAJOR}::Concurrent + GLIB2::GIO yaml-cpp ) diff --git a/applets/dde-apps/amappitemmodel.cpp b/applets/dde-apps/amappitemmodel.cpp index 5801dea82..a62f44086 100644 --- a/applets/dde-apps/amappitemmodel.cpp +++ b/applets/dde-apps/amappitemmodel.cpp @@ -6,6 +6,7 @@ #include "amappitem.h" #include "appitemmodel.h" #include "objectmanager1interface.h" +#include "trashmonitor.h" #include @@ -16,6 +17,7 @@ namespace apps AMAppItemModel::AMAppItemModel(QObject *parent) : AppItemModel(parent) , m_manager(new ObjectManager("org.desktopspec.ApplicationManager1", "/org/desktopspec/ApplicationManager1", QDBusConnection::sessionBus(), this)) + , m_trashMonitor(new TrashMonitor(this)) , m_ready(false) { qRegisterMetaType(); @@ -35,6 +37,18 @@ AMAppItemModel::AMAppItemModel(QObject *parent) return; } appendRow(new AMAppItem(objPath, interfacesAndProperties)); + updateTrashIcon(); + }); + + connect(m_trashMonitor, &TrashMonitor::emptyChanged, this, &AMAppItemModel::updateTrashIcon); + connect(this, &QAbstractItemModel::dataChanged, this, + [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) { + const auto trashIndex = match(index(0, 0), AppItemModel::DesktopIdRole, + QStringLiteral("dde-trash"), 1, Qt::MatchExactly).value(0); + if (trashIndex.isValid() && trashIndex.row() >= topLeft.row() && trashIndex.row() <= bottomRight.row() + && (roles.isEmpty() || roles.contains(AppItemModel::IconNameRole))) { + updateTrashIcon(); + } }); connect(m_manager, &ObjectManager::InterfacesRemoved, this, [this](const QDBusObjectPath &objPath, const QStringList &interfaces) { @@ -69,6 +83,9 @@ AMAppItemModel::AMAppItemModel(QObject *parent) appendRow(new AMAppItem(path, app.value())); } + + updateTrashIcon(); + m_ready = true; Q_EMIT readyChanged(true); qCDebug(appsLog) << "AMAppItemModel is now ready with apps counts:" << rowCount(); @@ -90,4 +107,17 @@ AMAppItem * AMAppItemModel::appItem(const QString &id) return nullptr; } +void AMAppItemModel::updateTrashIcon() +{ + auto *trash = appItem(QStringLiteral("dde-trash")); + if (!trash) + return; + + const QString iconName = m_trashMonitor->isEmpty() + ? QStringLiteral("user-trash") + : QStringLiteral("user-trash-full"); + if (trash->appIconName() != iconName) + trash->setAppIconName(iconName); +} + } diff --git a/applets/dde-apps/amappitemmodel.h b/applets/dde-apps/amappitemmodel.h index 5ace8b3c5..9c182c42f 100644 --- a/applets/dde-apps/amappitemmodel.h +++ b/applets/dde-apps/amappitemmodel.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,6 +10,7 @@ namespace apps { class AMAppItem; +class TrashMonitor; class AMAppItemModel : public AppItemModel { Q_OBJECT @@ -25,7 +26,10 @@ class AMAppItemModel : public AppItemModel void readyChanged(bool); private: + void updateTrashIcon(); + bool m_ready; ObjectManager *m_manager; + TrashMonitor *m_trashMonitor; }; } \ No newline at end of file diff --git a/applets/dde-apps/trashmonitor.cpp b/applets/dde-apps/trashmonitor.cpp new file mode 100644 index 000000000..3dd549511 --- /dev/null +++ b/applets/dde-apps/trashmonitor.cpp @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "trashmonitor.h" + +#include + +Q_LOGGING_CATEGORY(trashMonitorLog, "org.deepin.dde.shell.dde-apps.trash") + +namespace apps +{ +TrashMonitor::TrashMonitor(QObject *parent) + : QObject(parent) + , m_trash(g_file_new_for_uri("trash:///")) +{ + updateState(); + + GError *error = nullptr; + m_monitor = g_file_monitor_file(m_trash, G_FILE_MONITOR_NONE, nullptr, &error); + if (!m_monitor) { + qCWarning(trashMonitorLog) << "Failed to monitor trash:" + << (error ? error->message : "unknown error"); + g_clear_error(&error); + return; + } + + g_signal_connect(m_monitor, "changed", G_CALLBACK(onTrashChanged), this); +} + +TrashMonitor::~TrashMonitor() +{ + if (m_monitor) + g_object_unref(m_monitor); + if (m_trash) + g_object_unref(m_trash); +} + +bool TrashMonitor::isEmpty() const +{ + return m_empty; +} + +void TrashMonitor::updateState() +{ + GError *error = nullptr; + GFileInfo *info = g_file_query_info(m_trash, + G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, + G_FILE_QUERY_INFO_NONE, + nullptr, + &error); + if (!info) { + qCWarning(trashMonitorLog) << "Failed to query trash item count:" + << (error ? error->message : "unknown error"); + g_clear_error(&error); + return; + } + + const bool empty = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT) == 0; + g_object_unref(info); + + if (m_empty == empty) + return; + + m_empty = empty; + Q_EMIT emptyChanged(m_empty); +} + +void TrashMonitor::onTrashChanged(GFileMonitor *monitor, + GFile *file, + GFile *otherFile, + GFileMonitorEvent eventType, + gpointer userData) +{ + Q_UNUSED(monitor) + Q_UNUSED(file) + Q_UNUSED(otherFile) + + switch (eventType) { + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_DELETED: + static_cast(userData)->updateState(); + break; + default: + break; + } +} +} diff --git a/applets/dde-apps/trashmonitor.h b/applets/dde-apps/trashmonitor.h new file mode 100644 index 000000000..defc491f9 --- /dev/null +++ b/applets/dde-apps/trashmonitor.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#undef signals +#include +#define signals Q_SIGNALS + +namespace apps +{ +class TrashMonitor : public QObject +{ + Q_OBJECT + +public: + explicit TrashMonitor(QObject *parent = nullptr); + ~TrashMonitor() override; + + bool isEmpty() const; + +signals: + void emptyChanged(bool empty); + +private: + void updateState(); + static void onTrashChanged(GFileMonitor *monitor, + GFile *file, + GFile *otherFile, + GFileMonitorEvent eventType, + gpointer userData); + + GFile *m_trash = nullptr; + GFileMonitor *m_monitor = nullptr; + bool m_empty = true; +}; +}