feat(extension):实现收藏区域显示应用组和右键菜单功能

This commit is contained in:
qiqi49 2024-01-19 11:10:07 +08:00 committed by hewenfei
parent 1e34114387
commit 898b61d312
21 changed files with 1314 additions and 27 deletions

View File

@ -129,6 +129,10 @@ set(SOURCE_FILES
src/extension/favorite/favorite-extension-plugin.cpp src/extension/favorite/favorite-extension-plugin.h
src/extension/favorite/favorite-context-menu.cpp src/extension/favorite/favorite-context-menu.h
src/extension/favorite/favorite-widget.cpp src/extension/favorite/favorite-widget.h
src/extension/favorite/app-favorite-model.cpp src/extension/favorite/app-favorite-model.h
src/extension/favorite/favorites-config.cpp src/extension/favorite/favorites-config.h
src/extension/favorite/favorites-model.cpp src/extension/favorite/favorites-model.h
src/extension/favorite/favorite-folder-helper.cpp src/extension/favorite/favorite-folder-helper.h
src/libappdata/basic-app-model.cpp src/libappdata/basic-app-model.h
src/libappdata/app-database-interface.cpp src/libappdata/app-database-interface.h
)

View File

@ -34,7 +34,8 @@ public:
enum Type {
Normal, // 普通Item
Label, // 标签数据
Folder // 文件夹
Folder, // 应用组
Files // 文件夹
};
Q_ENUM(Type);
};

View File

@ -34,7 +34,7 @@ public:
Extension, /**< 扩展页 */
FolderPage, /**< 文件夹页面 */
FullScreen, /**< 全屏应用列表 */
FullScreenFolder, /**< 全屏文件夹页面 */
Folder, /**< 应用组页面 */
};
Q_ENUM(Location)
};

View File

