shift-shell/containments/homescreens/folio/applicationusagemodel.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

208 lines
6 KiB
C++
Raw Normal View History

// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
#include "applicationusagemodel.h"
#include "applicationlistmodel.h"
#include "foliodelegate.h"
#include "foliosettings.h"
#include "homescreen.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace
{
constexpr qsizetype s_maxStoredEntries = 24;
QString normalizedStorageId(const QString &storageId)
{
if (storageId.isEmpty()) {
return {};
}
if (storageId.endsWith(QLatin1String(".desktop"))) {
return storageId;
}
return storageId + QStringLiteral(".desktop");
}
}
ApplicationUsageStore::ApplicationUsageStore(HomeScreen *parent)
: QObject{parent}
, m_homeScreen{parent}
{
load();
}
QList<ApplicationUsageEntry> ApplicationUsageStore::entries() const
{
return m_entries.values();
}
void ApplicationUsageStore::recordUsage(const QString &storageId)
{
const QString normalizedId = normalizedStorageId(storageId);
if (normalizedId.isEmpty()) {
return;
}
auto &entry = m_entries[normalizedId];
entry.storageId = normalizedId;
entry.launchCount = std::max(0, entry.launchCount) + 1;
entry.lastUsed = QDateTime::currentDateTimeUtc();
save();
Q_EMIT usageChanged();
}
void ApplicationUsageStore::load()
{
m_entries.clear();
if (!m_homeScreen) {
return;
}
const QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->folioSettings()->applicationUsage().toUtf8());
const QJsonArray usageArray = doc.array();
for (const QJsonValue &value : usageArray) {
const QJsonObject object = value.toObject();
const QString storageId = normalizedStorageId(object.value(QStringLiteral("storageId")).toString());
if (storageId.isEmpty()) {
continue;
}
ApplicationUsageEntry entry;
entry.storageId = storageId;
entry.launchCount = object.value(QStringLiteral("launchCount")).toInt();
entry.lastUsed = QDateTime::fromString(object.value(QStringLiteral("lastUsed")).toString(), Qt::ISODateWithMs);
if (entry.launchCount <= 0 || !entry.lastUsed.isValid()) {
continue;
}
m_entries.insert(storageId, entry);
}
}
void ApplicationUsageStore::save()
{
if (!m_homeScreen) {
return;
}
QList<ApplicationUsageEntry> entries = m_entries.values();
std::sort(entries.begin(), entries.end(), [](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
return left.lastUsed > right.lastUsed;
});
if (entries.size() > s_maxStoredEntries) {
entries.resize(s_maxStoredEntries);
}
QJsonArray usageArray;
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
QJsonObject object;
object.insert(QStringLiteral("storageId"), entry.storageId);
object.insert(QStringLiteral("launchCount"), entry.launchCount);
object.insert(QStringLiteral("lastUsed"), entry.lastUsed.toString(Qt::ISODateWithMs));
usageArray.append(object);
}
m_homeScreen->folioSettings()->setApplicationUsage(QString::fromUtf8(QJsonDocument(usageArray).toJson(QJsonDocument::Compact)));
}
ApplicationUsageModel::ApplicationUsageModel(HomeScreen *homeScreen, ApplicationUsageStore *store, Mode mode)
: QAbstractListModel{homeScreen}
, m_homeScreen{homeScreen}
, m_store{store}
, m_mode{mode}
{
if (m_store) {
connect(m_store, &ApplicationUsageStore::usageChanged, this, &ApplicationUsageModel::rebuild);
}
if (m_homeScreen && m_homeScreen->applicationListModel()) {
auto *applicationListModel = m_homeScreen->applicationListModel();
connect(applicationListModel, &QAbstractItemModel::rowsInserted, this, &ApplicationUsageModel::rebuild);
connect(applicationListModel, &QAbstractItemModel::rowsRemoved, this, &ApplicationUsageModel::rebuild);
connect(applicationListModel, &QAbstractItemModel::modelReset, this, &ApplicationUsageModel::rebuild);
}
rebuild();
}
int ApplicationUsageModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_entries.size();
}
QVariant ApplicationUsageModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size()) {
return QVariant();
}
const ApplicationUsageEntry &entry = m_entries.at(index.row());
switch (role) {
case Qt::DisplayRole:
case DelegateRole:
return QVariant::fromValue(m_delegates.at(index.row()).get());
case LaunchCountRole:
return entry.launchCount;
case LastUsedRole:
return entry.lastUsed;
default:
return QVariant();
}
}
QHash<int, QByteArray> ApplicationUsageModel::roleNames() const
{
return {
{DelegateRole, QByteArrayLiteral("delegate")},
{LaunchCountRole, QByteArrayLiteral("launchCount")},
{LastUsedRole, QByteArrayLiteral("lastUsed")},
};
}
void ApplicationUsageModel::rebuild()
{
QList<ApplicationUsageEntry> entries = m_store ? m_store->entries() : QList<ApplicationUsageEntry>{};
std::sort(entries.begin(), entries.end(), [this](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
if (m_mode == MostUsed && left.launchCount != right.launchCount) {
return left.launchCount > right.launchCount;
}
return left.lastUsed > right.lastUsed;
});
QList<ApplicationUsageEntry> nextEntries;
QList<std::shared_ptr<FolioDelegate>> nextDelegates;
if (m_homeScreen && m_homeScreen->applicationListModel()) {
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
auto delegate = m_homeScreen->applicationListModel()->delegateForStorageId(entry.storageId);
if (!delegate || !delegate->application()) {
continue;
}
nextEntries.append(entry);
nextDelegates.append(delegate);
}
}
beginResetModel();
m_entries = nextEntries;
m_delegates = nextDelegates;
endResetModel();
}