File indexing completed on 2023-10-01 05:54:50
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"