File indexing completed on 2024-04-21 14:54:18

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