File indexing completed on 2024-12-22 05:15:22

0001 /*
0002     SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "rootmodel.h"
0008 #include "actionlist.h"
0009 #include "kastatsfavoritesmodel.h"
0010 #include "recentusagemodel.h"
0011 #include "systemmodel.h"
0012 
0013 #include <KLocalizedString>
0014 
0015 #include <QCollator>
0016 
0017 GroupEntry::GroupEntry(AppsModel *parentModel, const QString &name, const QString &iconName, AbstractModel *childModel)
0018     : AbstractGroupEntry(parentModel)
0019     , m_name(name)
0020     , m_iconName(iconName)
0021     , m_childModel(childModel)
0022 {
0023     QObject::connect(parentModel, &RootModel::cleared, childModel, &AbstractModel::deleteLater);
0024 
0025     QObject::connect(childModel, &AbstractModel::countChanged, [parentModel, this] {
0026         if (parentModel) {
0027             parentModel->entryChanged(this);
0028         }
0029     });
0030 }
0031 
0032 QString GroupEntry::name() const
0033 {
0034     return m_name;
0035 }
0036 
0037 QString GroupEntry::icon() const
0038 {
0039     return m_iconName;
0040 }
0041 
0042 bool GroupEntry::hasChildren() const
0043 {
0044     return m_childModel && m_childModel->count() > 0;
0045 }
0046 
0047 AbstractModel *GroupEntry::childModel() const
0048 {
0049     return m_childModel;
0050 }
0051 
0052 RootModel::RootModel(QObject *parent)
0053     : AppsModel(QString(), parent)
0054     , m_favorites(new KAStatsFavoritesModel(this))
0055     , m_systemModel(nullptr)
0056     , m_showAllApps(false)
0057     , m_showAllAppsCategorized(false)
0058     , m_showRecentApps(true)
0059     , m_showRecentDocs(true)
0060     , m_recentOrdering(RecentUsageModel::Recent)
0061     , m_showPowerSession(true)
0062     , m_showFavoritesPlaceholder(false)
0063     , m_recentAppsModel(nullptr)
0064     , m_recentDocsModel(nullptr)
0065 {
0066 }
0067 
0068 RootModel::~RootModel()
0069 {
0070 }
0071 
0072 QVariant RootModel::data(const QModelIndex &index, int role) const
0073 {
0074     if (!index.isValid() || index.row() >= m_entryList.count()) {
0075         return QVariant();
0076     }
0077 
0078     if (role == Kicker::HasActionListRole || role == Kicker::ActionListRole) {
0079         const AbstractEntry *entry = m_entryList.at(index.row());
0080 
0081         if (entry->type() == AbstractEntry::GroupType) {
0082             const GroupEntry *group = static_cast<const GroupEntry *>(entry);
0083             AbstractModel *model = group->childModel();
0084 
0085             if (model == m_recentAppsModel || model == m_recentDocsModel) {
0086                 if (role == Kicker::HasActionListRole) {
0087                     return true;
0088                 } else if (role == Kicker::ActionListRole) {
0089                     QVariantList actionList;
0090                     actionList << model->actions();
0091                     actionList << Kicker::createSeparatorActionItem();
0092                     actionList << Kicker::createActionItem(i18n("Hide %1", group->name()), QStringLiteral("view-hidden"), QStringLiteral("hideCategory"));
0093                     return actionList;
0094                 }
0095             }
0096         }
0097     }
0098 
0099     return AppsModel::data(index, role);
0100 }
0101 
0102 bool RootModel::trigger(int row, const QString &actionId, const QVariant &argument)
0103 {
0104     const AbstractEntry *entry = m_entryList.at(row);
0105 
0106     if (entry->type() == AbstractEntry::GroupType) {
0107         if (actionId == QLatin1String("hideCategory")) {
0108             AbstractModel *model = entry->childModel();
0109 
0110             if (model == m_recentAppsModel) {
0111                 setShowRecentApps(false);
0112 
0113                 return true;
0114             } else if (model == m_recentDocsModel) {
0115                 setShowRecentDocs(false);
0116 
0117                 return true;
0118             }
0119         } else if (entry->childModel()->hasActions()) {
0120             return entry->childModel()->trigger(-1, actionId, QVariant());
0121         }
0122     }
0123 
0124     return AppsModel::trigger(row, actionId, argument);
0125 }
0126 
0127 bool RootModel::showAllApps() const
0128 {
0129     return m_showAllApps;
0130 }
0131 
0132 void RootModel::setShowAllApps(bool show)
0133 {
0134     if (m_showAllApps != show) {
0135         m_showAllApps = show;
0136 
0137         refresh();
0138 
0139         Q_EMIT showAllAppsChanged();
0140     }
0141 }
0142 
0143 bool RootModel::showAllAppsCategorized() const
0144 {
0145     return m_showAllAppsCategorized;
0146 }
0147 
0148 void RootModel::setShowAllAppsCategorized(bool showCategorized)
0149 {
0150     if (m_showAllAppsCategorized != showCategorized) {
0151         m_showAllAppsCategorized = showCategorized;
0152 
0153         refresh();
0154 
0155         Q_EMIT showAllAppsCategorizedChanged();
0156     }
0157 }
0158 
0159 bool RootModel::showRecentApps() const
0160 {
0161     return m_showRecentApps;
0162 }
0163 
0164 void RootModel::setShowRecentApps(bool show)
0165 {
0166     if (show != m_showRecentApps) {
0167         m_showRecentApps = show;
0168 
0169         refresh();
0170 
0171         Q_EMIT showRecentAppsChanged();
0172     }
0173 }
0174 
0175 bool RootModel::showRecentDocs() const
0176 {
0177     return m_showRecentDocs;
0178 }
0179 
0180 void RootModel::setShowRecentDocs(bool show)
0181 {
0182     if (show != m_showRecentDocs) {
0183         m_showRecentDocs = show;
0184 
0185         refresh();
0186 
0187         Q_EMIT showRecentDocsChanged();
0188     }
0189 }
0190 
0191 int RootModel::recentOrdering() const
0192 {
0193     return m_recentOrdering;
0194 }
0195 
0196 void RootModel::setRecentOrdering(int ordering)
0197 {
0198     if (ordering != m_recentOrdering) {
0199         m_recentOrdering = ordering;
0200 
0201         refresh();
0202 
0203         Q_EMIT recentOrderingChanged();
0204     }
0205 }
0206 
0207 bool RootModel::showPowerSession() const
0208 {
0209     return m_showPowerSession;
0210 }
0211 
0212 void RootModel::setShowPowerSession(bool show)
0213 {
0214     if (show != m_showPowerSession) {
0215         m_showPowerSession = show;
0216 
0217         refresh();
0218 
0219         Q_EMIT showPowerSessionChanged();
0220     }
0221 }
0222 
0223 bool RootModel::showFavoritesPlaceholder() const
0224 {
0225     return m_showFavoritesPlaceholder;
0226 }
0227 
0228 void RootModel::setShowFavoritesPlaceholder(bool show)
0229 {
0230     if (show != m_showFavoritesPlaceholder) {
0231         m_showFavoritesPlaceholder = show;
0232 
0233         refresh();
0234 
0235         Q_EMIT showFavoritesPlaceholderChanged();
0236     }
0237 }
0238 
0239 AbstractModel *RootModel::favoritesModel()
0240 {
0241     return m_favorites;
0242 }
0243 
0244 AbstractModel *RootModel::systemFavoritesModel()
0245 {
0246     if (m_systemModel) {
0247         return m_systemModel->favoritesModel();
0248     }
0249 
0250     return nullptr;
0251 }
0252 
0253 void RootModel::refresh()
0254 {
0255     if (!m_complete) {
0256         return;
0257     }
0258 
0259     beginResetModel();
0260 
0261     AppsModel::refreshInternal();
0262 
0263     AppsModel *allModel = nullptr;
0264     m_recentAppsModel = nullptr;
0265     m_recentDocsModel = nullptr;
0266 
0267     if (m_showAllApps) {
0268         QHash<QString, AbstractEntry *> appsHash;
0269 
0270         std::function<void(AbstractEntry *)> processEntry = [&](AbstractEntry *entry) {
0271             if (entry->type() == AbstractEntry::RunnableType) {
0272                 AppEntry *appEntry = static_cast<AppEntry *>(entry);
0273                 appsHash.insert(appEntry->service()->menuId(), appEntry);
0274             } else if (entry->type() == AbstractEntry::GroupType) {
0275                 GroupEntry *groupEntry = static_cast<GroupEntry *>(entry);
0276                 AbstractModel *model = groupEntry->childModel();
0277 
0278                 if (!model) {
0279                     return;
0280                 }
0281 
0282                 for (int i = 0; i < model->count(); ++i) {
0283                     processEntry(static_cast<AbstractEntry *>(model->index(i, 0).internalPointer()));
0284                 }
0285             }
0286         };
0287 
0288         for (AbstractEntry *entry : std::as_const(m_entryList)) {
0289             processEntry(entry);
0290         }
0291 
0292         QList<AbstractEntry *> apps(appsHash.values());
0293         sortEntries(apps);
0294 
0295         if (!m_showAllAppsCategorized && !m_paginate) { // The app list built above goes into a model.
0296             allModel = new AppsModel(apps, false, this);
0297         } else if (m_paginate) { // We turn the apps list into a subtree of pages.
0298             m_favorites = new KAStatsFavoritesModel(this);
0299             Q_EMIT favoritesModelChanged();
0300 
0301             QList<AbstractEntry *> groups;
0302 
0303             int at = 0;
0304             QList<AbstractEntry *> page;
0305             page.reserve(m_pageSize);
0306 
0307             for (AbstractEntry *app : std::as_const(apps)) {
0308                 page.append(app);
0309 
0310                 if (at == (m_pageSize - 1)) {
0311                     at = 0;
0312                     AppsModel *model = new AppsModel(page, false, this);
0313                     groups.append(new GroupEntry(this, QString(), QString(), model));
0314                     page.clear();
0315                 } else {
0316                     ++at;
0317                 }
0318             }
0319 
0320             if (!page.isEmpty()) {
0321                 AppsModel *model = new AppsModel(page, false, this);
0322                 groups.append(new GroupEntry(this, QString(), QString(), model));
0323             }
0324 
0325             groups.prepend(new GroupEntry(this, QString(), QString(), m_favorites));
0326 
0327             allModel = new AppsModel(groups, true, this);
0328         } else { // We turn the apps list into a subtree of apps by starting letter.
0329             QList<AbstractEntry *> groups;
0330             QHash<QString, QList<AbstractEntry *>> m_categoryHash;
0331 
0332             for (const AbstractEntry *groupEntry : std::as_const(m_entryList)) {
0333                 AbstractModel *model = groupEntry->childModel();
0334 
0335                 if (!model)
0336                     continue;
0337 
0338                 for (int i = 0; i < model->count(); ++i) {
0339                     AbstractEntry *appEntry = static_cast<AbstractEntry *>(model->index(i, 0).internalPointer());
0340 
0341                     // App entry's group stores a transliterated first character of the name. Prefer to use that.
0342                     QString name = appEntry->group();
0343                     if (name.isEmpty()) {
0344                         name = appEntry->name();
0345                     }
0346 
0347                     if (name.isEmpty()) {
0348                         continue;
0349                     }
0350 
0351                     const QChar &first = name.at(0).toUpper();
0352                     m_categoryHash[first.isDigit() ? QStringLiteral("0-9") : first].append(appEntry);
0353                 }
0354             }
0355 
0356             QHashIterator<QString, QList<AbstractEntry *>> i(m_categoryHash);
0357 
0358             while (i.hasNext()) {
0359                 i.next();
0360                 AppsModel *model = new AppsModel(i.value(), false, this);
0361                 model->setDescription(i.key());
0362                 groups.append(new GroupEntry(this, i.key(), QString(), model));
0363             }
0364 
0365             allModel = new AppsModel(groups, true, this);
0366         }
0367 
0368         allModel->setDescription(QStringLiteral("KICKER_ALL_MODEL")); // Intentionally no i18n.
0369     }
0370 
0371     int separatorPosition = 0;
0372 
0373     if (allModel) {
0374         m_entryList.prepend(new GroupEntry(this, i18n("All Applications"), QStringLiteral("applications-all"), allModel));
0375         ++separatorPosition;
0376     }
0377 
0378     if (m_showFavoritesPlaceholder) {
0379         // This entry is a placeholder and shouldn't ever be visible
0380         QList<AbstractEntry *> placeholderList;
0381         AppsModel *placeholderModel = new AppsModel(placeholderList, false, this);
0382 
0383         // Favorites group containing a placeholder entry, so it would be considered as a group, not an entry
0384         QList<AbstractEntry *> placeholderEntry;
0385         placeholderEntry.append(new GroupEntry(this, //
0386                                                i18n("This shouldn't be visible! Use KICKER_FAVORITES_MODEL"),
0387                                                QStringLiteral("dialog-warning"),
0388                                                placeholderModel));
0389         AppsModel *favoritesPlaceholderModel = new AppsModel(placeholderEntry, false, this);
0390 
0391         favoritesPlaceholderModel->setDescription(QStringLiteral("KICKER_FAVORITES_MODEL")); // Intentionally no i18n.
0392         m_entryList.prepend(new GroupEntry(this, i18n("Favorites"), QStringLiteral("bookmarks"), favoritesPlaceholderModel));
0393         ++separatorPosition;
0394     }
0395 
0396     if (m_showRecentDocs) {
0397         m_recentDocsModel = new RecentUsageModel(this, RecentUsageModel::OnlyDocs, m_recentOrdering);
0398         m_entryList.prepend(new GroupEntry(this,
0399                                            m_recentOrdering == RecentUsageModel::Recent ? i18n("Recent Files") : i18n("Often Used Files"),
0400                                            m_recentOrdering == RecentUsageModel::Recent ? QStringLiteral("view-history") : QStringLiteral("office-chart-pie"),
0401                                            m_recentDocsModel));
0402         ++separatorPosition;
0403     }
0404 
0405     if (m_showRecentApps) {
0406         m_recentAppsModel = new RecentUsageModel(this, RecentUsageModel::OnlyApps, m_recentOrdering);
0407         m_entryList.prepend(new GroupEntry(this,
0408                                            m_recentOrdering == RecentUsageModel::Recent ? i18n("Recent Applications") : i18n("Often Used Applications"),
0409                                            m_recentOrdering == RecentUsageModel::Recent ? QStringLiteral("view-history") : QStringLiteral("office-chart-pie"),
0410                                            m_recentAppsModel));
0411         ++separatorPosition;
0412     }
0413 
0414     if (m_showSeparators && separatorPosition > 0) {
0415         m_entryList.insert(separatorPosition, new SeparatorEntry(this));
0416         ++m_separatorCount;
0417     }
0418 
0419     m_systemModel = new SystemModel(this);
0420     QObject::connect(m_systemModel, &SystemModel::sessionManagementStateChanged, this, &RootModel::refresh);
0421 
0422     if (m_showPowerSession) {
0423         m_entryList << new GroupEntry(this, i18n("Power / Session"), QStringLiteral("system-log-out"), m_systemModel);
0424     }
0425 
0426     endResetModel();
0427 
0428     m_favorites->refresh();
0429 
0430     Q_EMIT systemFavoritesModelChanged();
0431     Q_EMIT countChanged();
0432     Q_EMIT separatorCountChanged();
0433 
0434     Q_EMIT refreshed();
0435 }