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 | |
17 | QString 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 | |
25 | KCommandBarModel::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 | |
35 | void 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 = action->menu()) { |
45 | auto = 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 = 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 | |
69 | void 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 | |
115 | QVariant 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 | |
154 | void 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 | |
162 | QStringList KCommandBarModel::lastUsedActions() const |
163 | { |
164 | return m_lastTriggered; |
165 | } |
166 | |
167 | void 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 | |