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 }