@ -0,0 +1,316 @@
//
// Created by qiqi on 23-12-27.
//
#include "favorites-config.h"
#include "app-favorite-model.h"
#include "favorite-folder-helper.h"
#include <QMimeDatabase>
#include <QFile>
#include <QUrl>
#include <QDebug>
namespace UkuiMenu {
AppFavoritesModel &AppFavoritesModel::instance()
{
static AppFavoritesModel appFavoritesModel;
return appFavoritesModel;
}
AppFavoritesModel::AppFavoritesModel(QObject *parent) : QAbstractListModel(parent)
{
m_sourceModel = BasicAppModel::instance();
updateData();
connect(m_sourceModel, &BasicAppModel::dataChanged, this, &AppFavoritesModel::onAppUpdated);
connect(m_sourceModel, &BasicAppModel::rowsAboutToBeRemoved, this, &AppFavoritesModel::onAppRemoved);
connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderAdded, this,&AppFavoritesModel::onFolderAdded);
connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderToBeDeleted, this, &AppFavoritesModel::onFolderDeleted);
connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderDataChanged, this, &AppFavoritesModel::onFolderChanged);
}
AppFavoritesModel::~AppFavoritesModel()
{
}
QHash<int, QByteArray> AppFavoritesModel::roleNames() const
{
QHash<int, QByteArray> names;
names.insert(DataEntity::Id, "id");
names.insert(DataEntity::Icon, "icon");
names.insert(DataEntity::Name, "name");
names.insert(DataEntity::Type, "type");
return names;
}
int AppFavoritesModel::rowCount(const QModelIndex &parent) const
{
return m_favoritesApps.count() + m_folders.count() + m_favoritesFiles.count();
}
QVariant AppFavoritesModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= rowCount()) {
return {};
}
if (index.row() < m_favoritesApps.count()) {
return m_favoritesApps.at(index.row()).data(role);
} else if (index.row() < m_favoritesApps.count() + m_folders.count() && index.row() >= m_favoritesApps.count()) {
return folderData(index, role);
} else {
return fileData(index, role);
}
}
void AppFavoritesModel::getFavoritesApps()
{
m_favoritesApps.clear();
for (int i = 0; i < m_sourceModel->rowCount(QModelIndex()); i++) {
updateFavoritesApps(m_sourceModel->appOfIndex(i), m_sourceModel->index(i));
}
}
void AppFavoritesModel::onAppUpdated(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
for (const auto &role : roles) {
if (role == DataEntity::Favorite) {
updateFavoritesApps(m_sourceModel->appOfIndex(topLeft.row()), topLeft);
}
}
}
void AppFavoritesModel::getFoldersId()
{
QVector<int> foldersId;
for (const auto &folder : FavoriteFolderHelper::instance()->folderData()) {
foldersId.append(folder.getId());
}
m_folders.swap(foldersId);
}
void AppFavoritesModel::getFavoritesFiles()
{
}
void AppFavoritesModel::updateFavoritesApps(const DataEntity &app, const QModelIndex &sourceIndex)
{
if (app.id().isEmpty()) {
return;
}
if (app.favorite() > 0 && !FavoriteFolderHelper::instance()->containApp(app.id())) {
QPersistentModelIndex index(sourceIndex);
addFavoriteApp(index);
} else if (app.favorite() == 0) {
QPersistentModelIndex index(sourceIndex);
removeFavoriteApp(index);
}
}
void AppFavoritesModel::updateData()
{
getFavoritesApps();
getFoldersId();
getFavoritesFiles();
}
QVariant AppFavoritesModel::folderData(const QModelIndex &index, int role) const
{
FavoritesFolder folder;
int folderId = m_folders.at(index.row() - m_favoritesApps.count());
if (!FavoriteFolderHelper::instance()->getFolderFromId(folderId, folder)) {
return {};
}
switch (role) {
case DataEntity::Id:
return QString::number(folder.getId());
case DataEntity::Icon:
return FavoriteFolderHelper::folderIcon(folder).join(" ");
case DataEntity::Name:
return folder.getName();
case DataEntity::Type:
return DataType::Folder;
default:
break;
}
return {};
}
QVariant AppFavoritesModel::fileData(const QModelIndex &index, int role) const
{
int fileIndex = index.row() - m_favoritesApps.count() - m_folders.count();
QString url = m_favoritesFiles.at(fileIndex);
switch (role) {
case DataEntity::Id:
return url;
case DataEntity::Icon: {
QMimeDatabase mimeDatabase;
return mimeDatabase.mimeTypeForFile(url).iconName();
}
case DataEntity::Name:
return QUrl(url).fileName();
case DataEntity::Type:
return DataType::Files;
default:
break;
}
return {};
}
QStringList AppFavoritesModel::favoritesApps() const
{
QMap<int, QString> apps;
for (auto index : m_favoritesApps) {
QString id = m_sourceModel->data(index, DataEntity::Id).toString();
int favorite = m_sourceModel->data(index, DataEntity::Favorite).toInt();
apps.insert(favorite, id);
}
return apps.values();
}
void AppFavoritesModel::changeAppState(const QString &id, const int &index)
{
if (id.isEmpty()) {
return;
}
m_sourceModel->databaseInterface()->fixAppToFavorite(id, index);
// 同步配置文件,更新应用顺序
if (index > 0) {
FavoritesConfig::instance().insertValue(id, index);
}
}
bool AppFavoritesModel::getApp(const QString &appid, DataEntity &app)
{
return m_sourceModel->databaseInterface()->getApp(appid, app);
}
void AppFavoritesModel::onAppRemoved(const QModelIndex &parent, int first, int last)
{
for(int row = first; row <= last; ++row) {
QModelIndex sourceIndex = m_sourceModel->index(row, 0, {});
QPersistentModelIndex index(sourceIndex);
if (index.data(DataEntity::Favorite).toInt() > 0) {
QString appId = index.data(DataEntity::Id).toString();
if (!m_favoritesApps.contains(index) && FavoriteFolderHelper::instance()->containApp(appId)) {
FavoriteFolderHelper::instance()->removeAppFromFolder(appId);
}
removeFavoriteApp(index);
}
}
}
void AppFavoritesModel::addFavoriteApp(const QPersistentModelIndex &modelIndex)
{
if (m_favoritesApps.contains(modelIndex)) {
return;
}
beginInsertRows(QModelIndex(), m_favoritesApps.count(), m_favoritesApps.count());
m_favoritesApps.append(modelIndex);
endInsertRows();
FavoritesConfig::instance().insertValue(modelIndex.data(DataEntity::Id).toString());
}
void AppFavoritesModel::removeFavoriteApp(const QPersistentModelIndex &modelIndex)
{
if (!m_favoritesApps.contains(modelIndex)) {
return;
}
beginRemoveRows(QModelIndex(), m_favoritesApps.indexOf(modelIndex), m_favoritesApps.indexOf(modelIndex));
m_favoritesApps.removeOne(modelIndex);
endRemoveRows();
FavoritesConfig::instance().removeValueById(modelIndex.data(DataEntity::Id).toString());
}
void AppFavoritesModel::onFolderAdded(const int &folderId, const int &order)
{
if (!m_folders.contains(folderId)) {
beginInsertRows(QModelIndex(), m_favoritesApps.count() + m_folders.count(), m_favoritesApps.count() + m_folders.count());
m_folders.append(folderId);
endInsertRows();
FavoritesFolder folder;
FavoriteFolderHelper::instance()->getFolderFromId(folderId, folder);
for (auto app : folder.getApps()) {
removeFavoriteApp(getIndexFromAppId(app));
}
FavoritesConfig::instance().insertValue(QString::number(folderId), std::max(0, order));
}
}
void AppFavoritesModel::onFolderDeleted(const int &folderId, const QStringList &apps)
{
if (m_folders.contains(folderId)) {
beginRemoveRows(QModelIndex(), m_favoritesApps.count() + m_folders.indexOf(folderId), m_favoritesApps.count() + m_folders.indexOf(folderId));
m_folders.removeOne(folderId);
endRemoveRows();
for (auto app : apps) {
QPersistentModelIndex modelIndex(m_sourceModel->index(m_sourceModel->indexOfApp(app)));
addFavoriteApp(modelIndex);
}
FavoritesConfig::instance().removeValueById(QString::number(folderId));
}
}
void AppFavoritesModel::onFolderChanged(const int &folderId, const QString &appId)
{
QVector<int> roles;
if (appId.isEmpty()) {
roles.append(DataEntity::Name);
int row = m_favoritesApps.count() + m_folders.indexOf(folderId);
Q_EMIT dataChanged(index(row), index(row), roles);
return;
}
if (FavoriteFolderHelper::instance()->containApp(appId)) {
removeFavoriteApp(getIndexFromAppId(appId));
} else {
QPersistentModelIndex modelIndex(m_sourceModel->index(m_sourceModel->indexOfApp(appId)));
addFavoriteApp(modelIndex);
}
roles.append(DataEntity::Icon);
int row = m_favoritesApps.count() + m_folders.indexOf(folderId);
Q_EMIT dataChanged(index(row), index(row), roles);
}
QPersistentModelIndex AppFavoritesModel::getIndexFromAppId(const QString &id) const
{
auto modelIndex = std::find_if(m_favoritesApps.constBegin(), m_favoritesApps.constEnd(), [&id] (const QPersistentModelIndex &index) {
return index.data(DataEntity::Id).toString() == id;
});
if (modelIndex == m_favoritesApps.constEnd()) {
return {};
}
return *modelIndex;
}
} // UkuiMenu

