/* * pkgmodel.cc - Implementation of the custom package selection model classes * horizon-qt5, the Qt 5 user interface for * Project Horizon * * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. * This code is licensed under the AGPL 3.0 license, as noted in the * LICENSE-code file in the root directory of this repository. * * SPDX-License-Identifier: AGPL-3.0-only */ #include "pkgmodel.hh" #include #include #include PkgItem::PkgItem(PkgItem *parent, QString friendly, QString internal, QString description, QString icon) : _parent(parent), _friendly(friendly), _internal(internal), _desc(description) { if(icon.isEmpty()) { _icon = internal; } else { _icon = icon; } } PkgItem::~PkgItem() { qDeleteAll(_children); } void PkgItem::addChild(PkgItem *pkg) { _children.append(pkg); } PkgItem *PkgItem::child(int row) { if(row < 0 || row > _children.size()) { return nullptr; } return _children.at(row); } int PkgItem::childCount() const { return _children.size(); } QVariant PkgItem::data(int column, int role) { if(column != 0) { return QVariant(); } switch(role) { case Qt::DisplayRole: return QVariant(_friendly); case Qt::DecorationRole: return QVariant(QIcon::fromTheme(_icon)); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(_desc); case Qt::UserRole: return QVariant(_internal); } return QVariant(); } QStringList PkgItem::internalNames() const { QStringList list; if(!_internal.isEmpty()) { list.append(_internal); } for(auto &child : _children) { list.append(child->internalNames()); } return list; } PkgItem *PkgItem::parent() { return _parent; } int PkgItem::row() const { if(_parent == nullptr) { return 0; } return _parent->_children.indexOf(const_cast(this)); } PkgItemModel::PkgItemModel(QStringList *pkgList, QObject *parent) : QAbstractItemModel(parent), _packages(pkgList) { _root = new PkgItem(nullptr, "Root Item", "root", "The root of the tree. Never seen."); loadPackages(); } PkgItemModel::~PkgItemModel() { delete _root; } int PkgItemModel::columnCount(const QModelIndex &) const { return 1; } QVariant PkgItemModel::data(const QModelIndex &index, int role) const { if(!index.isValid()) { return QVariant(); } PkgItem *item = static_cast(index.internalPointer()); if(role == Qt::CheckStateRole && index.column() == 0) { QStringList pkgs = item->internalNames(); if(_packages && !pkgs.isEmpty()) { bool all = true, any = false; for(auto &pkg : pkgs) { if(_packages->contains(pkg)) { any = true; } else { all = false; } } if(all) return Qt::Checked; if(any) return Qt::PartiallyChecked; } return Qt::Unchecked; } return item->data(index.column(), role); } Qt::ItemFlags PkgItemModel::flags(const QModelIndex &index) const { if(!index.isValid()) { return Qt::NoItemFlags; } Qt::ItemFlags flags = Qt::ItemIsEnabled; if(index.column() == 0) { flags |= Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsAutoTristate; } return flags; } QVariant PkgItemModel::headerData(int, Qt::Orientation, int) const { return QVariant(); } QModelIndex PkgItemModel::index(int row, int column, const QModelIndex &parent) const { if(!hasIndex(row, column, parent)) { return QModelIndex(); } PkgItem *p; if(parent.isValid()) { p = static_cast(parent.internalPointer()); } else { p = _root; } PkgItem *item = p->child(row); if(item) { return createIndex(row, column, item); } else { return QModelIndex(); } } QModelIndex PkgItemModel::parent(const QModelIndex &child) const { if(!child.isValid()) { return QModelIndex(); } PkgItem *item = static_cast(child.internalPointer()); PkgItem *parent = item->parent(); if(parent == _root) { return QModelIndex(); } else { return createIndex(parent->row(), 0, parent); } } int PkgItemModel::rowCount(const QModelIndex &parent) const { if(parent.column() > 0) { return 0; } if(parent.isValid()) { PkgItem *item = static_cast(parent.internalPointer()); return item->childCount(); } else { return _root->childCount(); } } bool PkgItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(!index.isValid() || role != Qt::CheckStateRole) { return false; } PkgItem *item = static_cast(index.internalPointer()); if(item) { QStringList pkgs = item->internalNames(); if(value.toBool()) { _packages->append(pkgs); } else { for(auto &pkg : pkgs) { _packages->removeAll(pkg); } } emit dataChanged(index, index, {Qt::CheckStateRole}); if(item->childCount() > 0) { emit dataChanged(createIndex(0, 0, item), createIndex(item->childCount(), 0, item), {Qt::CheckStateRole}); } PkgItem *parent = item; while(parent->parent() != _root) { parent = parent->parent(); QModelIndex pidx = createIndex(parent->row(), 0, parent); emit dataChanged(pidx, pidx, {Qt::CheckStateRole}); } } return true; } void PkgItemModel::setPackageList(QStringList *pkgList) { _packages = pkgList; } void PkgItemModel::loadPackages() { QFile myList(":/packages.txt"); if(!myList.open(QFile::ReadOnly)) { QMessageBox::critical(nullptr, tr("Could Not Load Software List"), tr("An issue occurred loading the available software list.")); return; } QByteArray raw_pkgs = myList.readAll(); QList pkgs = raw_pkgs.split('\n'); int lastIndent = 0; PkgItem *temp = new PkgItem(_root, "Placeholder", "Placeholder", "Used for loading only"); PkgItem *last = temp; for(auto &pkg : pkgs) { int indent = 0; if(pkg.isEmpty()) continue; while(pkg.size() > 1 && pkg.at(0) == ' ') { indent++; pkg.remove(0, 1); } /* minimum valid size: A\tb\tC */ if(pkg.size() < 6) continue; PkgItem *myParent; if(indent == lastIndent) { /* We're siblings of the last item. */ myParent = last->parent(); } else if(indent < lastIndent) { /* Traverse up until we're equal */ while(lastIndent-- > indent) { last = last->parent(); } myParent = last->parent(); } else /*(indent > lastIndent)*/ { /* We're children of the last item. */ myParent = last; } QList fields = pkg.split('\t'); /* at least friendly, internal, desc required */ if(fields.size() < 3) continue; last = new PkgItem(myParent, fields[0], fields[1], fields[2], (fields.size() == 4 ? fields[3] : fields[1])); myParent->addChild(last); lastIndent = indent; } delete temp; }