File indexing completed on 2024-05-12 05:09:51

0001 /***************************************************************************
0002     Copyright (C) 2008-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "entrygroupmodel.h"
0026 #include "models.h"
0027 #include "../entrygroup.h"
0028 #include "../collectionfactory.h"
0029 #include "../tellico_debug.h"
0030 
0031 #include <KLocalizedString>
0032 #include <QIcon>
0033 
0034 using Tellico::EntryGroupModel;
0035 
0036 class EntryGroupModel::Node {
0037 public:
0038   Node(Node* parent_) : m_parent(parent_), m_row(-1) { }
0039   ~Node() { qDeleteAll(m_children); }
0040 
0041   Node* parent() const { return m_parent; }
0042   Node* child(int row) const { return row < m_children.count() ? m_children.at(row) : nullptr; }
0043   int row() const { return m_row; }
0044   int childCount() const { return m_children.count(); };
0045 
0046   void addChild(Node* child) {
0047     child->m_row = m_children.count();
0048     m_children.append(child);
0049   }
0050   void removeChild(int i) {
0051     delete m_children.takeAt(i);
0052     // all subsequent children move up a row
0053     for(int j = i; j < m_children.count(); ++j) { --m_children.at(j)->m_row; }
0054   }
0055   void removeAll() {
0056     qDeleteAll(m_children);
0057     m_children.clear();
0058   }
0059 
0060 private:
0061   Node* m_parent;
0062   QList<Node*> m_children;
0063   int m_row;
0064 };
0065 
0066 EntryGroupModel::EntryGroupModel(QObject* parent) : QAbstractItemModel(parent), m_rootNode(new Node(nullptr)) {
0067   m_groupHeader = i18nc("Group Name Header", "Group");
0068 }
0069 
0070 EntryGroupModel::~EntryGroupModel() {
0071   delete m_rootNode;
0072   m_rootNode = nullptr;
0073 }
0074 
0075 int EntryGroupModel::rowCount(const QModelIndex& index_) const {
0076   if(!index_.isValid()) {
0077     return m_rootNode->childCount();
0078   }
0079 
0080   Node* node = static_cast<Node*>(index_.internalPointer());
0081   Q_ASSERT(node);
0082   return node->childCount();
0083 }
0084 
0085 int EntryGroupModel::columnCount(const QModelIndex&) const {
0086   return 1;
0087 }
0088 
0089 QVariant EntryGroupModel::headerData(int section_, Qt::Orientation orientation_, int role_) const {
0090   if(section_ < 0 || section_ >= columnCount() || orientation_ != Qt::Horizontal) {
0091     return QVariant();
0092   }
0093   if(role_ == Qt::DisplayRole) {
0094     return m_groupHeader;
0095   }
0096   return QVariant();
0097 }
0098 
0099 bool EntryGroupModel::setHeaderData(int section_, Qt::Orientation orientation_,
0100                                     const QVariant& value_, int role_) {
0101   if(section_ < 0 || section_ >= columnCount() || orientation_ != Qt::Horizontal || role_ != Qt::EditRole) {
0102     return false;
0103   }
0104   m_groupHeader = value_.toString();
0105   emit headerDataChanged(orientation_, section_, section_);
0106   return true;
0107 }
0108 
0109 QVariant EntryGroupModel::data(const QModelIndex& index_, int role_) const {
0110   if(!index_.isValid()) {
0111     return QVariant();
0112   }
0113 
0114   switch(role_) {
0115     case Qt::DisplayRole:
0116       if(hasValidParent(index_)) {
0117         // it probably points to an entry
0118         Tellico::Data::EntryPtr e = entry(index_);
0119         if(e) {
0120           return e->title();
0121         }
0122       } else {
0123         // it probably points to a group
0124         Tellico::Data::EntryGroup* g = group(index_);
0125         if(g) {
0126           return g->groupName();
0127         }
0128       }
0129       return QString(); // DisplayRole should get an empty string, supposedly...
0130     case Qt::DecorationRole:
0131       if(hasValidParent(index_)) {
0132         // assume all the entries have the same icon
0133         // so no need to lookup the entry(index_), just use first one we find
0134         foreach(Data::EntryGroup* group, m_groups) {
0135           if(!group->isEmpty()) {
0136             return QIcon(QLatin1String(":/icons/") + CollectionFactory::typeName(group->first()->collection()));
0137           }
0138         }
0139       }
0140       // for groups, check the icon name list
0141       return QIcon::fromTheme(m_groupIconNames.at(index_.row()));
0142     case RowCountRole:
0143       return rowCount(index_);
0144     case EntryPtrRole:
0145       return QVariant::fromValue(entry(index_));
0146     case GroupPtrRole:
0147       return QVariant::fromValue(group(index_));
0148     case ValidParentRole:
0149       return hasValidParent(index_);
0150   }
0151 
0152   return QVariant();
0153 }
0154 
0155 bool EntryGroupModel::setData(const QModelIndex& index_, const QVariant& value_, int role_) {
0156   // if the index has a parent, then it's not a group
0157   if(!index_.isValid() || hasValidParent(index_) || index_.row() >= rowCount() || role_ != Qt::DecorationRole) {
0158     return false;
0159   }
0160   m_groupIconNames.replace(index_.row(), value_.toString());
0161   return true;
0162 }
0163 
0164 QModelIndex EntryGroupModel::index(int row_, int column_, const QModelIndex& parent_) const {
0165   if(!hasIndex(row_, column_, parent_)) {
0166     return QModelIndex();
0167   }
0168 
0169   Node* parentNode;
0170   if(parent_.isValid()) {
0171     parentNode = static_cast<Node*>(parent_.internalPointer());
0172   } else {
0173     parentNode = m_rootNode;
0174   }
0175 
0176   Node* child = parentNode->child(row_);
0177   Q_ASSERT(child);
0178 /*
0179 if(!child) {
0180     myWarning() << "no child at row" << row_;
0181     return QModelIndex();
0182   }
0183 */
0184   return createIndex(row_, column_, child);
0185 }
0186 
0187 QModelIndex EntryGroupModel::parent(const QModelIndex& index_) const {
0188   if(!index_.isValid()) {
0189     return QModelIndex();
0190   }
0191 
0192   Node* node = static_cast<Node*>(index_.internalPointer());
0193   Q_ASSERT(node);
0194   Node* parentNode = node->parent();
0195   Q_ASSERT(parentNode);
0196 
0197   // if it's top-level, it has no parent
0198   if(parentNode == m_rootNode) {
0199     return QModelIndex();
0200   }
0201   return createIndex(parentNode->row(), 0, parentNode);
0202 }
0203 
0204 void EntryGroupModel::clear() {
0205   beginResetModel();
0206   m_groups.clear();
0207   delete m_rootNode;
0208   m_rootNode = new Node(nullptr);
0209   m_groupIconNames.clear();
0210   endResetModel();
0211 }
0212 
0213 void EntryGroupModel::addGroups(const QList<Tellico::Data::EntryGroup*>& groups_, const QString& iconName_) {
0214   if(groups_.isEmpty()) { // shouldn't ever happen
0215     myWarning() << "adding empty group list!";
0216     return;
0217   }
0218   beginInsertRows(QModelIndex(), rowCount(), rowCount()+groups_.count()-1);
0219   m_groups += groups_;
0220   foreach(Tellico::Data::EntryGroup* group, groups_) {
0221     Node* groupNode = new Node(m_rootNode);
0222     m_rootNode->addChild(groupNode);
0223     for(int i = 0; i < group->count(); ++i) {
0224       Node* childNode = new Node(groupNode);
0225       groupNode->addChild(childNode);
0226     }
0227     m_groupIconNames.append(iconName_);
0228   }
0229   endInsertRows();
0230 }
0231 
0232 QModelIndex EntryGroupModel::addGroup(Tellico::Data::EntryGroup* group_) {
0233   Q_ASSERT(group_);
0234   addGroups(QList<Tellico::Data::EntryGroup*>() << group_, QString());
0235   // rowCount() has increased now
0236   return index(rowCount()-1, 0);
0237 }
0238 
0239 QModelIndex EntryGroupModel::modifyGroup(Tellico::Data::EntryGroup* group_) {
0240   Q_ASSERT(group_);
0241   const int idx = m_groups.indexOf(group_);
0242   if(idx < 0) {
0243     myWarning() << "no group named" << group_->groupName();
0244     return QModelIndex();
0245   }
0246 
0247   QModelIndex groupIndex = index(idx, 0);
0248   Node* groupNode = m_rootNode->child(idx);
0249   const int oldCount = groupNode->childCount();
0250 
0251   beginRemoveRows(groupIndex, 0, groupNode->childCount() - 1);
0252   groupNode->removeAll();
0253   endRemoveRows();
0254 
0255   beginInsertRows(groupIndex, 0, group_->count() - 1);
0256   for(int i = 0; i < group_->count(); ++i) {
0257     Node* childNode = new Node(groupNode);
0258     groupNode->addChild(childNode);
0259   }
0260   endInsertRows();
0261 
0262   // the only data that might have changed is the count
0263   if(oldCount != groupNode->childCount()) {
0264     emit dataChanged(groupIndex, groupIndex);
0265   }
0266   return groupIndex;
0267 }
0268 
0269 void EntryGroupModel::removeGroup(Tellico::Data::EntryGroup* group_) {
0270   Q_ASSERT(group_);
0271   int idx = m_groups.indexOf(group_);
0272   if(idx < 0) {
0273     myWarning() << "no group named" << group_->groupName();
0274     return;
0275   }
0276 
0277   beginRemoveRows(QModelIndex(), idx, idx);
0278   m_groups.removeAt(idx);
0279   m_rootNode->removeChild(idx);
0280   m_groupIconNames.removeAt(idx);
0281   endRemoveRows();
0282 }
0283 
0284 Tellico::Data::EntryGroup* EntryGroupModel::group(const QModelIndex& index_) const {
0285   // if the parent isn't invalid, then it's not a top-level group
0286   if(!index_.isValid() || hasValidParent(index_) || index_.row() >= m_groups.count()) {
0287     return nullptr;
0288   }
0289   return m_groups.at(index_.row());
0290 }
0291 
0292 Tellico::Data::EntryPtr EntryGroupModel::entry(const QModelIndex& index_) const {
0293   // if there's not a parent, then it's a top-level item, no entry
0294   if(!hasValidParent(index_)) {
0295     return Tellico::Data::EntryPtr();
0296   }
0297   Tellico::Data::EntryPtr entry;
0298   Tellico::Data::EntryGroup* group = this->group(index_.parent());
0299   // it's possible to have an inconsistent model where the number of rows != number of entries in group
0300   // like when an entry is removed. modeltest catches this
0301   if(group && index_.row() < group->count()) {
0302     entry = group->at(index_.row());
0303   }
0304   return entry;
0305 }
0306 
0307 QModelIndex EntryGroupModel::indexFromGroup(Tellico::Data::EntryGroup* group_) const {
0308   if(!group_) {
0309     return QModelIndex();
0310   }
0311   int idx = m_groups.indexOf(group_);
0312   return idx < 0 ? QModelIndex() : index(idx, 0);
0313 }
0314 
0315 // quick parent check for when we don't actually need to know the parent
0316 // just that the parent is valid
0317 // since calling parent() does the node indexOf(), which turns out to be slightly expensive
0318 bool EntryGroupModel::hasValidParent(const QModelIndex& index_) const {
0319   if(!index_.isValid()) {
0320     return false;
0321   }
0322 
0323   Node* node = static_cast<Node*>(index_.internalPointer());
0324   Q_ASSERT(node);
0325   Node* parentNode = node->parent();
0326   Q_ASSERT(parentNode);
0327 
0328   // if it's top-level, it has no parent
0329   return parentNode && parentNode != m_rootNode;
0330 }