View File

@ -0,0 +1,71 @@
//
// Created by qiqi on 23-12-27.
//
#ifndef UKUI_MENU_APP_FAVORITE_MODEL_H
#define UKUI_MENU_APP_FAVORITE_MODEL_H
#include "data-entity.h"
#include "libappdata/basic-app-model.h"
#include <QObject>
#include <QStringList>
#include <QAbstractListModel>
namespace UkuiMenu {
class AppFavoritesModel : public QAbstractListModel
{
Q_OBJECT
public:
static AppFavoritesModel &instance();
~AppFavoritesModel() override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QStringList favoritesApps() const;
void changeAppState(const QString &id, const int &index);
bool getApp(const QString &appid, DataEntity &app);
private:
explicit AppFavoritesModel(QObject *parent = nullptr);
void getFavoritesApps();
void getFoldersId();
void getFavoritesFiles();
void updateData();
void addFavoriteApp(const QPersistentModelIndex &modelIndex);
void removeFavoriteApp(const QPersistentModelIndex &modelIndex);
void onAppRemoved(const QModelIndex &parent, int first, int last);
void onAppUpdated(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
void updateFavoritesApps(const DataEntity &app, const QModelIndex &sourceIndex);
QPersistentModelIndex getIndexFromAppId(const QString &id) const;
void onFolderAdded(const int &folderId, const int &order);
void onFolderDeleted(const int &folderId, const QStringList &apps);
void onFolderChanged(const int &folderId, const QString &appId);
QVariant folderData(const QModelIndex &index, int role) const;
QVariant fileData(const QModelIndex &index, int role) const;
private:
/**
*baseModel的对应index
*/
QVector<QPersistentModelIndex> m_favoritesApps;
/**
*Id
*/
QVector<int> m_folders;
/**
*
*/
QVector<QString> m_favoritesFiles;
BasicAppModel *m_sourceModel = nullptr;
};
} // UkuiMenu
#endif //UKUI_MENU_APP_FAVORITE_MODEL_H

View File

@ -17,7 +17,9 @@
*/
#include "favorite-context-menu.h"
#include "favorite-folder-helper.h"
#include "app-data-manager.h"
#include "favorites-model.h"
#include "event-track.h"
namespace UkuiMenu {
@ -35,33 +37,51 @@ FavoriteContextMenu::actions(const DataEntity &data, QMenu *parent, const MenuIn
return {};
}
if (data.type() != DataType::Normal) {
return {};
}
QList<QAction *> list;
if (data.type() == DataType::Folder) {
list << new QAction(QObject::tr("Dissolve folder"), parent);
QObject::connect(list.last(), &QAction::triggered, parent, [data] {
FavoriteFolderHelper::instance()->deleteFolder(data.id().toInt());
});
return list;
}
switch (location) {
case MenuInfo::AppList:
case MenuInfo::FolderPage:
case MenuInfo::Extension: {
case MenuInfo::FolderPage: {
if (data.favorite() == 0) {
list << new QAction(QObject::tr("Fix to favorite"), parent);
QObject::connect(list.last(), &QAction::triggered, parent, [data] {
Q_EMIT AppDataManager::instance()->fixToFavoriteSignal(data.id(), 1);
FavoritesModel::instance().addAppToFavorites(data.id());
EventTrack::instance()->sendDefaultEvent("fix_to_favorite", "Right-click Menu");
});
} else if (locationId == "favorite") {
} else if (data.favorite() > 0) {
list << new QAction(QObject::tr("Remove from favorite"), parent);
QObject::connect(list.last(), &QAction::triggered, parent, [data] {
Q_EMIT AppDataManager::instance()->fixToFavoriteSignal(data.id(), 0);
FavoritesModel::instance().removeAppFromFavorites(data.id());
EventTrack::instance()->sendDefaultEvent("remove_from_favorite", "Right-click Menu");
});
}
break;
}
case MenuInfo::Extension: {
QAction *action = new QAction(QObject::tr("Remove from favorite"), parent);
QObject::connect(action, &QAction::triggered, parent, [data] {
FavoritesModel::instance().removeAppFromFavorites(data.id());
EventTrack::instance()->sendDefaultEvent("remove_from_favorite", "Right-click Menu");
});
list << action;
appendActionsForAppsOutsideFolder(parent, data.id(), list);
break;
}
case MenuInfo::FullScreen:
case MenuInfo::FullScreenFolder:
case MenuInfo::Folder: {
appendActionsForAppsInFolder(parent, data.id(), list);
break;
}
default:
break;
}
@ -69,4 +89,39 @@ FavoriteContextMenu::actions(const DataEntity &data, QMenu *parent, const MenuIn
return list;
}
void FavoriteContextMenu::appendActionsForAppsInFolder(QObject *parent, const QString &appId, QList<QAction *> &list)
{
FavoritesFolder folder;
list << new QAction(QObject::tr("Remove from folder"), parent);
QObject::connect(list.last(), &QAction::triggered, parent, [appId] {
FavoriteFolderHelper::instance()->removeAppFromFolder(appId);
});
}
void FavoriteContextMenu::appendActionsForAppsOutsideFolder(QObject *parent, const QString &appId, QList<QAction *> &list)
{
QAction *action = new QAction(QObject::tr("Add to folder"), parent);
QMenu *subMenu = new QMenu();
QAction *subAction = new QAction(QObject::tr("Create a new folder"), parent);
QObject::connect(subAction, &QAction::triggered, parent, [appId] {
FavoritesModel::instance().addAppToFolder(appId, "");
});
subMenu->addAction(subAction);
for (auto folder : FavoriteFolderHelper::instance()->folderData()) {
QString folderName = folder.getName();
int folderId = folder.getId();
QAction *subAction = new QAction(QObject::tr("Add to ").append(folderName), parent);
QObject::connect(subAction, &QAction::triggered, parent, [appId, folderId] {
FavoritesModel::instance().addAppToFolder(appId, QString::number(folderId));
});
subMenu->addAction(subAction);
}
action->setMenu(subMenu);
list << action;
}
} // UkuiMenu

View File

@ -30,6 +30,10 @@ public:
QList<QAction *> actions(const DataEntity &data, QMenu *parent,
const MenuInfo::Location &location,
const QString &locationId) override;
private:
void appendActionsForAppsInFolder(QObject *parent, const QString &appId, QList<QAction *> &list);
void appendActionsForAppsOutsideFolder(QObject *parent, const QString &appId, QList<QAction *> &list);
};
} // UkuiMenu

