// 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 #include #include 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 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 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 ApplicationUsageModel::roleNames() const { return { {DelegateRole, QByteArrayLiteral("delegate")}, {LaunchCountRole, QByteArrayLiteral("launchCount")}, {LastUsedRole, QByteArrayLiteral("lastUsed")}, }; } void ApplicationUsageModel::rebuild() { QList entries = m_store ? m_store->entries() : QList{}; 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 nextEntries; QList> 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(); }