1 /*
2 * SPDX-FileCopyrightText: 2009 Ben Cooksley <bcooksley@kde.org>
3 * SPDX-FileCopyrightText: 2007 Will Stephenson <wstephenson@kde.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "MenuModel.h"
9
10 #include "MenuItem.h"
11 #include <KCModuleInfo>
12 #include <KCategorizedSortFilterProxyModel>
13 #include <KPluginInfo>
14 #include <QIcon>
15
16 class MenuModel::Private
17 {
18 public:
Private()19 Private()
20 {
21 }
22
23 MenuItem *rootItem = nullptr;
24 QList<MenuItem *> exceptions;
25 };
26
MenuModel(MenuItem * menuRoot,QObject * parent)27 MenuModel::MenuModel(MenuItem *menuRoot, QObject *parent)
28 : QAbstractItemModel(parent)
29 , d(new Private())
30 {
31 d->rootItem = menuRoot;
32 }
33
~MenuModel()34 MenuModel::~MenuModel()
35 {
36 d->exceptions.clear();
37 delete d;
38 }
39
roleNames() const40 QHash<int, QByteArray> MenuModel::roleNames() const
41 {
42 QHash<int, QByteArray> names = QAbstractItemModel::roleNames();
43 names[DepthRole] = "DepthRole";
44 names[IsCategoryRole] = "IsCategoryRole";
45 names[IsKCMRole] = "IsKCMRole";
46 names[DefaultIndicatorRole] = "showDefaultIndicator";
47 return names;
48 }
49
columnCount(const QModelIndex & parent) const50 int MenuModel::columnCount(const QModelIndex &parent) const
51 {
52 Q_UNUSED(parent);
53 return 1;
54 }
55
rowCount(const QModelIndex & parent) const56 int MenuModel::rowCount(const QModelIndex &parent) const
57 {
58 MenuItem *mi;
59 if (parent.isValid()) {
60 mi = static_cast<MenuItem *>(parent.internalPointer());
61 } else {
62 mi = d->rootItem;
63 }
64 return childrenList(mi).count();
65 }
66
data(const QModelIndex & index,int role) const67 QVariant MenuModel::data(const QModelIndex &index, int role) const
68 {
69 MenuItem *mi = nullptr;
70 QVariant theData;
71 if (!index.isValid()) {
72 return QVariant();
73 }
74
75 mi = static_cast<MenuItem *>(index.internalPointer());
76 switch (role) {
77 case Qt::DisplayRole:
78 theData.setValue(mi->name());
79 break;
80 case Qt::ToolTipRole:
81 theData.setValue(mi->comment());
82 break;
83 case Qt::DecorationRole:
84 theData = QVariant(QIcon::fromTheme(mi->iconName()));
85 break;
86 case KCategorizedSortFilterProxyModel::CategorySortRole:
87 if (mi->parent()) {
88 theData.setValue(QStringLiteral("%1%2").arg(QString::number(mi->parent()->weight()), 5, QLatin1Char('0')).arg(mi->parent()->name()));
89 }
90 break;
91 case KCategorizedSortFilterProxyModel::CategoryDisplayRole: {
92 MenuItem *candidate = mi->parent();
93 // The model has an invisible single root item.
94 // So to get the "root category" we don't go up all the way
95 // To the actual root, but to the list of the first childs.
96 // That's why we check for candidate->parent()->parent()
97 while (candidate && candidate->parent() && candidate->parent()->parent()) {
98 candidate = candidate->parent();
99 }
100 if (candidate) {
101 // Children of this special root category don't have an user visible category
102 if (!candidate->isSystemsettingsRootCategory()) {
103 theData.setValue(candidate->name());
104 }
105 }
106 break;
107 }
108 case MenuModel::MenuItemRole:
109 theData.setValue(mi);
110 break;
111 case MenuModel::UserFilterRole:
112 theData.setValue(mi->keywords().join(QString()));
113 break;
114 case MenuModel::UserSortRole:
115 //Category owners are always before everything else, regardless of weight
116 if (mi->isCategoryOwner()) {
117 theData.setValue(QStringLiteral("%1").arg(QString::number(mi->weight()), 5, QLatin1Char('0')));
118 } else {
119 theData.setValue(QStringLiteral("1%1").arg(QString::number(mi->weight()), 5, QLatin1Char('0')));
120 }
121 break;
122 case MenuModel::DepthRole: {
123 MenuItem *candidate = mi;
124 // -1 excludes the invisible root, having main categories at depth 0
125 int depth = -1;
126 while (candidate && candidate->parent()) {
127 candidate = candidate->parent();
128 ++depth;
129 }
130
131 MenuItem *parent = mi->parent();
132 // Items that are in a category with an owner are one level deeper,
133 // except the owner
134 if (parent && parent->menu() && (parent->item().isValid() && !parent->item().service()->library().isEmpty()) && !mi->isCategoryOwner()) {
135 ++depth;
136 }
137 theData.setValue(depth);
138 break;
139 }
140 case MenuModel::IsCategoryRole:
141 theData.setValue(mi->menu());
142 break;
143 case MenuModel::IsKCMRole:
144 theData.setValue(!mi->item().library().isEmpty());
145 break;
146 case MenuModel::DefaultIndicatorRole:
147 theData.setValue(mi->showDefaultIndicator());
148 break;
149 default:
150 break;
151 }
152 return theData;
153 }
154
flags(const QModelIndex & index) const155 Qt::ItemFlags MenuModel::flags(const QModelIndex &index) const
156 {
157 if (!index.isValid()) {
158 return Qt::NoItemFlags;
159 }
160
161 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
162 }
163
index(int row,int column,const QModelIndex & parent) const164 QModelIndex MenuModel::index(int row, int column, const QModelIndex &parent) const
165 {
166 if (!hasIndex(row, column, parent)) {
167 return QModelIndex();
168 }
169
170 MenuItem *parentItem;
171 if (!parent.isValid()) {
172 parentItem = d->rootItem;
173 } else {
174 parentItem = static_cast<MenuItem *>(parent.internalPointer());
175 }
176
177 MenuItem *childItem = childrenList(parentItem).value(row);
178 if (childItem) {
179 return createIndex(row, column, childItem);
180 } else {
181 return QModelIndex();
182 }
183 }
184
parent(const QModelIndex & index) const185 QModelIndex MenuModel::parent(const QModelIndex &index) const
186 {
187 MenuItem *childItem = static_cast<MenuItem *>(index.internalPointer());
188 if (!childItem) {
189 return QModelIndex();
190 }
191
192 MenuItem *parent = parentItem(childItem);
193 MenuItem *grandParent = parentItem(parent);
194
195 int childRow = 0;
196 if (grandParent) {
197 childRow = childrenList(grandParent).indexOf(parent);
198 }
199
200 if (parent == d->rootItem) {
201 return QModelIndex();
202 }
203 return createIndex(childRow, 0, parent);
204 }
205
childrenList(MenuItem * parent) const206 QList<MenuItem *> MenuModel::childrenList(MenuItem *parent) const
207 {
208 QList<MenuItem *> children = parent->children();
209 foreach (MenuItem *child, children) {
210 if (d->exceptions.contains(child)) {
211 children.removeOne(child);
212 children.append(child->children());
213 }
214 }
215 return children;
216 }
217
parentItem(MenuItem * child) const218 MenuItem *MenuModel::parentItem(MenuItem *child) const
219 {
220 MenuItem *parent = child->parent();
221 if (d->exceptions.contains(parent)) {
222 parent = parentItem(parent);
223 }
224 return parent;
225 }
226
indexForItem(MenuItem * item) const227 QModelIndex MenuModel::indexForItem(MenuItem *item) const
228 {
229 MenuItem *parent = parentItem(item);
230
231 if (!parent) {
232 return QModelIndex();
233 }
234
235 const int row = childrenList(parent).indexOf(item);
236
237 if (row < 0) {
238 return QModelIndex();
239 }
240
241 return createIndex(row, 0, item);
242 }
243
rootItem() const244 MenuItem *MenuModel::rootItem() const
245 {
246 return d->rootItem;
247 }
248
addException(MenuItem * exception)249 void MenuModel::addException(MenuItem *exception)
250 {
251 if (exception == d->rootItem) {
252 return;
253 }
254 d->exceptions.append(exception);
255 }
256
removeException(MenuItem * exception)257 void MenuModel::removeException(MenuItem *exception)
258 {
259 d->exceptions.removeAll(exception);
260 }
261