View File

@ -0,0 +1,354 @@
/*
* 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 "favorite-folder-helper.h"
#include "app-data-manager.h"
#include "model-manager.h"
#include "app-model.h"
#include "event-track.h"
#include "favorites-config.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QMenu>
#define FOLDER_FILE_PATH ".config/ukui-menu/"
#define FOLDER_FILE_NAME "folder0.json"
namespace UkuiMenu {
QString FavoriteFolderHelper::s_folderConfigFile = QDir::homePath() + "/" + FOLDER_FILE_PATH + FOLDER_FILE_NAME;
FavoriteFolderHelper *FavoriteFolderHelper::instance()
{
static FavoriteFolderHelper FavoriteFolderHelper;
return &FavoriteFolderHelper;
}
FavoriteFolderHelper::FavoriteFolderHelper()
{
qRegisterMetaType<FavoritesFolder>("FavoritesFolder");
if (!QFile::exists(s_folderConfigFile)) {
QDir dir;
QString folderConfigDir(QDir::homePath() + "/" + FOLDER_FILE_PATH);
if (!dir.exists(folderConfigDir)) {
if (!dir.mkdir(folderConfigDir)) {
qWarning() << "Unable to create profile folder.";
return;
}
}
QFile file(s_folderConfigFile);
file.open(QFile::WriteOnly);
file.close();
}
readData();
}
FavoriteFolderHelper::~FavoriteFolderHelper()
{
saveData();
}
void FavoriteFolderHelper::insertFolder(const FavoritesFolder &folder)
{
QMutexLocker locker(&m_mutex);
m_folders.insert(folder.id, folder);
}
void FavoriteFolderHelper::addAppToFolder(const QString &appId, const int &folderId)
{
if (appId.isEmpty()) {
return;
}
{
QMutexLocker locker(&m_mutex);
if (!m_folders.contains(folderId)) {
return;
}
FavoritesFolder &folder = m_folders[folderId];
if (folder.apps.contains(appId)) {
return;
}
folder.apps.append(appId);
}
forceSync();
Q_EMIT folderDataChanged(folderId,appId);
EventTrack::instance()->sendDefaultEvent("add_app_to_folder", "AppView");
}
void FavoriteFolderHelper::addAppToNewFolder(const QString &appId, const QString &folderName)
{
if (appId.isEmpty()) {
return;
}
// TODO: max越界处理
int max = m_folders.isEmpty() ? -1 : m_folders.lastKey();
QString name = folderName;
if (name.isEmpty()) {
name = tr("New Folder %1").arg(m_folders.size() + 1);
}
FavoritesFolder folder;
folder.id = ++max;
folder.name = name;
folder.apps.append(appId);
insertFolder(folder);
forceSync();
Q_EMIT folderAdded(folder.id, FavoritesConfig::instance().getOrderById(appId));
EventTrack::instance()->sendDefaultEvent("add_app_to_new_folder", "AppView");
}
void FavoriteFolderHelper::addAppsToNewFolder(const QString &idFrom, const QString &idTo, const QString &folderName)
{
if (idFrom.isEmpty() || idTo.isEmpty()) {
return;
}
// TODO: max越界处理
int max = m_folders.isEmpty() ? -1 : m_folders.lastKey();
QString name = folderName;
if (name.isEmpty()) {
name = tr("New Folder %1").arg(m_folders.size() + 1);
}
FavoritesFolder folder;
folder.id = ++max;
folder.name = name;
folder.apps.append(idFrom);
folder.apps.append(idTo);
insertFolder(folder);
Q_EMIT folderAdded(folder.id, FavoritesConfig::instance().getOrderById(idTo));
forceSync();
}
void FavoriteFolderHelper::removeAppFromFolder(const QString& appId)
{
if (appId.isEmpty()) {
return;
}
int folderId;
{
QMutexLocker locker(&m_mutex);
for (const auto &folder: m_folders) {
if (folder.apps.contains(appId)) {
folderId = folder.getId();
}
}
if (!m_folders.contains(folderId)) {
return;
}
FavoritesFolder &folder = m_folders[folderId];
if (!folder.apps.contains(appId)) {
return;
}
folder.apps.removeOne(appId);
}
if (m_folders[folderId].getApps().isEmpty()) {
deleteFolder(folderId);
}
forceSync();
Q_EMIT folderDataChanged(folderId, appId);
}
bool FavoriteFolderHelper::deleteFolder(const int& folderId)
{
Q_EMIT folderToBeDeleted(folderId, m_folders[folderId].getApps());
{
QMutexLocker locker(&m_mutex);
if (!m_folders.contains(folderId)) {
return false;
}
if (!m_folders.remove(folderId)) {
return false;
}
}
forceSync();
EventTrack::instance()->sendDefaultEvent("delete_folder", "AppView");
return true;
}
void FavoriteFolderHelper::renameFolder(const int &folderId, const QString &folderName)
{
{
QMutexLocker locker(&m_mutex);
if (!m_folders.contains(folderId)) {
return;
}
if (m_folders[folderId].name == folderName) {
return;
}
m_folders[folderId].name = folderName;
}
Q_EMIT folderDataChanged(folderId, "");
forceSync();
}
QList<FavoritesFolder> FavoriteFolderHelper::folderData()
{
QMutexLocker locker(&m_mutex);
return m_folders.values();
}
bool FavoriteFolderHelper::getFolderFromId(const int &folderId, FavoritesFolder& folder)
{
QMutexLocker locker(&m_mutex);
if (!m_folders.contains(folderId)) {
return false;
}
const FavoritesFolder &tmp = m_folders[folderId];
folder = tmp;
return true;
}
bool FavoriteFolderHelper::containApp(const QString &appId)
{
QMutexLocker locker(&m_mutex);
return std::any_of(m_folders.constBegin(), m_folders.constEnd(), [appId] (const FavoritesFolder &folder) {
return folder.apps.contains(appId);
});
}
void FavoriteFolderHelper::forceSync()
{
saveData();
}
void FavoriteFolderHelper::readData()
{
QFile file(s_folderConfigFile);
if (!file.open(QFile::ReadOnly)) {
return;
}
// 读取json数据
QByteArray byteArray = file.readAll();
file.close();
QJsonDocument jsonDocument(QJsonDocument::fromJson(byteArray));
if (jsonDocument.isNull() || jsonDocument.isEmpty() || !jsonDocument.isArray()) {
qWarning() << "FavoriteFolderHelper: Incorrect configuration files are ignored.";
return;
}
{
QMutexLocker locker(&m_mutex);
m_folders.clear();
}
// 遍历json数据节点
QJsonArray jsonArray = jsonDocument.array();
for (const auto &value : jsonArray) {
QJsonObject object = value.toObject();
if (object.contains("name") && object.contains("id") && object.contains("apps")) {
FavoritesFolder folder;
folder.name = object.value(QLatin1String("name")).toString();
folder.id = object.value(QLatin1String("id")).toInt();
QJsonArray apps = object.value(QLatin1String("apps")).toArray();
for (const auto &app : apps) {
folder.apps.append(app.toString());
}
if (!folder.apps.isEmpty()) {
insertFolder(folder);
}
}
}
}
void FavoriteFolderHelper::saveData()
{
QFile file(s_folderConfigFile);
if (!file.open(QFile::WriteOnly)) {
return;
}
QJsonDocument jsonDocument;
QJsonArray folderArray;
{
QMutexLocker locker(&m_mutex);
for (const auto &folder : m_folders) {
QJsonObject object;
QJsonArray apps;
for (const auto &app : folder.apps) {
apps.append(app);
}
object.insert("name", folder.name);
object.insert("id", folder.id);
object.insert("apps", apps);
folderArray.append(object);
}
}
jsonDocument.setArray(folderArray);
if (file.write(jsonDocument.toJson()) == -1) {
qWarning() << "Error saving configuration file.";
}
file.flush();
file.close();
}
QStringList FavoriteFolderHelper::folderIcon(const FavoritesFolder &folder)
{
// TODO: 使用绘图API生成图片
QStringList icons;
DataEntity app;
int count = qMin(folder.apps.count(), FOLDER_MAX_ICON_NUM);
for (int i = 0; i < count; ++i) {
if (AppDataManager::instance()->getApp(folder.apps.at(i), app)) {
icons.append(app.icon());
}
}
return icons;
}
} // UkuiMenu

View File

@ -0,0 +1,97 @@
/*
* 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_FAVORITE_FOLDER_HELPER_H
#define UKUI_MENU_FAVORITE_FOLDER_HELPER_H
#include <QObject>
#include <QMutex>
#include <QMutexLocker>
#include <QMap>
#include <QString>
#include <QStringList>
namespace UkuiMenu {
#define FOLDER_MAX_ICON_NUM 16
class FavoriteFolderHelper;
class FavoritesFolder
{
Q_GADGET
Q_PROPERTY(int id READ getId WRITE setId)
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(QStringList apps READ getApps)
friend class FavoriteFolderHelper;
public:
void setId(int folderId) {id = folderId;}
int getId() const { return id; }
QString getName() const { return name; }
QStringList getApps() const { return apps; }
private:
int id; // 文件夹唯一Id文件夹排序值
QString name; // 名称
QStringList apps; // 应用列表
};
class FavoriteFolderHelper : public QObject
{
Q_OBJECT
public:
static FavoriteFolderHelper *instance();
static QStringList folderIcon(const FavoritesFolder &folder);
~FavoriteFolderHelper() override;
bool getFolderFromId(const int& folderId, FavoritesFolder& folder);
bool containApp(const QString& appId);
QList<FavoritesFolder> folderData();
void addAppToFolder(const QString& appId, const int& folderId);
void addAppToNewFolder(const QString& appId, const QString& folderName);
void addAppsToNewFolder(const QString& idFrom, const QString& idTo, const QString& folderName);
void removeAppFromFolder(const QString& appId);
bool deleteFolder(const int& folderId);
void renameFolder(const int& folderId, const QString& folderName);
void forceSync();
Q_SIGNALS:
void folderAdded(int folderId, const int& order);
void folderToBeDeleted(int folderId, const QStringList& apps);
void folderDataChanged(int folderId, const QString& app);
private:
FavoriteFolderHelper();
void readData();
void saveData();
void insertFolder(const FavoritesFolder& folder);
// TODO 配置文件监听
private:
QMutex m_mutex;
//TODO 指针
QMap<int, FavoritesFolder> m_folders;
static QString s_folderConfigFile;
};
} // UkuiMenu
#endif //UKUI_MENU_FAVORITE_FOLDER_HELPER_H

View File

@ -17,6 +17,8 @@
*/
#include "favorite-widget.h"
#include "app-favorite-model.h"
#include "favorites-model.h"
namespace UkuiMenu {
@ -31,6 +33,10 @@ FavoriteWidget::FavoriteWidget(QObject *parent) : WidgetExtension(parent)
m_metadata.insert(WidgetMetadata::Main, "qrc:///qml/extensions/FavoriteExtension.qml");
m_metadata.insert(WidgetMetadata::Type, WidgetMetadata::Widget);
m_metadata.insert(WidgetMetadata::Flag, WidgetMetadata::Normal);
FavoritesModel::instance().setSourceModel(&AppFavoritesModel::instance());
FavoritesModel::instance().sort(0);
m_data.insert("favoriteAppsModel", QVariant::fromValue(&FavoritesModel::instance()));
}
int FavoriteWidget::index() const
@ -45,7 +51,7 @@ MetadataMap FavoriteWidget::metadata() const
QVariantMap FavoriteWidget::data()
{
return WidgetExtension::data();
return m_data;
}
void FavoriteWidget::receive(const QVariantMap &data)

