1/*
2 SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6#include "kcommandbarmodel_p.h"
7#include "kcommandbar.h" // For ActionGroup
8#include "kconfigwidgets_debug.h"
9
10#include <KLocalizedString>
11
12#include <QAction>
13#include <QMenu>
14
15#include <unordered_set>
16
17QString KCommandBarModel::Item::displayName() const
18{
19 const QString group = KLocalizedString::removeAcceleratorMarker(label: groupName);
20 const QString command = KLocalizedString::removeAcceleratorMarker(label: action->text());
21
22 return group + QStringLiteral(": ") + command;
23}
24
25KCommandBarModel::KCommandBarModel(QObject *parent)
26 : QAbstractTableModel(parent)
27{
28 m_clearHistoryAction = new QAction(tr(s: "Clear History"), this);
29 m_clearHistoryAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
30 connect(sender: m_clearHistoryAction, signal: &QAction::triggered, context: this, slot: [this]() {
31 m_lastTriggered.clear();
32 });
33}
34
35void fillRows(QList<KCommandBarModel::Item> &rows, const QString &title, const QList<QAction *> &actions, std::unordered_set<QAction *> &uniqueActions)
36{
37 for (const auto &action : actions) {
38 // We don't want diabled actions
39 if (!action->isEnabled()) {
40 continue;
41 }
42
43 // Is this action actually a menu?
44 if (auto menu = action->menu()) {
45 auto menuActionList = menu->actions();
46
47 // Empty? => Maybe the menu loads action on aboutToShow()?
48 if (menuActionList.isEmpty()) {
49 Q_EMIT menu->aboutToShow();
50 menuActionList = menu->actions();
51 }
52
53 const QString menuTitle = menu->title();
54 fillRows(rows, title: menuTitle, actions: menuActionList, uniqueActions);
55 continue;
56 }
57
58 if (action->text().isEmpty() && !action->isSeparator()) {
59 qCWarning(KCONFIG_WIDGETS_LOG) << "Action" << action << "in group" << title << "has no text";
60 continue;
61 }
62
63 if (uniqueActions.insert(x: action).second) {
64 rows.push_back(t: KCommandBarModel::Item{.groupName: title, .action: action, .score: -1});
65 }
66 }
67}
68
69void KCommandBarModel::refresh(const QList<KCommandBar::ActionGroup> &actionGroups)
70{
71 int totalActions = std::accumulate(first: actionGroups.begin(), last: actionGroups.end(), init: 0, binary_op: [](int a, const KCommandBar::ActionGroup &ag) {
72 return a + ag.actions.count();
73 });
74 ++totalActions; // for m_clearHistoryAction
75
76 QList<Item> temp_rows;
77 std::unordered_set<QAction *> uniqueActions;
78 temp_rows.reserve(asize: totalActions);
79 for (const auto &ag : actionGroups) {
80 const auto &agActions = ag.actions;
81 fillRows(rows&: temp_rows, title: ag.name, actions: agActions, uniqueActions);
82 }
83
84 temp_rows.push_back(t: {.groupName: tr(s: "Command Bar"), .action: m_clearHistoryAction, .score: -1});
85
86 /**
87 * For each action in last triggered actions,
88 * - Find it in the actions
89 * - Use the score variable to set its score
90 *
91 * Items in m_lastTriggered are stored in descending order
92 * by their usage i.e., the first item in the vector is the most
93 * recently invoked action.
94 *
95 * Here we traverse them in reverse order, i.e., from least recent to
96 * most recent and then assign a score to them in a way that most recent
97 * ends up having the highest score. Thus when proxy model does the sorting
98 * later, most recent item will end up on the top
99 */
100 int score = 0;
101 std::for_each(first: m_lastTriggered.crbegin(), last: m_lastTriggered.crend(), f: [&score, &temp_rows](const QString &act) {
102 auto it = std::find_if(first: temp_rows.begin(), last: temp_rows.end(), pred: [act](const KCommandBarModel::Item &i) {
103 return i.action->text() == act;
104 });
105 if (it != temp_rows.end()) {
106 it->score = score++;
107 }
108 });
109
110 beginResetModel();
111 m_rows = std::move(temp_rows);
112 endResetModel();
113}
114
115QVariant KCommandBarModel::data(const QModelIndex &index, int role) const
116{
117 if (!index.isValid()) {
118 return {};
119 }
120
121 const auto &entry = m_rows[index.row()];
122 const int col = index.column();
123
124 switch (role) {
125 case Qt::DisplayRole:
126 if (col == Column_Command) {
127 return entry.displayName();
128 }
129 Q_ASSERT(col == Column_Shortcut);
130 return entry.action->shortcut().toString();
131 case Qt::DecorationRole:
132 if (col == Column_Command) {
133 return entry.action->icon();
134 }
135 break;
136 case Qt::ToolTipRole: {
137 QString toolTip = entry.displayName();
138 if (!entry.action->shortcut().isEmpty()) {
139 toolTip += QLatin1Char('\n');
140 toolTip += entry.action->shortcut().toString();
141 }
142 return toolTip;
143 }
144 case Qt::UserRole: {
145 return QVariant::fromValue(value: entry.action);
146 }
147 case Role::Score:
148 return entry.score;
149 }
150
151 return {};
152}
153
154void KCommandBarModel::actionTriggered(const QString &name)
155{
156 if (m_lastTriggered.size() == 6) {
157 m_lastTriggered.pop_back();
158 }
159 m_lastTriggered.push_front(t: name);
160}
161
162QStringList KCommandBarModel::lastUsedActions() const
163{
164 return m_lastTriggered;
165}
166
167void KCommandBarModel::setLastUsedActions(const QStringList &actionNames)
168{
169 m_lastTriggered = actionNames;
170
171 while (m_lastTriggered.size() > 6) {
172 m_lastTriggered.pop_back();
173 }
174}
175
176#include "moc_kcommandbarmodel_p.cpp"
177

source code of kconfigwidgets/src/kcommandbarmodel_p.cpp