File indexing completed on 2022-09-27 16:24:29

0001 /***************************************************************************
0002  *   Copyright (C) 2010-2011 by Daniel Nicoletti                           *
0003  *   dantti12@gmail.com                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; see the file COPYING. If not, write to       *
0017  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0018  *   Boston, MA 02110-1301, USA.                                           *
0019  ***************************************************************************/
0020 
0021 #include "CategoryModel.h"
0022 
0023 #include <QMetaEnum>
0024 #include <QFile>
0025 #include <QTimer>
0026 #include <QStringBuilder>
0027 
0028 #include <PkStrings.h>
0029 #include <PkIcons.h>
0030 
0031 #include <KCategorizedSortFilterProxyModel>
0032 #include <KLocalizedString>
0033 #include <KServiceGroup>
0034 #include <KDesktopFile>
0035 #include <KConfigGroup>
0036 
0037 #include <QLoggingCategory>
0038 
0039 #include <Daemon>
0040 
0041 #include <config.h>
0042 
0043 Q_DECLARE_LOGGING_CATEGORY(APPER)
0044 
0045 using namespace PackageKit;
0046 
0047 CategoryModel::CategoryModel(QObject *parent) :
0048     QStandardItemModel(parent)
0049 {
0050     QStandardItem *item;
0051     item = new QStandardItem(i18n("Installed Software"));
0052     item->setDragEnabled(false);
0053     item->setData(Transaction::RoleGetPackages, SearchRole);
0054     item->setData(i18n("Lists"), KCategorizedSortFilterProxyModel::CategoryDisplayRole);
0055     item->setData(0, KCategorizedSortFilterProxyModel::CategorySortRole);
0056     item->setIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply")));
0057     appendRow(item);
0058 
0059     item = new QStandardItem(i18n("Updates"));
0060     item->setDragEnabled(false);
0061     item->setData(Transaction::RoleGetUpdates, SearchRole);
0062     item->setData(i18n("Lists"), KCategorizedSortFilterProxyModel::CategoryDisplayRole);
0063     item->setData(0, KCategorizedSortFilterProxyModel::CategorySortRole);
0064     item->setIcon(QIcon::fromTheme(QLatin1String("system-software-update")));
0065     appendRow(item);
0066 
0067 #ifdef HAVE_APPSTREAM
0068     // Get the groups
0069 #ifdef AS_CATEGORIES_PATH
0070     fillWithServiceGroups();
0071 #else
0072     fillWithStandardGroups();
0073 #endif // AS_CATEGORIES_PATH
0074 //         category("",
0075 //                              "servers",
0076 //                              "Servers",
0077 //                              "const QString &summary",
0078 //                              "computer");
0079 //     category("servers",
0080 //                              "@coomputer",
0081 //                              "Lighttpd",
0082 //                              "const QString &summary",
0083 //                              "emblem-new");
0084 //     category("servers",
0085 //                              "@coomputer2",
0086 //                              "Apache",
0087 //                              "const QString &summary",
0088 //                              "dialog-cancel");
0089 #else
0090 
0091 #endif //HAVE_APPSTREAM
0092     QTimer::singleShot(0, this, SIGNAL(finished()));
0093 }
0094 
0095 CategoryModel::~CategoryModel()
0096 {
0097 }
0098 
0099 void CategoryModel::setRoles(Transaction::Roles roles)
0100 {
0101     m_roles = roles;
0102     removeRows(2, rowCount() - 2);
0103 
0104     QDBusPendingReply<QList<QDBusObjectPath> > transactions = Daemon::getTransactionList();
0105     transactions.waitForFinished();
0106     if (m_roles & Transaction::RoleGetCategories
0107         && transactions.value().isEmpty()) {
0108         Transaction *trans = Daemon::getCategories();
0109         connect(trans, &Transaction::category, this, &CategoryModel::category);
0110         connect(trans, &Transaction::finished, this, &CategoryModel::finished);
0111     } else {
0112         fillWithStandardGroups();
0113     }
0114 }
0115 
0116 QModelIndex CategoryModel::index(int row, int column, const QModelIndex &parent) const
0117 {
0118     if (parent.isValid()) {
0119         return QStandardItemModel::index(row, column, parent);
0120     }
0121     return QStandardItemModel::index(row, column, m_rootIndex);
0122 }
0123 
0124 int CategoryModel::rowCount(const QModelIndex &parent) const
0125 {
0126     if (parent.isValid()) {
0127         return QStandardItemModel::rowCount(parent);
0128     }
0129     return QStandardItemModel::rowCount(m_rootIndex);
0130 }
0131 
0132 void CategoryModel::setRootIndex(const QModelIndex &index)
0133 {
0134     beginResetModel();
0135     m_rootIndex = index;
0136     endResetModel();
0137     emit finished();
0138 }
0139 
0140 bool CategoryModel::setParentIndex()
0141 {
0142     if (m_rootIndex.isValid()) {
0143         setRootIndex(m_rootIndex.parent());
0144         // Return the future parent so that Back button can be disabled
0145         return m_rootIndex.parent().isValid();
0146     }
0147     // if there is no higher level return false
0148     return false;
0149 }
0150 
0151 bool CategoryModel::hasParent() const
0152 {
0153     return m_rootIndex.isValid();
0154 }
0155 
0156 void CategoryModel::category(const QString &parentId,
0157                              const QString &categoryId,
0158                              const QString &name,
0159                              const QString &summary,
0160                              const QString &icon)
0161 {
0162     qCDebug(APPER) << parentId << categoryId << name << summary << icon;
0163     auto item = new QStandardItem(name);
0164     item->setDragEnabled(false);
0165     item->setData(Transaction::RoleSearchGroup, SearchRole);
0166     item->setData(categoryId, GroupRole);
0167     item->setData(i18n("Categories"), KCategorizedSortFilterProxyModel::CategoryDisplayRole);
0168     item->setData(2, KCategorizedSortFilterProxyModel::CategorySortRole);
0169     item->setToolTip(summary);
0170     item->setIcon(QIcon(QLatin1String("/usr/share/pixmaps/comps/") + icon + QLatin1String(".png")));
0171 
0172     if (parentId.isEmpty()) {
0173         appendRow(item);
0174     } else {
0175         QStandardItem *parent = findCategory(parentId);
0176         if (parent) {
0177             item->setData(parent->text(),
0178                           KCategorizedSortFilterProxyModel::CategoryDisplayRole);
0179             item->setData(2, KCategorizedSortFilterProxyModel::CategorySortRole);
0180             parent->appendRow(item);
0181         } else {
0182             appendRow(item);
0183         }
0184     }
0185 
0186     // This is a MUST since the spacing needs to be fixed
0187     emit finished();
0188 }
0189 
0190 QStandardItem* CategoryModel::findCategory(const QString &categoryId, const QModelIndex &parent) const
0191 {
0192     QStandardItem *ret = nullptr;
0193     for (int i = 0; i < rowCount(parent); i++) {
0194         QModelIndex group = index(i, 0, parent);
0195         if (group.data(SearchRole).toUInt() == Transaction::RoleSearchGroup
0196          && group.data(GroupRole).toString() == categoryId) {
0197             ret = itemFromIndex(group);
0198         } else if (hasChildren(group)) {
0199             ret = findCategory(categoryId, group);
0200         }
0201 
0202         if (ret) {
0203             break;
0204         }
0205     }
0206     return ret;
0207 }
0208 
0209 void CategoryModel::fillWithStandardGroups()
0210 {
0211     // Get the groups
0212     m_groups = Daemon::global()->groups();
0213     qCDebug(APPER);
0214     QStandardItem *item;
0215     for (int i = 1; i < 64; ++i) {
0216         if (m_groups & i) {
0217             Transaction::Group group;
0218             group = static_cast<Transaction::Group>(i);
0219             if (group != Transaction::GroupUnknown) {
0220                 item = new QStandardItem(PkStrings::groups(group));
0221                 item->setDragEnabled(false);
0222                 item->setData(Transaction::RoleSearchGroup, SearchRole);
0223                 item->setData(group, GroupRole);
0224                 item->setData(i18n("Groups"), KCategorizedSortFilterProxyModel::CategoryDisplayRole);
0225                 item->setData(1, KCategorizedSortFilterProxyModel::CategorySortRole);
0226                 item->setIcon(PkIcons::groupsIcon(group));
0227                 if (!(m_roles & Transaction::RoleSearchGroup)) {
0228                     item->setSelectable(false);
0229                 }
0230                 appendRow(item);
0231             }
0232         }
0233     }
0234 
0235     emit finished();
0236 }
0237 
0238 void CategoryModel::fillWithServiceGroups()
0239 {
0240 #ifdef AS_CATEGORIES_PATH
0241     KLocale::global()->insertCatalog("gnome-menus");
0242     QFile file(QString(AS_CATEGORIES_PATH) + "/categories.xml");
0243      if (!file.open(QIODevice::ReadOnly)) {
0244          qCDebug(APPER) << "Failed to open file";
0245          fillWithStandardGroups();
0246          return;
0247     }
0248     QXmlStreamReader xml(&file);
0249 
0250     while(xml.readNextStartElement() && !xml.hasError()) {
0251         // Read next element.
0252         if(xml.tokenType() == QXmlStreamReader::StartDocument) {
0253 //             qCDebug(APPER) << "StartDocument";
0254             continue;
0255         }
0256 
0257         if(xml.name() == "Menu") {
0258             parseMenu(xml, QString());
0259         }
0260     }
0261 #endif //AS_CATEGORIES_PATH
0262 }
0263 
0264 void CategoryModel::parseMenu(QXmlStreamReader &xml, const QString &parentIcon, QStandardItem *parent)
0265 {
0266     QString icon = parentIcon;
0267     QStandardItem *item = nullptr;
0268     while(!xml.atEnd() &&
0269           !(xml.tokenType() == QXmlStreamReader::EndElement &&
0270             xml.name() == QLatin1String("Menu"))) {
0271 
0272         if(xml.tokenType() == QXmlStreamReader::StartElement) {
0273             if (xml.name() == QLatin1String("Menu")) {
0274                 xml.readNext();
0275                 parseMenu(xml, icon, item);
0276             } else if (xml.name() == QLatin1String("Name")) {
0277                 QString name = xml.readElementText();
0278                 if (!item) {
0279                     item = new QStandardItem(i18n(name.toUtf8().data()));
0280                     item->setDragEnabled(false);
0281                 } else if (item->text().isEmpty()) {
0282                     item->setText(i18n(name.toUtf8().data()));
0283                 }
0284             } else if (xml.name() == QLatin1String("Icon")) {
0285                 if (!item) {
0286                     item = new QStandardItem;
0287                     item->setDragEnabled(false);
0288                 }
0289                 // only sets the icon if it wasn't set,
0290                 // the .directory might have a better one
0291                 QString _icon;
0292                 _icon = xml.readElementText();
0293                 if (item->icon().isNull()) {
0294                     item->setIcon(PkIcons::getIcon(_icon, icon));
0295                     icon = _icon;
0296                 }
0297             } else if (xml.name() == QLatin1String("Categories")) {
0298                 QList<CategoryMatcher> categories;
0299                 categories = parseCategories(xml);
0300                 if (!categories.isEmpty()) {
0301                     if (!item) {
0302                         item = new QStandardItem;
0303                         item->setDragEnabled(false);
0304                     }
0305 
0306                     // If we only have one category inside get the first item
0307                     if (categories.size() == 1) {
0308                         item->setData(qVariantFromValue(categories.first()), CategoryRole);
0309                     } else {
0310                         CategoryMatcher parser(CategoryMatcher::And);
0311                         parser.setChild(categories);
0312                         item->setData(qVariantFromValue(parser), CategoryRole);
0313                     }
0314                     item->setData(Transaction::RoleResolve, SearchRole);
0315                 }
0316             } else if (xml.name() == QLatin1String("Directory")) {
0317                 if (!item) {
0318                     item = new QStandardItem;
0319                     item->setDragEnabled(false);
0320                 }
0321                 QString directory = xml.readElementText();
0322 
0323                 const KDesktopFile desktopFile(directory);
0324                 const KConfigGroup config = desktopFile.desktopGroup();
0325                 QString _icon = config.readEntry("Icon");
0326                 QString _name = config.readEntry("Name");
0327                 if (!_icon.isEmpty()) {
0328                     item->setIcon(PkIcons::getIcon(_icon, icon));
0329                     icon = _icon;
0330                 }
0331                 if (!_name.isEmpty()) {
0332                     item->setText(_name);
0333                 }
0334             } else if (xml.name() == QLatin1String("PkGroups")) {
0335                 if (!item) {
0336                     item = new QStandardItem;
0337                     item->setDragEnabled(false);
0338                 }
0339                 QString group = xml.readElementText();
0340                 Transaction::Group groupEnum;
0341                 int groupInt = Daemon::enumFromString<Transaction>(group, "Group");
0342                 groupEnum = static_cast<Transaction::Group>(groupInt);
0343                 if (groupEnum != Transaction::GroupUnknown && m_groups & groupEnum) {
0344                     item->setData(Transaction::RoleSearchGroup, SearchRole);
0345                     item->setData(groupEnum, GroupRole);
0346                 }
0347             }
0348         }
0349 
0350         // next...
0351         xml.readNext();
0352     }
0353 
0354     if (item &&
0355         (!item->data(GroupRole).isNull() || !item->data(CategoryRole).isNull())) {
0356         if (item->data(CategoryRole).isNull()) {
0357             // Set the group name to get it translated
0358             Transaction::Group group;
0359             group = item->data(GroupRole).value<Transaction::Group>();
0360             item->setText(PkStrings::groups(group));
0361         }
0362         item->setData(i18n("Categories"), KCategorizedSortFilterProxyModel::CategoryDisplayRole);
0363         item->setData(1, KCategorizedSortFilterProxyModel::CategorySortRole);
0364         if (parent) {
0365             parent->appendRow(item);
0366         } else {
0367             appendRow(item);
0368         }
0369     }
0370 }
0371 
0372 QList<CategoryMatcher> CategoryModel::parseCategories(QXmlStreamReader &xml)
0373 {
0374     QString token = xml.name().toString();
0375 
0376     QList<CategoryMatcher> ret;
0377     while(!xml.atEnd() && !(xml.readNext() == QXmlStreamReader::EndElement && xml.name() == token)) {
0378         if(xml.tokenType() == QXmlStreamReader::StartElement) {
0379             // Where the categories where AND
0380             if (xml.name() == QLatin1String("And")) {
0381                 // We are going to read the next element to save the token name
0382                 QList<CategoryMatcher> parsers;
0383                 parsers = parseCategories(xml);
0384                 if (!parsers.isEmpty()) {
0385                     CategoryMatcher opAND(CategoryMatcher::And);
0386                     opAND.setChild(parsers);
0387                     ret << opAND;
0388                 }
0389             } else if (xml.name() == QLatin1String("Or")) {
0390                 // Where the categories where OR
0391                 QList<CategoryMatcher> parsers;
0392                 parsers = parseCategories(xml);
0393                 if (!parsers.isEmpty()) {
0394                     CategoryMatcher opOR(CategoryMatcher::Or);
0395                     opOR.setChild(parsers);
0396                     ret << opOR;
0397                 }
0398             } else if (xml.name() == QLatin1String("Not")) {
0399                 // USED to negate the categories inside it
0400                 QList<CategoryMatcher> parsers;
0401                 parsers = parseCategories(xml);
0402                 if (!parsers.isEmpty()) {
0403                     CategoryMatcher opNot(CategoryMatcher::Not);
0404                     opNot.setChild(parsers);
0405                     ret << opNot;
0406                 }
0407             } else if (xml.name() == QLatin1String("Category")) {
0408                 // Found the real category, if the join was not means
0409                 // that applications in this category should NOT be displayed
0410                 QString name = xml.readElementText();
0411                 if (!name.isEmpty()){
0412                     ret << CategoryMatcher(CategoryMatcher::Term, name);
0413                 }
0414             }
0415         }
0416     }
0417 
0418     return ret;
0419 }
0420 
0421 #include "moc_CategoryModel.cpp"