View File

@ -36,6 +36,7 @@ public:
private:
MetadataMap m_metadata;
QVariantMap m_data;
};
} // UkuiMenu

View File

@ -0,0 +1,162 @@
//
// Created by qiqi on 23-12-27.
//
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDir>
#include <QFile>
#include "favorites-config.h"
#define FOLDER_FILE_PATH ".config/ukui-menu/"
#define FOLDER_FILE_NAME "favorite.json"
namespace UkuiMenu {
QString FavoritesConfig::s_favoritesConfigFile = QDir::homePath() + "/" + FOLDER_FILE_PATH + FOLDER_FILE_NAME;
FavoritesConfig &FavoritesConfig::instance()
{
static FavoritesConfig favoritesConfig;
return favoritesConfig;
}
FavoritesConfig::FavoritesConfig(QObject *parent)
{
}
QJsonObject FavoritesConfig::getValue(const int &index) const
{
if (index < 0 || index > arrayFromConfigFile().count()) {
return {};
}
return arrayFromConfigFile().at(index).toObject();
}
void FavoritesConfig::insertValue(const QString &id, const int &index)
{
QJsonArray array = arrayFromConfigFile();
if (indexOfObject(id, array) > -1) {
return;
}
QJsonObject object;
object.insert("id", id);
array.insert(index, object);
setArrayToConfigFile(array);
}
void FavoritesConfig::removeValueById(const QString &id)
{
QJsonArray array = arrayFromConfigFile();
int index = indexOfObject(id, array);
if (index > -1 && index < array.size()) {
array.removeAt(index);
}
setArrayToConfigFile(array);
}
void FavoritesConfig::forceSync()
{
}
QString FavoritesConfig::configFile() const
{
return s_favoritesConfigFile;
}
void FavoritesConfig::initConfig(const QStringList &apps)
{
QJsonArray favoritesArray;
for (const auto &appId : apps) {
QJsonObject object;
object.insert("id", appId);
favoritesArray.append(object);
}
setArrayToConfigFile(favoritesArray);
}
void FavoritesConfig::setArrayToConfigFile(const QJsonArray &array)
{
QDir dir;
QString folderConfigDir(QDir::homePath() + "/" + FOLDER_FILE_PATH);
if (!dir.exists(folderConfigDir)) {
if (!dir.mkdir(folderConfigDir)) {
qWarning() << "Unable to create profile favorites.";
return;
}
}
QFile file(s_favoritesConfigFile);
file.open(QFile::WriteOnly);
QJsonDocument jsonDocument;
jsonDocument.setArray(array);
if (file.write(jsonDocument.toJson()) == -1) {
qWarning() << "Error saving configuration file.";
}
file.flush();
file.close();
Q_EMIT configChanged();
}
QJsonArray FavoritesConfig::arrayFromConfigFile() const
{
QFile file(s_favoritesConfigFile);
if (!file.open(QFile::ReadOnly)) {
return {};
}
QJsonDocument jsonDocument(QJsonDocument::fromJson(file.readAll()));
file.close();
if (jsonDocument.isNull() || jsonDocument.isEmpty() || !jsonDocument.isArray()) {
qWarning() << "AppFolderHelper: Incorrect configuration files are ignored.";
return {};
}
return jsonDocument.array();
}
int FavoritesConfig::indexOfObject(const QString &id, const QJsonArray &array)
{
if (id.isEmpty()) {
qWarning() << "id is unavailable!";
return -1;
}
for (int i = 0; i < array.size(); i++) {
if (array.at(i).toObject().contains("id")) {
if (array.at(i).toObject().value("id").toString() == id) {
return i;
}
}
}
return -1;
}
int FavoritesConfig::getOrderById(const QString &id)
{
return indexOfObject(id, arrayFromConfigFile());
}
void FavoritesConfig::changeOrder(const int &indexFrom, const int &indexTo)
{
QVariantList list = arrayFromConfigFile().toVariantList();
list.move(indexFrom, indexTo);
setArrayToConfigFile(QJsonArray::fromVariantList(list));
}
} // UkuiMenu

