File indexing completed on 2024-04-21 03:53:21

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "kcommandbarmodel_p.h"
0007 #include "kcommandbar.h" // For ActionGroup
0008 #include "kconfigwidgets_debug.h"
0009 
0010 #include <KLocalizedString>
0011 
0012 #include <QAction>
0013 #include <QMenu>
0014 
0015 #include <unordered_set>
0016 
0017 QString KCommandBarModel::Item::displayName() const
0018 {
0019     const QString group = KLocalizedString::removeAcceleratorMarker(groupName);
0020     const QString command = KLocalizedString::removeAcceleratorMarker(action->text());
0021 
0022     return group + QStringLiteral(": ") + command;
0023 }
0024 
0025 KCommandBarModel::KCommandBarModel(QObject *parent)
0026     : QAbstractTableModel(parent)
0027 {
0028     m_clearHistoryAction = new QAction(tr("Clear History"), this);
0029     m_clearHistoryAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
0030     connect(m_clearHistoryAction, &QAction::triggered, this, [this]() {
0031         m_lastTriggered.clear();
0032     });
0033 }
0034 
0035 void fillRows(QList<KCommandBarModel::Item> &rows, const QString &title, const QList<QAction *> &actions, std::unordered_set<QAction *> &uniqueActions)
0036 {
0037     for (const auto &action : actions) {
0038         // We don't want diabled actions
0039         if (!action->isEnabled()) {
0040             continue;
0041         }
0042 
0043         // Is this action actually a menu?
0044         if (auto menu = action->menu()) {
0045             auto menuActionList = menu->actions();
0046 
0047             // Empty? => Maybe the menu loads action on aboutToShow()?
0048             if (menuActionList.isEmpty()) {
0049                 Q_EMIT menu->aboutToShow();
0050                 menuActionList = menu->actions();
0051             }
0052 
0053             const QString menuTitle = menu->title();
0054             fillRows(rows, menuTitle, menuActionList, uniqueActions);
0055             continue;
0056         }
0057 
0058         if (action->text().isEmpty() && !action->isSeparator()) {
0059             qCWarning(KCONFIG_WIDGETS_LOG) << "Action" << action << "in group" << title << "has no text";
0060             continue;
0061         }
0062 
0063         if (uniqueActions.insert(action).second) {
0064             rows.push_back(KCommandBarModel::Item{title, action, -1});
0065         }
0066     }
0067 }
0068 
0069 void KCommandBarModel::refresh(const QList<KCommandBar::ActionGroup> &actionGroups)
0070 {
0071     int totalActions = std::accumulate(actionGroups.begin(), actionGroups.end(), 0, [](int a, const KCommandBar::ActionGroup &ag) {
0072         return a + ag.actions.count();
0073     });
0074     ++totalActions; // for m_clearHistoryAction
0075 
0076     QList<Item> temp_rows;
0077     std::unordered_set<QAction *> uniqueActions;
0078     temp_rows.reserve(totalActions);
0079     for (const auto &ag : actionGroups) {
0080         const auto &agActions = ag.actions;
0081         fillRows(temp_rows, ag.name, agActions, uniqueActions);
0082     }
0083 
0084     temp_rows.push_back({tr("Command Bar"), m_clearHistoryAction, -1});
0085 
0086     /**
0087      * For each action in last triggered actions,
0088      *  - Find it in the actions
0089      *  - Use the score variable to set its score
0090      *
0091      * Items in m_lastTriggered are stored in descending order
0092      * by their usage i.e., the first item in the vector is the most
0093      * recently invoked action.
0094      *
0095      * Here we traverse them in reverse order, i.e., from least recent to
0096      * most recent and then assign a score to them in a way that most recent
0097      * ends up having the highest score. Thus when proxy model does the sorting
0098      * later, most recent item will end up on the top
0099      */
0100     int score = 0;
0101     std::for_each(m_lastTriggered.crbegin(), m_lastTriggered.crend(), [&score, &temp_rows](const QString &act) {
0102         auto it = std::find_if(temp_rows.begin(), temp_rows.end(), [act](const KCommandBarModel::Item &i) {
0103             return i.action->text() == act;
0104         });
0105         if (it != temp_rows.end()) {
0106             it->score = score++;
0107         }
0108     });
0109 
0110     beginResetModel();
0111     m_rows = std::move(temp_rows);
0112     endResetModel();
0113 }
0114 
0115 QVariant KCommandBarModel::data(const QModelIndex &index, int role) const
0116 {
0117     if (!index.isValid()) {
0118         return {};
0119     }
0120 
0121     const auto &entry = m_rows[index.row()];
0122     const int col = index.column();
0123 
0124     switch (role) {
0125     case Qt::DisplayRole:
0126         if (col == Column_Command) {
0127             return entry.displayName();
0128         }
0129         Q_ASSERT(col == Column_Shortcut);
0130         return entry.action->shortcut().toString();
0131     case Qt::DecorationRole:
0132         if (col == Column_Command) {
0133             return entry.action->icon();
0134         }
0135         break;
0136     case Qt::ToolTipRole: {
0137         QString toolTip = entry.displayName();
0138         if (!entry.action->shortcut().isEmpty()) {
0139             toolTip += QLatin1Char('\n');
0140             toolTip += entry.action->shortcut().toString();
0141         }
0142         return toolTip;
0143     }
0144     case Qt::UserRole: {
0145         return QVariant::fromValue(entry.action);
0146     }
0147     case Role::Score:
0148         return entry.score;
0149     }
0150 
0151     return {};
0152 }
0153 
0154 void KCommandBarModel::actionTriggered(const QString &name)
0155 {
0156     if (m_lastTriggered.size() == 6) {
0157         m_lastTriggered.pop_back();
0158     }
0159     m_lastTriggered.push_front(name);
0160 }
0161 
0162 QStringList KCommandBarModel::lastUsedActions() const
0163 {
0164     return m_lastTriggered;
0165 }
0166 
0167 void KCommandBarModel::setLastUsedActions(const QStringList &actionNames)
0168 {
0169     m_lastTriggered = actionNames;
0170 
0171     while (m_lastTriggered.size() > 6) {
0172         m_lastTriggered.pop_back();
0173     }
0174 }
0175 
0176 #include "moc_kcommandbarmodel_p.cpp"