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

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 "filtermodel.h"
0026 #include "models.h"
0027 #include "../filter.h"
0028 #include "../document.h"
0029 #include "../collection.h"
0030 #include "../collectionfactory.h"
0031 #include "../entry.h"
0032 #include "../tellico_debug.h"
0033 
0034 #include <KLocalizedString>
0035 #include <QIcon>
0036 
0037 using namespace Tellico;
0038 using Tellico::FilterModel;
0039 
0040 class FilterModel::Node {
0041 public:
0042   // for nodes that refer to a filter (not an entry), then overload the id variable to indicate whether
0043   // the filter node has been populated or not. m_id == -1 means not populated, m_id == 0 means yes populated
0044   Node(Node* parent_, Data::ID id_=-1) : m_parent(parent_), m_id(id_) {}
0045   ~Node() { qDeleteAll(m_children); }
0046 
0047   Node* parent() const { return m_parent; }
0048   Node* child(int row) const { return m_children.at(row); }
0049   int row() const { return m_parent ? m_parent->m_children.indexOf(const_cast<Node*>(this)) : 0; }
0050   Data::ID id() const { return m_id; }
0051   void setID(Data::ID id_) { m_id = id_; }
0052   int childCount() const { return m_children.count(); }
0053 
0054   void addChild(Node* child) {  m_children.append(child); }
0055   void removeChild(int i) {  delete m_children.takeAt(i); }
0056   void removeAll() { qDeleteAll(m_children); m_children.clear(); }
0057 
0058 private:
0059   Node* m_parent;
0060   QList<Node*> m_children;
0061   Data::ID m_id;
0062 };
0063 
0064 FilterModel::FilterModel(QObject* parent) : QAbstractItemModel(parent), m_rootNode(new Node(nullptr)), m_beingInvalidated(false) {
0065 }
0066 
0067 FilterModel::~FilterModel() {
0068   delete m_rootNode;
0069   m_rootNode = nullptr;
0070 }
0071 
0072 int FilterModel::rowCount(const QModelIndex& index_) const {
0073   if(!index_.isValid()) {
0074     return m_filters.count();
0075   }
0076   QModelIndex parent = index_.parent();
0077   if(parent.isValid()) {
0078     return 0; // a parent index means it points to an entry, not a filter, so there are no children
0079   }
0080   Node* node = static_cast<Node*>(index_.internalPointer());
0081   Q_ASSERT(node);
0082   // node may not be populated yet, do so unless we're in the middle of invalidating the node
0083   // for a filter node, an id == -1 then it means it has not yet been populated (better than checking
0084   // if childCount() == 0 since a filter could have zero entry matches)
0085   if(!m_beingInvalidated && node->id() == -1) {
0086     populateFilterNode(node, m_filters.at(index_.row()));
0087   }
0088   return node->childCount();
0089 }
0090 
0091 int FilterModel::columnCount(const QModelIndex&) const {
0092   return 1;
0093 }
0094 
0095 QVariant FilterModel::headerData(int section_, Qt::Orientation orientation_, int role_) const {
0096   if(section_ < 0 || section_ >= columnCount() || orientation_ != Qt::Horizontal) {
0097     return QVariant();
0098   }
0099   if(role_ == Qt::DisplayRole) {
0100     return m_header;
0101   }
0102   return QVariant();
0103 }
0104 
0105 bool FilterModel::setHeaderData(int section_, Qt::Orientation orientation_,
0106                                     const QVariant& value_, int role_) {
0107   if(section_ < 0 || section_ >= columnCount() || orientation_ != Qt::Horizontal || role_ != Qt::EditRole) {
0108     return false;
0109   }
0110   m_header = value_.toString();
0111   emit headerDataChanged(orientation_, section_, section_);
0112   return true;
0113 }
0114 
0115 QVariant FilterModel::data(const QModelIndex& index_, int role_) const {
0116   if(!index_.isValid()) {
0117     return QVariant();
0118   }
0119 
0120   QModelIndex parent = index_.parent();
0121 
0122   if(index_.row() >= rowCount(parent)) {
0123     return QVariant();
0124   }
0125 
0126   switch(role_) {
0127     case Qt::DisplayRole:
0128       if(parent.isValid()) {
0129         // it points to an entry
0130         Data::EntryPtr e = entry(index_);
0131         return e ? e->title() : QString();
0132       } else {
0133         // it points to a filter
0134         FilterPtr f = filter(index_);
0135         return f ? f->name() : QString();
0136       }
0137     case Qt::DecorationRole:
0138       return parent.isValid() ? QIcon(QLatin1String(":/icons/") + CollectionFactory::typeName(entry(index_)->collection()))
0139                               : QIcon::fromTheme(QLatin1String("view-filter"));
0140     case RowCountRole:
0141       return rowCount(index_);
0142     case EntryPtrRole:
0143       return QVariant::fromValue(entry(index_));
0144   }
0145 
0146   return QVariant();
0147 }
0148 
0149 QModelIndex FilterModel::index(int row_, int column_, const QModelIndex& parent_) const {
0150   if(!hasIndex(row_, column_, parent_)) {
0151     return QModelIndex();
0152   }
0153 
0154   Node* parentNode;
0155   if(parent_.isValid()) {
0156     parentNode = static_cast<Node*>(parent_.internalPointer());
0157   } else {
0158     parentNode = m_rootNode;
0159   }
0160 
0161   Node* child = parentNode->child(row_);
0162   if(!child) {
0163     return QModelIndex();
0164   }
0165   return createIndex(row_, column_, child);
0166 }
0167 
0168 QModelIndex FilterModel::parent(const QModelIndex& index_) const {
0169   if(!index_.isValid()) {
0170     return QModelIndex();
0171   }
0172 
0173   Node* node = static_cast<Node*>(index_.internalPointer());
0174   Q_ASSERT(node);
0175   Node* parentNode = node->parent();
0176   Q_ASSERT(parentNode);
0177 
0178   // if it's top-level, it has no parent
0179   if(parentNode == m_rootNode) {
0180     return QModelIndex();
0181   }
0182   return createIndex(parentNode->row(), 0, parentNode);
0183 }
0184 
0185 void FilterModel::clear() {
0186   beginResetModel();
0187   m_filters.clear();
0188   delete m_rootNode;
0189   m_rootNode = new Node(nullptr);
0190   endResetModel();
0191 }
0192 
0193 void FilterModel::addFilters(const Tellico::FilterList& filters_) {
0194   beginInsertRows(QModelIndex(), rowCount(), rowCount()+filters_.count()-1);
0195   m_filters += filters_;
0196   foreach(FilterPtr filter, filters_) {
0197     Node* filterNode = new Node(m_rootNode);
0198     m_rootNode->addChild(filterNode);
0199   }
0200   endInsertRows();
0201 }
0202 
0203 QModelIndex FilterModel::addFilter(Tellico::FilterPtr filter_) {
0204   Q_ASSERT(filter_);
0205   addFilters(FilterList() << filter_);
0206   // rowCount() has increased now
0207   return index(rowCount()-1, 0);
0208 }
0209 
0210 void FilterModel::removeFilter(Tellico::FilterPtr filter_) {
0211   Q_ASSERT(filter_);
0212   int idx = m_filters.indexOf(filter_);
0213   if(idx < 0) {
0214     myWarning() << "no filter named" << filter_->name();
0215     return;
0216   }
0217 
0218   beginRemoveRows(QModelIndex(), idx, idx);
0219   m_filters.removeAt(idx);
0220   m_rootNode->removeChild(idx);
0221   endRemoveRows();
0222 }
0223 
0224 Tellico::FilterPtr FilterModel::filter(const QModelIndex& index_) const {
0225   // if the parent isn't invalid, then it's not a top-level filter
0226   if(!index_.isValid() || index_.parent().isValid() || index_.row() >= m_filters.count()) {
0227     myDebug() << "no filter found for" << index_.row();
0228     return FilterPtr();
0229   }
0230   return m_filters.at(index_.row());
0231 }
0232 
0233 Tellico::Data::EntryPtr FilterModel::entry(const QModelIndex& index_) const {
0234   // if there's not a parent, then it's a top-level item, no entry
0235   if(!index_.parent().isValid()) {
0236     return Data::EntryPtr();
0237   }
0238   Data::EntryPtr entry;
0239   Node* node = static_cast<Node*>(index_.internalPointer());
0240   if(node) {
0241     entry = Data::Document::self()->collection()->entryById(node->id());
0242     if(!entry)  {
0243       myWarning() << "no entry found for id" << node->id();
0244     }
0245   }
0246   return entry;
0247 }
0248 
0249 void FilterModel::invalidate(const QModelIndex& index_) {
0250   // delete and recreate the node, only if
0251   // it has no parent, i.e. it points to a filter
0252   if(index_.parent().isValid()) {
0253     return;
0254   }
0255 
0256   m_beingInvalidated = true;
0257 
0258   Node* filterNode = static_cast<Node*>(index_.internalPointer());
0259   Q_ASSERT(filterNode);
0260   if(!filterNode) {
0261     return;
0262   }
0263 
0264   beginRemoveRows(index_, 0, filterNode->childCount() - 1);
0265   filterNode->removeAll();
0266   endRemoveRows();
0267 
0268   Data::EntryList entries = Data::Document::self()->filteredEntries(filter(index_));
0269   beginInsertRows(index_, 0, entries.count() - 1);
0270   foreach(Data::EntryPtr entry, entries) {
0271     Node* childNode = new Node(filterNode, entry->id());
0272     filterNode->addChild(childNode);
0273   }
0274   endInsertRows();
0275 
0276   emit dataChanged(index_, index_);
0277   m_beingInvalidated = false;
0278 }
0279 
0280 bool FilterModel::indexContainsEntry(const QModelIndex& parent_, Data::EntryPtr entry_) const {
0281   Q_ASSERT(entry_);
0282   Q_ASSERT(parent_.isValid());
0283   if(!entry_ || !parent_.isValid()) {
0284     return false;
0285   }
0286   Node* parentNode = static_cast<Node*>(parent_.internalPointer());
0287   Q_ASSERT(parentNode);
0288   if(!parentNode) {
0289     return false;
0290   }
0291   for(int i = 0; i < parentNode->childCount(); ++i) {
0292     Node* childNode = parentNode->child(i);
0293     Q_ASSERT(childNode);
0294     if(childNode && childNode->id() == entry_->id()) {
0295       return true;
0296     }
0297   }
0298   return false;
0299 }
0300 
0301 void FilterModel::populateFilterNode(Node* node_, const FilterPtr filter_) const {
0302   Q_ASSERT(node_);
0303   Q_ASSERT(filter_);
0304   if(!node_ || !filter_) {
0305     return;
0306   }
0307 
0308   Data::EntryList entries = Data::Document::self()->filteredEntries(filter_);
0309   foreach(Data::EntryPtr entry, entries) {
0310     Node* childNode = new Node(node_, entry->id());
0311     node_->addChild(childNode);
0312   }
0313   // for filter nodes (which don't need ID), an ID value of 0 instead of -1 means it has been populated
0314   node_->setID(0);
0315 }