View File

@ -0,0 +1,45 @@
//
// Created by qiqi on 23-12-27.
//
#ifndef UKUI_MENU_FAVORITES_CONFIG_H
#define UKUI_MENU_FAVORITES_CONFIG_H
#include <QObject>
#include <QVariant>
#include <QJsonObject>
namespace UkuiMenu {
class FavoritesConfig : public QObject
{
Q_OBJECT
public:
static FavoritesConfig &instance();
QJsonObject getValue(const int &index) const;
void insertValue(const QString &id, const int &index = 0);
void removeValueById(const QString &id);
int getOrderById(const QString &id);
void changeOrder(const int &indexFrom, const int &indexTo);
void forceSync();
QString configFile() const;
void initConfig(const QStringList &apps);
Q_SIGNALS:
void configChanged();
private:
explicit FavoritesConfig(QObject *parent = nullptr);
void setArrayToConfigFile(const QJsonArray &array);
QJsonArray arrayFromConfigFile() const;
int indexOfObject(const QString &id, const QJsonArray &array);
private:
static QString s_favoritesConfigFile;
};
} // UkuiMenu
#endif //UKUI_MENU_FAVORITES_CONFIG_H

View File

@ -0,0 +1,95 @@
//
// Created by qiqi on 23-12-28.
//
#include <QDir>
#include "../context-menu-manager.h"
#include "favorites-model.h"
#include "app-favorite-model.h"
#include "favorite-folder-helper.h"
namespace UkuiMenu {
FavoritesModel &FavoritesModel::instance()
{
static FavoritesModel favoritesModel;
return favoritesModel;
}
FavoritesModel::FavoritesModel(QObject *parent) : QSortFilterProxyModel(parent)
{
connect(&FavoritesConfig::instance(), &FavoritesConfig::configChanged, this, &FavoritesModel::invalidate);
}
bool FavoritesModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
QVariant leftId = sourceModel()->data(source_left, DataEntity::Id);
QVariant rightId = sourceModel()->data(source_right, DataEntity::Id);
int leftIndex = -1;
int rightIndex = -1;
for (int i = 0; i < sourceModel()->rowCount(); i++) {
QJsonObject object = FavoritesConfig::instance().getValue(i);
if (object.contains("id")) {
if (object.value(QLatin1String("id")).toString() == leftId) {
leftIndex = i;
} else if (object.value(QLatin1String("id")).toString() == rightId) {
rightIndex = i;
}
}
if ((leftIndex != -1) && (rightIndex != -1)) {
return leftIndex < rightIndex;
}
}
// 对应Id没有在配置文件中找到
return true;
}
void FavoritesModel::openMenu(const int &row)
{
if (row < 0 || row >= sourceModel()->rowCount()) {
return;
}
DataEntity data;
data.setId(sourceModel()->data(mapToSource(index(row, 0)), DataEntity::Id).toString());
data.setFavorite(sourceModel()->data(mapToSource(index(row, 0)), DataEntity::Favorite).toInt());
data.setType(sourceModel()->data(mapToSource(index(row, 0)), DataEntity::Type).value<DataType::Type>());
ContextMenuManager::instance()->showMenu(data, MenuInfo::Extension, "favorite");
}
void FavoritesModel::addAppToFavorites(const QString &id, const int &index)
{
AppFavoritesModel::instance().changeAppState(id, index);
}
void FavoritesModel::removeAppFromFavorites(const QString &id)
{
AppFavoritesModel::instance().changeAppState(id, 0);
}
void FavoritesModel::exchangedAppsOrder(const int &indexFrom, const int &indexTo)
{
FavoritesConfig::instance().changeOrder(indexFrom, indexTo);
}
void FavoritesModel::addAppsToNewFolder(const QString &idFrom, const QString &idTo)
{
FavoriteFolderHelper::instance()->addAppsToNewFolder(idFrom, idTo, "");
}
void FavoritesModel::addAppToFolder(const QString &appId, const QString &folderId)
{
if (folderId == "") {
FavoriteFolderHelper::instance()->addAppToNewFolder(appId, "");
return;
}
FavoriteFolderHelper::instance()->addAppToFolder(appId, folderId.toInt());
}
} // UkuiMenu

