diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ec3410..023a3a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,7 @@ set(SOURCE_FILES src/appdata/data-provider-manager.cpp src/appdata/data-provider-manager.h src/appdata/plugin/all-app-data-provider.cpp src/appdata/plugin/all-app-data-provider.h src/appdata/plugin/app-search-plugin.cpp src/appdata/plugin/app-search-plugin.h + src/appdata/plugin/app-category-plugin.cpp src/appdata/plugin/app-category-plugin.h src/extension/menu-extension-iface.h src/extension/menu-extension.cpp src/extension/menu-extension.h src/extension/extensions/folder-extension.cpp src/extension/extensions/folder-extension.h diff --git a/src/appdata/data-provider-manager.cpp b/src/appdata/data-provider-manager.cpp index 4f8ead8..0bf306a 100644 --- a/src/appdata/data-provider-manager.cpp +++ b/src/appdata/data-provider-manager.cpp @@ -19,6 +19,7 @@ #include "data-provider-manager.h" #include "plugin/all-app-data-provider.h" #include "plugin/app-search-plugin.h" +#include "plugin/app-category-plugin.h" namespace UkuiMenu { @@ -47,9 +48,13 @@ void DataProviderManager::initProviders() auto *search = new AppSearchPlugin; registerProvider(search); + auto *category = new AppCategoryPlugin; + registerProvider(category); + activateProvider(allProvider->id()); allProvider->moveToThread(&m_worker); search->moveToThread(&m_worker); + category->moveToThread(&m_worker); } void DataProviderManager::registerProvider(DataProviderPluginIFace *provider) diff --git a/src/appdata/data-provider-plugin-iface.h b/src/appdata/data-provider-plugin-iface.h index 5193e34..f075b9a 100644 --- a/src/appdata/data-provider-plugin-iface.h +++ b/src/appdata/data-provider-plugin-iface.h @@ -71,6 +71,7 @@ public: * @return 数据容器 */ virtual QVector data() = 0; + virtual QVector labels() { return {}; }; /** * 强制刷新数据,刷新完成后发送dataChange信号 @@ -83,6 +84,7 @@ Q_SIGNALS: * 数据变化 */ void dataChanged(QVector data, DataUpdateMode::Mode mode = DataUpdateMode::Reset, quint32 index = 0); + void labelChanged(); }; } // UkuiMenu diff --git a/src/appdata/plugin/app-category-plugin.cpp b/src/appdata/plugin/app-category-plugin.cpp new file mode 100644 index 0000000..6912b7e --- /dev/null +++ b/src/appdata/plugin/app-category-plugin.cpp @@ -0,0 +1,239 @@ +/* + * 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 . + * + */ + +#include "app-category-plugin.h" +#include "app-data-manager.h" + +namespace UkuiMenu { + +// 临时使用,可能会删除 +class CategoryPair +{ +public: + CategoryPair() = default; + CategoryPair(QString id_t, QString localName_t) : id(std::move(id_t)), localName(std::move(localName_t)) {} + QString id; + QString localName; +}; + +class CategoryItem +{ +public: + LabelItem label; + QList apps; +}; + +class AppCategoryPluginPrivate +{ +public: + QHash categoryIndex; + QList categoryData; +}; + +AppCategoryPlugin::AppCategoryPlugin() : d(new AppCategoryPluginPrivate) +{ + initCategories(); + reLoadApps(); + + connect(AppDataManager::instance(), &AppDataManager::appAdded, this, &AppCategoryPlugin::onAppAdded); + connect(AppDataManager::instance(), &AppDataManager::appDeleted, this, &AppCategoryPlugin::onAppDeleted); +} + +AppCategoryPlugin::~AppCategoryPlugin() +{ + if (d) { + delete d; + d = nullptr; + } +} + +int AppCategoryPlugin::index() +{ + return 1; +} + +QString AppCategoryPlugin::id() +{ + return "category"; +} + +QString AppCategoryPlugin::name() +{ + return tr("Category"); +} + +QString AppCategoryPlugin::icon() +{ + return "image://appicon/text-plain"; +} + +QString AppCategoryPlugin::title() +{ + return "Category"; +} + +PluginGroup::Group AppCategoryPlugin::group() +{ + return PluginGroup::SortMenuItem; +} + +QVector AppCategoryPlugin::data() +{ + QVector data; + generateData(data); + return data; +} + +void AppCategoryPlugin::forceUpdate() +{ + +} + +QVector AppCategoryPlugin::labels() +{ + QMutexLocker locker(&m_mutex); + QVector labels; + for (const auto &item : d->categoryData) { +// qDebug() << "==label==" << item.label.id() << item.label.displayName(); + labels.append(item.label); + } + + return labels; +} + +// see: https://specifications.freedesktop.org/menu-spec/latest/apa.html +// see: https://specifications.freedesktop.org/menu-spec/latest/apas02.html +// TODO: 暂时只列出freedesktop规范中的常用分类,需要继续支持显示应用自定义的分类进行显示 +void AppCategoryPlugin::initCategories() +{ + QList primaryCategories; + primaryCategories.append({"Audio", tr("Audio")}); + primaryCategories.append({"AudioVideo", tr("AudioVideo")}); + primaryCategories.append({"Development", tr("Development")}); + primaryCategories.append({"Education", tr("Education")}); + primaryCategories.append({"Game", tr("Game")}); + primaryCategories.append({"Graphics", tr("Graphics")}); + primaryCategories.append({"Network", tr("Network")}); + primaryCategories.append({"Office", tr("Office")}); + primaryCategories.append({"Science", tr("Science")}); + primaryCategories.append({"Settings", tr("Settings")}); + primaryCategories.append({"System", tr("System")}); + primaryCategories.append({"Utility", tr("Utility")}); + primaryCategories.append({"Video", tr("Video")}); + + // 未定义Category的应用类别 + m_otherLabelId = "Other"; + primaryCategories.append({m_otherLabelId, tr("Other")}); + + for (const auto &category : primaryCategories) { + // 默认拷贝 + CategoryItem categoryItem; + categoryItem.label.setId(category.id); + categoryItem.label.setDisplayName(category.localName); +// categoryItem.label.setDisable(true); + categoryItem.label.setIndex(d->categoryData.size()); + + d->categoryData.append(categoryItem); + d->categoryIndex.insert(category.id, categoryItem.label.index()); + } +} + +int AppCategoryPlugin::parseAppCategory(const DataEntity &app) +{ +// qDebug() << "=parseAppCategory=" << app.name(); + // TODO: 某些应用的Category字段不是使用;进行分割的,是否需要支持? + QStringList categories = app.category().split(";"); + for (const auto &category : categories) { + int index = d->categoryIndex.value(category, -1); + if (index != -1) { + return index; + } + } + +// qDebug() << "Other index:" << d->categoryIndex.value(m_otherLabelId) << "==" << m_otherLabelId; + return d->categoryIndex.value(m_otherLabelId); +} + +void AppCategoryPlugin::addAppToCategoryList(const DataEntity &app) +{ + CategoryItem &categoryItem = d->categoryData[parseAppCategory(app)]; + categoryItem.label.setDisable(false); + categoryItem.apps.append(app); +} + +void AppCategoryPlugin::reLoadApps() +{ + QMutexLocker locker(&m_mutex); + + for (auto &item : d->categoryData) { + item.label.setDisable(true); + } + + QList apps = AppDataManager::instance()->normalApps(); + for (const auto &app : apps) { + addAppToCategoryList(app); + } +} + +void AppCategoryPlugin::generateData(QVector &data) +{ + QMutexLocker locker(&m_mutex); + for (const auto &item : d->categoryData) { + // TODO: 显示不包含应用的分类? + if (item.apps.isEmpty()) { + continue; + } + + DataEntity label; + label.setType(DataType::Label); + label.setId(item.label.id()); + label.setName(item.label.displayName()); + + data.append(label); + // TODO: 性能优化 +// data.append(item.apps.toVector()); + for (const auto &app : item.apps) { + data.append(app); + } + } +} + +void AppCategoryPlugin::updateData() +{ + Q_EMIT dataChanged(data()); +} + +void AppCategoryPlugin::onAppAdded(const QList& apps) +{ + { + QMutexLocker locker(&m_mutex); + for (const auto &app : apps) { + addAppToCategoryList(app); + } + } + updateData(); +} + +void AppCategoryPlugin::onAppDeleted(const QStringList& idList) +{ + reLoadApps(); + updateData(); + Q_EMIT labelChanged(); +} + +} // UkuiMenu diff --git a/src/appdata/plugin/app-category-plugin.h b/src/appdata/plugin/app-category-plugin.h new file mode 100644 index 0000000..c33102e --- /dev/null +++ b/src/appdata/plugin/app-category-plugin.h @@ -0,0 +1,68 @@ +/* + * 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 . + * + */ + +#ifndef UKUI_MENU_APP_CATEGORY_PLUGIN_H +#define UKUI_MENU_APP_CATEGORY_PLUGIN_H + +#include "data-provider-plugin-iface.h" + +namespace UkuiMenu { + +class AppCategoryPluginPrivate; + +class AppCategoryPlugin : public DataProviderPluginIFace +{ + Q_OBJECT +public: + AppCategoryPlugin(); + ~AppCategoryPlugin() override; + + int index() override; + QString id() override; + QString name() override; + QString icon() override; + QString title() override; + PluginGroup::Group group() override; + QVector data() override; + void forceUpdate() override; + QVector labels() override; + +private Q_SLOTS: + void onAppAdded(const QList& apps); + void onAppDeleted(const QStringList& idList); + +private: + void reLoadApps(); + void initCategories(); + // 解析Category,获取正确的index + int parseAppCategory(const DataEntity &app); + void addAppToCategoryList(const DataEntity &app); + + // 从私有数据类型生成数据 + void generateData(QVector &data); + inline void updateData(); + +private: + QMutex m_mutex; + QString m_otherLabelId; + AppCategoryPluginPrivate *d{nullptr}; +}; + +} // UkuiMenu + +#endif //UKUI_MENU_APP_CATEGORY_PLUGIN_H diff --git a/src/commons.cpp b/src/commons.cpp index 10c740b..7926f0f 100644 --- a/src/commons.cpp +++ b/src/commons.cpp @@ -145,3 +145,47 @@ QString UkuiMenu::DataEntity::extraData() const { return m_extraData; } + +// ====== LabelItem ====== // +UkuiMenu::LabelItem::LabelItem(bool disable, int index, QString id, QString displayName) +: m_disable(disable), m_index(index), m_id(std::move(id)), m_displayName(std::move(displayName)) {} + +bool UkuiMenu::LabelItem::isDisable() const +{ + return m_disable; +} + +void UkuiMenu::LabelItem::setDisable(bool disable) +{ + LabelItem::m_disable = disable; +} + +int UkuiMenu::LabelItem::index() const +{ + return m_index; +} + +void UkuiMenu::LabelItem::setIndex(int index) +{ + LabelItem::m_index = index; +} + +const QString &UkuiMenu::LabelItem::displayName() const +{ + return m_id; +} + +void UkuiMenu::LabelItem::setDisplayName(const QString &name) +{ + LabelItem::m_id = name; +} + +const QString &UkuiMenu::LabelItem::id() const +{ + return m_id; +} + +void UkuiMenu::LabelItem::setId(const QString &id) +{ + m_id = id; +} diff --git a/src/commons.h b/src/commons.h index b472467..7302e5b 100644 --- a/src/commons.h +++ b/src/commons.h @@ -24,6 +24,37 @@ namespace UkuiMenu { +// 标签项 +class LabelItem +{ + Q_GADGET +public: + LabelItem() = default; + explicit LabelItem(bool disable, int index, QString id, QString displayName); + + friend inline bool operator==(const LabelItem& a, const LabelItem& b) { + return QString::compare(a.id(), b.id(), Qt::CaseInsensitive); + } + + bool isDisable() const; + void setDisable(bool disable); + + int index() const; + void setIndex(int index); + + const QString &id() const; + void setId(const QString &id); + + const QString &displayName() const; + void setDisplayName(const QString &name); + +private: + bool m_disable{true}; + int m_index{0}; + QString m_id; + QString m_displayName; +}; + class Display { Q_GADGET public: diff --git a/src/extension/menu-extension.cpp b/src/extension/menu-extension.cpp index 5ed546d..6453b42 100644 --- a/src/extension/menu-extension.cpp +++ b/src/extension/menu-extension.cpp @@ -39,7 +39,7 @@ MenuExtension::MenuExtension() // TODO load extension from filesystem. // register extension. - registerExtension(new FolderExtension(this)); +// registerExtension(new FolderExtension(this)); registerExtension(new RecentFileExtension(this)); registerExtension(new FavoriteExtension(this));