| 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 | |