View File

@ -0,0 +1,51 @@
//
// Created by qiqi on 23-12-28.
//
#ifndef UKUI_MENU_FAVORITES_MODEL_H
#define UKUI_MENU_FAVORITES_MODEL_H
#include <QSortFilterProxyModel>
#include "favorites-config.h"
#include "app-favorite-model.h"
namespace UkuiMenu {
class FavoritesModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
static FavoritesModel &instance();
Q_INVOKABLE void openMenu(const int &row);
Q_INVOKABLE void addAppToFavorites(const QString &id, const int &index = 1);
Q_INVOKABLE void removeAppFromFavorites(const QString &id);
/**
*
* @param indexFrom
* @param indexTo
*/
Q_INVOKABLE void exchangedAppsOrder(const int &indexFrom, const int &indexTo);
/**
*
* @param idFrom
* @param idTo
*/
Q_INVOKABLE void addAppsToNewFolder(const QString &idFrom, const QString &idTo);
/**
*
* @param appId
* @param folderId id为"",id为数值时
*/
Q_INVOKABLE void addAppToFolder(const QString &appId, const QString& folderId);
protected:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
private:
explicit FavoritesModel(QObject *parent = nullptr);
};
} // UkuiMenu
#endif //UKUI_MENU_FAVORITES_MODEL_H

View File

