File indexing completed on 2024-12-01 08:12:19

0001 /*
0002  * SPDX-FileCopyrightText: 2009 Ben Cooksley <bcooksley@kde.org>
0003  * SPDX-FileCopyrightText: 2007 Will Stephenson <wstephenson@kde.org>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "MenuModel.h"
0009 
0010 #include "MenuItem.h"
0011 #include <KCategorizedSortFilterProxyModel>
0012 #include <QIcon>
0013 
0014 class MenuModel::Private
0015 {
0016 public:
0017     Private()
0018     {
0019     }
0020 
0021     MenuItem *rootItem = nullptr;
0022     QList<MenuItem *> exceptions;
0023 };
0024 
0025 MenuModel::MenuModel(MenuItem *menuRoot, QObject *parent)
0026     : QAbstractItemModel(parent)
0027     , d(new Private())
0028 {
0029     d->rootItem = menuRoot;
0030 }
0031 
0032 MenuModel::~MenuModel()
0033 {
0034     d->exceptions.clear();
0035     delete d;
0036 }
0037 
0038 QHash<int, QByteArray> MenuModel::roleNames() const
0039 {
0040     QHash<int, QByteArray> names = QAbstractItemModel::roleNames();
0041     names[DepthRole] = "DepthRole";
0042     names[IsCategoryRole] = "IsCategoryRole";
0043     names[IsKCMRole] = "IsKCMRole";
0044     names[DefaultIndicatorRole] = "showDefaultIndicator";
0045     names[IconNameRole] = "iconName";
0046     return names;
0047 }
0048 
0049 int MenuModel::columnCount(const QModelIndex & /*parent*/) const
0050 {
0051     return 1;
0052 }
0053 
0054 int MenuModel::rowCount(const QModelIndex &parent) const
0055 {
0056     MenuItem *mi;
0057     if (parent.isValid()) {
0058         mi = static_cast<MenuItem *>(parent.internalPointer());
0059     } else {
0060         mi = d->rootItem;
0061     }
0062     return childrenList(mi).count();
0063 }
0064 
0065 QVariant MenuModel::data(const QModelIndex &index, int role) const
0066 {
0067     QVariant theData;
0068     if (!index.isValid()) {
0069         return {};
0070     }
0071 
0072     auto mi = static_cast<MenuItem *>(index.internalPointer());
0073     switch (role) {
0074     case Qt::DisplayRole:
0075         theData.setValue(mi->name());
0076         break;
0077     case Qt::DecorationRole:
0078         theData = QVariant(QIcon::fromTheme(mi->iconName()));
0079         break;
0080     case KCategorizedSortFilterProxyModel::CategorySortRole:
0081         if (mi->parent()) {
0082             theData.setValue(QStringLiteral("%1%2").arg(QString::number(mi->parent()->weight()), 5, QLatin1Char('0')).arg(mi->parent()->name()));
0083         }
0084         break;
0085     case KCategorizedSortFilterProxyModel::CategoryDisplayRole: {
0086         MenuItem *candidate = mi->parent();
0087         // The model has an invisible single root item.
0088         // So to get the "root category" we don't go up all the way
0089         // To the actual root, but to the list of the first childs.
0090         // That's why we check for candidate->parent()->parent()
0091         while (candidate && candidate->parent() && candidate->parent()->parent()) {
0092             candidate = candidate->parent();
0093         }
0094         if (candidate) {
0095             // Children of this special root category don't have an user visible category
0096             if (!candidate->isSystemsettingsRootCategory()) {
0097                 theData.setValue(candidate->name());
0098             }
0099         }
0100         break;
0101     }
0102     case MenuModel::MenuItemRole:
0103         theData.setValue(mi);
0104         break;
0105     case MenuModel::UserFilterRole:
0106         theData.setValue(mi->keywords().join(QString()));
0107         break;
0108     case MenuModel::UserSortRole:
0109         // Category owners are always before everything else, regardless of weight
0110         if (mi->isCategoryOwner()) {
0111             theData.setValue(QStringLiteral("%1").arg(QString::number(mi->weight()), 5, QLatin1Char('0')));
0112         } else {
0113             theData.setValue(QStringLiteral("1%1").arg(QString::number(mi->weight()), 5, QLatin1Char('0')));
0114         }
0115         break;
0116     case MenuModel::DepthRole: {
0117         MenuItem *candidate = mi;
0118         // -1 excludes the invisible root, having main categories at depth 0
0119         int depth = -1;
0120         while (candidate && candidate->parent()) {
0121             candidate = candidate->parent();
0122             ++depth;
0123         }
0124 
0125         MenuItem *parent = mi->parent();
0126         // Items that are in a category with an owner are one level deeper,
0127         // except the owner
0128         if (parent && parent->menu() && parent->isLibrary() && !mi->isCategoryOwner()) {
0129             ++depth;
0130         }
0131         theData.setValue(depth);
0132         break;
0133     }
0134     case MenuModel::IsCategoryRole:
0135         theData.setValue(mi->menu());
0136         break;
0137     case MenuModel::IsKCMRole:
0138         theData.setValue(mi->isLibrary());
0139         break;
0140     case MenuModel::DefaultIndicatorRole:
0141         theData.setValue(mi->showDefaultIndicator());
0142         break;
0143     case MenuModel::IconNameRole:
0144         theData.setValue(mi->iconName());
0145         break;
0146     default:
0147         break;
0148     }
0149     return theData;
0150 }
0151 
0152 Qt::ItemFlags MenuModel::flags(const QModelIndex &index) const
0153 {
0154     if (!index.isValid()) {
0155         return Qt::NoItemFlags;
0156     }
0157 
0158     return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0159 }
0160 
0161 QModelIndex MenuModel::index(int row, int column, const QModelIndex &parent) const
0162 {
0163     if (!hasIndex(row, column, parent)) {
0164         return {};
0165     }
0166 
0167     MenuItem *parentItem;
0168     if (!parent.isValid()) {
0169         parentItem = d->rootItem;
0170     } else {
0171         parentItem = static_cast<MenuItem *>(parent.internalPointer());
0172     }
0173 
0174     MenuItem *childItem = childrenList(parentItem).value(row);
0175     if (childItem) {
0176         return createIndex(row, column, childItem);
0177     } else {
0178         return {};
0179     }
0180 }
0181 
0182 QModelIndex MenuModel::parent(const QModelIndex &index) const
0183 {
0184     auto childItem = static_cast<MenuItem *>(index.internalPointer());
0185     if (!childItem) {
0186         return {};
0187     }
0188 
0189     MenuItem *parent = parentItem(childItem);
0190     MenuItem *grandParent = parentItem(parent);
0191 
0192     int childRow = 0;
0193     if (grandParent) {
0194         childRow = childrenList(grandParent).indexOf(parent);
0195     }
0196 
0197     if (parent == d->rootItem) {
0198         return {};
0199     }
0200     return createIndex(childRow, 0, parent);
0201 }
0202 
0203 QList<MenuItem *> MenuModel::childrenList(MenuItem *parent) const
0204 {
0205     QList<MenuItem *> children = parent->children();
0206     foreach (MenuItem *child, children) {
0207         if (d->exceptions.contains(child)) {
0208             children.removeOne(child);
0209             children.append(child->children());
0210         }
0211     }
0212     return children;
0213 }
0214 
0215 MenuItem *MenuModel::parentItem(MenuItem *child) const
0216 {
0217     MenuItem *parent = child->parent();
0218     if (d->exceptions.contains(parent)) {
0219         parent = parentItem(parent);
0220     }
0221     return parent;
0222 }
0223 
0224 QModelIndex MenuModel::indexForItem(MenuItem *item) const
0225 {
0226     MenuItem *parent = parentItem(item);
0227 
0228     if (!parent) {
0229         return {};
0230     }
0231 
0232     const int row = childrenList(parent).indexOf(item);
0233 
0234     if (row < 0) {
0235         return {};
0236     }
0237 
0238     return createIndex(row, 0, item);
0239 }
0240 
0241 MenuItem *MenuModel::rootItem() const
0242 {
0243     return d->rootItem;
0244 }
0245 
0246 void MenuModel::addException(MenuItem *exception)
0247 {
0248     if (exception == d->rootItem) {
0249         return;
0250     }
0251     d->exceptions.append(exception);
0252 }
0253 
0254 void MenuModel::removeException(MenuItem *exception)
0255 {
0256     d->exceptions.removeAll(exception);
0257 }
0258 
0259 #include "moc_MenuModel.cpp"