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