@ -71,7 +71,7 @@ QList<QAction *> AppContentMenu::actions(const DataEntity &data, QMenu *parent,
case MenuInfo::Extension:
case MenuInfo::FolderPage:
case MenuInfo::FullScreen:
case MenuInfo::FullScreenFolder:
case MenuInfo::Folder:
// 卸载
addToPanelAction(parent, appId, list);
// 添加到任务栏

View File

@ -38,6 +38,7 @@ class AppDatabaseWorkerPrivate : public QObject
public:
explicit AppDatabaseWorkerPrivate(AppDatabaseInterface *parent = nullptr);
DataEntityVector getAllApps();
bool getApp(const QString &appid, DataEntity &app);
// 数据库操作函数
void setAppProperty(const QString &appid, const UkuiSearch::ApplicationPropertyMap &propertyMap);
@ -317,6 +318,17 @@ void AppDatabaseWorkerPrivate::setAppProperty(const QString &appid, const UkuiSe
}
}
bool AppDatabaseWorkerPrivate::getApp(const QString &appid, DataEntity &app)
{
if (appid.isEmpty()) {
return false;
}
const UkuiSearch::ApplicationPropertyMap appInfo = appDatabase->getInfo(appid, properties);
addInfoToApp(appInfo, app);
return true;
}
// ====== AppDatabaseInterface ====== //
AppDatabaseInterface::AppDatabaseInterface(QObject *parent) : QObject(parent), d(new AppDatabaseWorkerPrivate(this))
{
@ -328,7 +340,7 @@ DataEntityVector AppDatabaseInterface::apps() const
return d->getAllApps();
}
void AppDatabaseInterface::fixAppToTop(const QString &appid, int index)
void AppDatabaseInterface::fixAppToTop(const QString &appid, int index) const
{
if (index < 0) {
index = 0;
@ -337,7 +349,7 @@ void AppDatabaseInterface::fixAppToTop(const QString &appid, int index)
d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Top, index);
}
void AppDatabaseInterface::fixAppToFavorite(const QString &appid, int index)
void AppDatabaseInterface::fixAppToFavorite(const QString &appid, int index) const
{
if (index < 0) {
index = 0;
@ -346,11 +358,16 @@ void AppDatabaseInterface::fixAppToFavorite(const QString &appid, int index)
d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Favorites, index);
}
void AppDatabaseInterface::updateApLaunchedState(const QString &appid, bool state)
void AppDatabaseInterface::updateApLaunchedState(const QString &appid, bool state) const
{
d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Launched, state);
}
bool AppDatabaseInterface::getApp(const QString &appid, DataEntity &app) const
{
return d->getApp(appid, app);
}
} // UkuiMenu
#include "app-database-interface.moc"

View File

@ -45,26 +45,32 @@ public:
* @return
*/
DataEntityVector apps() const;
/**
*
* @param appid id
* @param app
*/
bool getApp(const QString &appid, DataEntity &app) const;
/**
*
* @param appid id
* @param index 0
*/
void fixAppToTop(const QString &appid, int index);
void fixAppToTop(const QString &appid, int index) const;
/**
*
* @param appid id
* @param index 0
*/
void fixAppToFavorite(const QString &appid, int index);
void fixAppToFavorite(const QString &appid, int index) const;
/**
*
* @param state true已被启动false未被启动
*/
void updateApLaunchedState(const QString &appid, bool state = true);
void updateApLaunchedState(const QString &appid, bool state = true) const;
Q_SIGNALS:
/**

View File

@ -136,7 +136,7 @@ void BasicAppModel::onAppUpdated(const QVector<QPair<DataEntity, QVector<int> >
QVector<int> roles = pair.second;
if (!roles.isEmpty()) {
for (const auto &role : roles) {
app.setValue(static_cast<DataEntity::PropertyName>(role), static_cast<DataEntity::PropertyName>(role));
app.setValue(static_cast<DataEntity::PropertyName>(role), pair.first.getValue(static_cast<DataEntity::PropertyName>(role)));
}
} else {
app = pair.first;

View File

@ -41,6 +41,7 @@ public:
*/
const AppDatabaseInterface *databaseInterface() const;
DataEntity appOfIndex(int row) const;
int indexOfApp(const QString &appid) const;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
@ -55,7 +56,6 @@ private Q_SLOTS:
private:
explicit BasicAppModel(QObject *parent = nullptr);
int indexOfApp(const QString &appid) const;
AppDatabaseInterface *m_databaseInterface {nullptr};
QVector<DataEntity> m_apps;

View File

@ -19,13 +19,15 @@
#include "folder-model.h"
#include "app-folder-helper.h"
#include "app-data-manager.h"
#include "favorite/favorite-folder-helper.h"
#include "context-menu-manager.h"
namespace UkuiMenu {
FolderModel::FolderModel(QObject *parent) : QAbstractListModel(parent)
{
connect(AppFolderHelper::instance(), &AppFolderHelper::folderDataChanged, this, &FolderModel::loadFolderData);
connect(AppFolderHelper::instance(), &AppFolderHelper::folderDeleted, this, [this] (int id) {
connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderDataChanged, this, &FolderModel::loadFolderData);
connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderToBeDeleted, this, [this] (int id) {
if (id == m_folderId) {
beginResetModel();
m_folderId = -1;
@ -48,13 +50,13 @@ void FolderModel::setFolderId(const QString &folderId)
void FolderModel::renameFolder(const QString &folderName)
{
AppFolderHelper::instance()->renameFolder(m_folderId, folderName);
FavoriteFolderHelper::instance()->renameFolder(m_folderId, folderName);
}
void FolderModel::loadFolderData(int id)
{
Folder folder;
if (!AppFolderHelper::instance()->searchFolder(id, folder)) {
FavoritesFolder folder;
if (!FavoriteFolderHelper::instance()->getFolderFromId(id, folder)) {
return;
}