File indexing completed on 2024-05-19 04:56:07

0001 /**
0002  * \file modeliterator.cpp
0003  * Iterator for Qt models.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 26-Mar-2011
0008  *
0009  * Copyright (C) 2011-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "modeliterator.h"
0028 #include <QItemSelectionModel>
0029 #include "fileproxymodel.h"
0030 
0031 /**
0032  * Constructor.
0033  *
0034  * @param rootIdx root of model to iterate
0035  */
0036 ModelIterator::ModelIterator(const QPersistentModelIndex& rootIdx)
0037     : m_model(rootIdx.model())
0038 {
0039   m_nodes.push(rootIdx);
0040   next();
0041 }
0042 
0043 /**
0044  * Check if a next item exists.
0045  * @return true if there is a next index
0046  */
0047 bool ModelIterator::hasNext() const
0048 {
0049   return m_model && m_nextIdx.isValid();
0050 }
0051 
0052 /**
0053  * Advance iterator and return next item.
0054  * @return next index
0055  */
0056 QPersistentModelIndex ModelIterator::next()
0057 {
0058   if (!m_model)
0059     return QPersistentModelIndex();
0060   QPersistentModelIndex result = m_nextIdx;
0061   if (!m_nodes.isEmpty()) {
0062     m_nextIdx = m_nodes.pop();
0063     if (m_nextIdx.isValid()) {
0064       for (int row = m_model->rowCount(m_nextIdx) - 1; row >= 0; --row) {
0065         m_nodes.push(m_model->index(row, 0, m_nextIdx));
0066       }
0067     }
0068   } else {
0069     m_nextIdx = QPersistentModelIndex();
0070   }
0071   return result;
0072 }
0073 
0074 /**
0075  * Get next item without moving iterator.
0076  * @return next index
0077  */
0078 QPersistentModelIndex ModelIterator::peekNext() const
0079 {
0080   if (!m_model)
0081     return QPersistentModelIndex();
0082   return m_nextIdx;
0083 }
0084 
0085 /**
0086  * Advance iterator and return data of next index.
0087  * @param role model item role to get
0088  * @return data of next index
0089  */
0090 QVariant ModelIterator::nextData(int role) {
0091   if (!m_model)
0092     return QVariant();
0093   return m_model->data(next(), role);
0094 }
0095 
0096 /**
0097  * Get data of next item without moving iterator.
0098  * @param role model item role to get
0099  * @return data of next index
0100  */
0101 QVariant ModelIterator::peekNextData(int role) const {
0102   if (!m_model)
0103     return QVariant();
0104   return m_model->data(m_nextIdx, role);
0105 }
0106 
0107 
0108 /**
0109  * Constructor.
0110  *
0111  * @param rootIdx root of model to iterate
0112  */
0113 ModelBfsIterator::ModelBfsIterator(const QPersistentModelIndex& rootIdx)
0114     : m_model(rootIdx.model()), m_nextIdx(rootIdx), m_parentIdx(rootIdx), m_row(0)
0115 {
0116 }
0117 
0118 /**
0119  * Check if a next item exists.
0120  * @return true if there is a next index
0121  */
0122 bool ModelBfsIterator::hasNext() const
0123 {
0124   return m_model && m_nextIdx.isValid();
0125 }
0126 
0127 /**
0128  * Advance iterator and return next item.
0129  * @return next index
0130  */
0131 QPersistentModelIndex ModelBfsIterator::next()
0132 {
0133   if (!m_model)
0134     return QPersistentModelIndex();
0135   QPersistentModelIndex result = m_nextIdx;
0136   forever {
0137     if (m_parentIdx.isValid() && m_row < m_model->rowCount(m_parentIdx)) {
0138       m_nextIdx = m_model->index(m_row, 0, m_parentIdx);
0139       m_nodes.enqueue(m_nextIdx);
0140       ++m_row;
0141       break;
0142     }
0143     if (!m_nodes.isEmpty()) {
0144       m_parentIdx = m_nodes.dequeue();
0145       m_row = 0;
0146     } else {
0147       m_nextIdx = QPersistentModelIndex();
0148       break;
0149     }
0150   }
0151   return result;
0152 }
0153 
0154 /**
0155  * Get next item without moving iterator.
0156  * @return next index
0157  */
0158 QPersistentModelIndex ModelBfsIterator::peekNext() const
0159 {
0160   if (!m_model)
0161     return QPersistentModelIndex();
0162   return m_nextIdx;
0163 }
0164 
0165 /**
0166  * Advance iterator and return data of next index.
0167  * @param role model item role to get
0168  * @return data of next index
0169  */
0170 QVariant ModelBfsIterator::nextData(int role) {
0171   if (!m_model)
0172     return QVariant();
0173   return m_model->data(next(), role);
0174 }
0175 
0176 /**
0177  * Get data of next item without moving iterator.
0178  * @param role model item role to get
0179  * @return data of next index
0180  */
0181 QVariant ModelBfsIterator::peekNextData(int role) const {
0182   if (!m_model)
0183     return QVariant();
0184   return m_model->data(m_nextIdx, role);
0185 }
0186 
0187 
0188 /**
0189  * Destructor.
0190  */
0191 AbstractTaggedFileIterator::~AbstractTaggedFileIterator()
0192 {
0193 }
0194 
0195 
0196 /**
0197  * Constructor.
0198  *
0199  * @param rootIdx root of model to iterate
0200  */
0201 TaggedFileIterator::TaggedFileIterator(const QPersistentModelIndex& rootIdx)
0202     : m_it(rootIdx), m_nextFile(nullptr)
0203 {
0204   next();
0205 }
0206 
0207 /**
0208  * Advance iterator and return next item.
0209  * @return next file
0210  */
0211 TaggedFile* TaggedFileIterator::next()
0212 {
0213   TaggedFile* result = m_nextFile;
0214   m_nextFile = nullptr;
0215   while (m_it.hasNext()) {
0216     if (QPersistentModelIndex index = m_it.next();
0217         (m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr)
0218       break;
0219   }
0220   return result;
0221 }
0222 
0223 /**
0224  * Try to close the file handles.
0225  *
0226  * @param index root of model to iterate
0227  */
0228 void TaggedFileIterator::closeFileHandles(const QPersistentModelIndex& index)
0229 {
0230   TaggedFileIterator it(index);
0231   while (it.hasNext()) {
0232     it.next()->closeFileHandle();
0233   }
0234 }
0235 
0236 
0237 /**
0238  * Constructor.
0239  *
0240  * @param rootIdx root of model to iterate
0241  * @param selectModel selection model
0242  * @param allIfNoneSelected treat all files as selected when nothing is
0243  * selected
0244  */
0245 SelectedTaggedFileIterator::SelectedTaggedFileIterator(
0246     const QPersistentModelIndex& rootIdx,
0247     const QItemSelectionModel* selectModel,
0248     bool allIfNoneSelected)
0249     : m_it(rootIdx), m_nextFile(nullptr), m_selectModel(selectModel),
0250       m_allSelected(!m_selectModel ||
0251                     (allIfNoneSelected && !m_selectModel->hasSelection()))
0252 {
0253   next();
0254 }
0255 
0256 /**
0257  * Advance iterator and return next item.
0258  * @return next file
0259  */
0260 TaggedFile* SelectedTaggedFileIterator::next()
0261 {
0262   TaggedFile* result = m_nextFile;
0263   m_nextFile = nullptr;
0264   while (m_it.hasNext()) {
0265     if (QPersistentModelIndex index = m_it.next();
0266         (m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr &&
0267         (m_allSelected || m_selectModel->isSelected(index)))
0268       break;
0269     m_nextFile = nullptr;
0270   }
0271   return result;
0272 }
0273 
0274 /**
0275  * Check if nothing is selected.
0276  * @return true if nothing is selected.
0277  */
0278 bool SelectedTaggedFileIterator::hasNoSelection() const
0279 {
0280   return m_selectModel && !m_selectModel->hasSelection();
0281 }
0282 
0283 /**
0284  * Constructor.
0285  *
0286  * @param index of the directory or a file in it
0287  */
0288 TaggedFileOfDirectoryIterator::TaggedFileOfDirectoryIterator(
0289     const QPersistentModelIndex& index)
0290     : m_row(0), m_model(index.model()),
0291       m_parentIdx(m_model && m_model->hasChildren(index)
0292                   ? index
0293                   : QPersistentModelIndex(index.parent())),
0294       m_nextFile(nullptr)
0295 {
0296   next();
0297 }
0298 
0299 /**
0300  * Check if a next item exists.
0301  * @return true if there is a next file
0302  */
0303 bool TaggedFileOfDirectoryIterator::hasNext() const
0304 {
0305   return m_model && m_nextFile != nullptr;
0306 }
0307 
0308 /**
0309  * Advance iterator and return next item.
0310  * @return next file
0311  */
0312 TaggedFile* TaggedFileOfDirectoryIterator::next() {
0313   if (!m_model)
0314     return nullptr;
0315   TaggedFile* result = m_nextFile;
0316   m_nextFile = nullptr;
0317   while (m_row < m_model->rowCount(m_parentIdx)) {
0318     if (QModelIndex index = m_model->index(m_row++, 0, m_parentIdx);
0319         (m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr)
0320       break;
0321   }
0322   return result;
0323 }
0324 
0325 /**
0326  * Get next item without moving iterator.
0327  * @return next file
0328  */
0329 TaggedFile* TaggedFileOfDirectoryIterator::peekNext() const
0330 {
0331   if (!m_model)
0332     return nullptr;
0333   return m_nextFile;
0334 }
0335 
0336 /**
0337  * Get first tagged file in directory.
0338  * @param index of the directory or a file in it
0339  * @return first tagged file in directory, 0 if none.
0340  */
0341 TaggedFile* TaggedFileOfDirectoryIterator::first(
0342     const QPersistentModelIndex& index)
0343 {
0344   if (TaggedFileOfDirectoryIterator it(index); it.hasNext())
0345     return it.peekNext();
0346   return nullptr;
0347 }
0348 
0349 
0350 /**
0351  * Constructor.
0352  *
0353  * @param index of the directory or a file in it
0354  * @param selectModel selection model
0355  * @param allIfNoneSelected treat all files as selected when nothing is
0356  * selected
0357  */
0358 SelectedTaggedFileOfDirectoryIterator::SelectedTaggedFileOfDirectoryIterator(
0359   const QPersistentModelIndex& index,
0360   const QItemSelectionModel* selectModel,
0361   bool allIfNoneSelected)
0362   : m_row(0), m_model(index.model()),
0363     m_parentIdx(m_model && m_model->hasChildren(index)
0364                 ? index : QPersistentModelIndex(index.parent())),
0365     m_nextFile(nullptr),
0366     m_selectModel(selectModel),
0367     m_allSelected(!m_selectModel ||
0368                   (allIfNoneSelected && !m_selectModel->hasSelection()))
0369 {
0370   next();
0371 }
0372 
0373 /**
0374  * Check if a next item exists.
0375  * @return true if there is a next file
0376  */
0377 bool SelectedTaggedFileOfDirectoryIterator::hasNext() const
0378 {
0379   return m_model && m_nextFile != nullptr;
0380 }
0381 
0382 /**
0383  * Advance iterator and return next item.
0384  * @return next file
0385  */
0386 TaggedFile* SelectedTaggedFileOfDirectoryIterator::next() {
0387   if (!m_model)
0388     return nullptr;
0389   TaggedFile* result = m_nextFile;
0390   m_nextFile = nullptr;
0391   while (m_row < m_model->rowCount(m_parentIdx)) {
0392     if (QModelIndex index = m_model->index(m_row++, 0, m_parentIdx);
0393         (m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr &&
0394         (m_allSelected || m_selectModel->isSelected(index)))
0395       break;
0396     m_nextFile = nullptr;
0397   }
0398   return result;
0399 }
0400 
0401 /**
0402  * Get next item without moving iterator.
0403  * @return next file
0404  */
0405 TaggedFile* SelectedTaggedFileOfDirectoryIterator::peekNext() const
0406 {
0407   if (!m_model)
0408     return nullptr;
0409   return m_nextFile;
0410 }
0411 
0412 
0413 /**
0414  * Constructor.
0415  *
0416  * @param selectModel selection model
0417  */
0418 TaggedFileOfSelectedDirectoriesIterator::TaggedFileOfSelectedDirectoriesIterator(
0419   const QItemSelectionModel* selectModel) : m_model(nullptr), m_dirIdx(0), m_row(0),
0420   m_nextFile(nullptr)
0421 {
0422   if (selectModel &&
0423       (m_model = qobject_cast<const FileProxyModel*>(selectModel->model()))
0424       != nullptr) {
0425     const auto indexes = selectModel->selectedRows();
0426     for (const QModelIndex& index : indexes) {
0427       if (m_model->isDir(index)) {
0428         m_dirIndexes.append(getIndexesOfDirWithSubDirs(index));
0429       }
0430     }
0431   }
0432   next();
0433 }
0434 
0435 /**
0436  * Get indexes of directory and recursively all subdirectories.
0437  * @param dirIndex index of directory
0438  * @return list with dirIndex and its subdirectories.
0439  */
0440 QList<QPersistentModelIndex>
0441 TaggedFileOfSelectedDirectoriesIterator::getIndexesOfDirWithSubDirs(
0442   const QModelIndex& dirIndex) const
0443 {
0444   QList<QPersistentModelIndex> dirs;
0445   dirs.append(dirIndex);
0446   for (int dirsPos = 0; dirsPos < dirs.size(); ++dirsPos) {
0447     QPersistentModelIndex parentIndex(dirs.at(dirsPos));
0448     for (int row = 0; row < m_model->rowCount(parentIndex); ++row) {
0449       if (QModelIndex index(m_model->index(row, 0, parentIndex));
0450           m_model->isDir(index)) {
0451         dirs.append(index);
0452       }
0453     }
0454   }
0455   return dirs;
0456 }
0457 
0458 /**
0459  * Check if a next item exists.
0460  * @return true if there is a next file
0461  */
0462 bool TaggedFileOfSelectedDirectoriesIterator::hasNext() const
0463 {
0464   return m_model && m_nextFile != nullptr;
0465 }
0466 
0467 /**
0468  * Advance iterator and return next item.
0469  * @return next file
0470  */
0471 TaggedFile* TaggedFileOfSelectedDirectoriesIterator::next()
0472 {
0473   if (!m_model)
0474     return nullptr;
0475   TaggedFile* result = m_nextFile;
0476   m_nextFile = nullptr;
0477   while (!m_nextFile) {
0478     if (m_dirIdx >= m_dirIndexes.size())
0479       break;
0480     QPersistentModelIndex parentIdx(m_dirIndexes.at(m_dirIdx));
0481     while (m_row < m_model->rowCount(parentIdx)) {
0482       if (QModelIndex index = m_model->index(m_row++, 0, parentIdx);
0483           (m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr)
0484         break;
0485     }
0486     if (m_row >= m_model->rowCount(parentIdx)) {
0487       ++m_dirIdx;
0488       m_row = 0;
0489     }
0490   }
0491   return result;
0492 }
0493 
0494 /**
0495  * Get next item without moving iterator.
0496  * @return next file
0497  */
0498 TaggedFile* TaggedFileOfSelectedDirectoriesIterator::peekNext() const
0499 {
0500   if (!m_model)
0501     return nullptr;
0502   return m_nextFile;
0503 }