1 /**
2  * \file modeliterator.cpp
3  * Iterator for Qt models.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 26-Mar-2011
8  *
9  * Copyright (C) 2011-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "modeliterator.h"
28 #include <QItemSelectionModel>
29 #include "fileproxymodel.h"
30 
31 /**
32  * Constructor.
33  *
34  * @param rootIdx root of model to iterate
35  */
ModelIterator(const QPersistentModelIndex & rootIdx)36 ModelIterator::ModelIterator(const QPersistentModelIndex& rootIdx)
37     : m_model(rootIdx.model())
38 {
39   m_nodes.push(rootIdx);
40   next();
41 }
42 
43 /**
44  * Check if a next item exists.
45  * @return true if there is a next index
46  */
hasNext() const47 bool ModelIterator::hasNext() const
48 {
49   return m_model && m_nextIdx.isValid();
50 }
51 
52 /**
53  * Advance iterator and return next item.
54  * @return next index
55  */
next()56 QPersistentModelIndex ModelIterator::next()
57 {
58   if (!m_model)
59     return QPersistentModelIndex();
60   QPersistentModelIndex result = m_nextIdx;
61   if (!m_nodes.isEmpty()) {
62     m_nextIdx = m_nodes.pop();
63     if (m_nextIdx.isValid()) {
64       for (int row = m_model->rowCount(m_nextIdx) - 1; row >= 0; --row) {
65         m_nodes.push(m_model->index(row, 0, m_nextIdx));
66       }
67     }
68   } else {
69     m_nextIdx = QPersistentModelIndex();
70   }
71   return result;
72 }
73 
74 /**
75  * Get next item without moving iterator.
76  * @return next index
77  */
peekNext() const78 QPersistentModelIndex ModelIterator::peekNext() const
79 {
80   if (!m_model)
81     return QPersistentModelIndex();
82   return m_nextIdx;
83 }
84 
85 /**
86  * Advance iterator and return data of next index.
87  * @param role model item role to get
88  * @return data of next index
89  */
nextData(int role)90 QVariant ModelIterator::nextData(int role) {
91   if (!m_model)
92     return QVariant();
93   return m_model->data(next(), role);
94 }
95 
96 /**
97  * Get data of next item without moving iterator.
98  * @param role model item role to get
99  * @return data of next index
100  */
peekNextData(int role) const101 QVariant ModelIterator::peekNextData(int role) const {
102   if (!m_model)
103     return QVariant();
104   return m_model->data(m_nextIdx, role);
105 }
106 
107 
108 /**
109  * Constructor.
110  *
111  * @param rootIdx root of model to iterate
112  */
ModelBfsIterator(const QPersistentModelIndex & rootIdx)113 ModelBfsIterator::ModelBfsIterator(const QPersistentModelIndex& rootIdx)
114     : m_model(rootIdx.model()), m_nextIdx(rootIdx), m_parentIdx(rootIdx), m_row(0)
115 {
116 }
117 
118 /**
119  * Check if a next item exists.
120  * @return true if there is a next index
121  */
hasNext() const122 bool ModelBfsIterator::hasNext() const
123 {
124   return m_model && m_nextIdx.isValid();
125 }
126 
127 /**
128  * Advance iterator and return next item.
129  * @return next index
130  */
next()131 QPersistentModelIndex ModelBfsIterator::next()
132 {
133   if (!m_model)
134     return QPersistentModelIndex();
135   QPersistentModelIndex result = m_nextIdx;
136   forever {
137     if (m_parentIdx.isValid() && m_row < m_model->rowCount(m_parentIdx)) {
138       m_nextIdx = m_model->index(m_row, 0, m_parentIdx);
139       m_nodes.enqueue(m_nextIdx);
140       ++m_row;
141       break;
142     } else if (!m_nodes.isEmpty()) {
143       m_parentIdx = m_nodes.dequeue();
144       m_row = 0;
145     } else {
146       m_nextIdx = QPersistentModelIndex();
147       break;
148     }
149   }
150   return result;
151 }
152 
153 /**
154  * Get next item without moving iterator.
155  * @return next index
156  */
peekNext() const157 QPersistentModelIndex ModelBfsIterator::peekNext() const
158 {
159   if (!m_model)
160     return QPersistentModelIndex();
161   return m_nextIdx;
162 }
163 
164 /**
165  * Advance iterator and return data of next index.
166  * @param role model item role to get
167  * @return data of next index
168  */
nextData(int role)169 QVariant ModelBfsIterator::nextData(int role) {
170   if (!m_model)
171     return QVariant();
172   return m_model->data(next(), role);
173 }
174 
175 /**
176  * Get data of next item without moving iterator.
177  * @param role model item role to get
178  * @return data of next index
179  */
peekNextData(int role) const180 QVariant ModelBfsIterator::peekNextData(int role) const {
181   if (!m_model)
182     return QVariant();
183   return m_model->data(m_nextIdx, role);
184 }
185 
186 
187 /**
188  * Destructor.
189  */
~AbstractTaggedFileIterator()190 AbstractTaggedFileIterator::~AbstractTaggedFileIterator()
191 {
192 }
193 
194 
195 /**
196  * Constructor.
197  *
198  * @param rootIdx root of model to iterate
199  */
TaggedFileIterator(const QPersistentModelIndex & rootIdx)200 TaggedFileIterator::TaggedFileIterator(const QPersistentModelIndex& rootIdx)
201     : m_it(rootIdx), m_nextFile(nullptr)
202 {
203   next();
204 }
205 
206 /**
207  * Advance iterator and return next item.
208  * @return next file
209  */
next()210 TaggedFile* TaggedFileIterator::next()
211 {
212   TaggedFile* result = m_nextFile;
213   m_nextFile = nullptr;
214   while (m_it.hasNext()) {
215     QPersistentModelIndex index = m_it.next();
216     if ((m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr)
217       break;
218   }
219   return result;
220 }
221 
222 /**
223  * Try to close the file handles.
224  *
225  * @param index root of model to iterate
226  */
closeFileHandles(const QPersistentModelIndex & index)227 void TaggedFileIterator::closeFileHandles(const QPersistentModelIndex& index)
228 {
229   TaggedFileIterator it(index);
230   while (it.hasNext()) {
231     it.next()->closeFileHandle();
232   }
233 }
234 
235 
236 /**
237  * Constructor.
238  *
239  * @param rootIdx root of model to iterate
240  * @param selectModel selection model
241  * @param allIfNoneSelected treat all files as selected when nothing is
242  * selected
243  */
SelectedTaggedFileIterator(const QPersistentModelIndex & rootIdx,const QItemSelectionModel * selectModel,bool allIfNoneSelected)244 SelectedTaggedFileIterator::SelectedTaggedFileIterator(
245     const QPersistentModelIndex& rootIdx,
246     const QItemSelectionModel* selectModel,
247     bool allIfNoneSelected)
248     : m_it(rootIdx), m_nextFile(nullptr), m_selectModel(selectModel),
249       m_allSelected(!m_selectModel ||
250                     (allIfNoneSelected && !m_selectModel->hasSelection()))
251 {
252   next();
253 }
254 
255 /**
256  * Advance iterator and return next item.
257  * @return next file
258  */
next()259 TaggedFile* SelectedTaggedFileIterator::next()
260 {
261   TaggedFile* result = m_nextFile;
262   m_nextFile = nullptr;
263   while (m_it.hasNext()) {
264     QPersistentModelIndex index = m_it.next();
265     if ((m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr &&
266         (m_allSelected || m_selectModel->isSelected(index)))
267       break;
268     else
269       m_nextFile = nullptr;
270   }
271   return result;
272 }
273 
274 /**
275  * Check if nothing is selected.
276  * @return true if nothing is selected.
277  */
hasNoSelection() const278 bool SelectedTaggedFileIterator::hasNoSelection() const
279 {
280   return m_selectModel && !m_selectModel->hasSelection();
281 }
282 
283 /**
284  * Constructor.
285  *
286  * @param index of the directory or a file in it
287  */
TaggedFileOfDirectoryIterator(const QPersistentModelIndex & index)288 TaggedFileOfDirectoryIterator::TaggedFileOfDirectoryIterator(
289     const QPersistentModelIndex& index)
290     : m_row(0), m_model(index.model()),
291       m_parentIdx(m_model && m_model->hasChildren(index)
292                   ? index
293                   : QPersistentModelIndex(index.parent())),
294       m_nextFile(nullptr)
295 {
296   next();
297 }
298 
299 /**
300  * Check if a next item exists.
301  * @return true if there is a next file
302  */
hasNext() const303 bool TaggedFileOfDirectoryIterator::hasNext() const
304 {
305   return m_model && m_nextFile != nullptr;
306 }
307 
308 /**
309  * Advance iterator and return next item.
310  * @return next file
311  */
next()312 TaggedFile* TaggedFileOfDirectoryIterator::next() {
313   if (!m_model)
314     return nullptr;
315   TaggedFile* result = m_nextFile;
316   m_nextFile = nullptr;
317   while (m_row < m_model->rowCount(m_parentIdx)) {
318     QModelIndex index = m_model->index(m_row++, 0, m_parentIdx);
319     if ((m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr)
320       break;
321   }
322   return result;
323 }
324 
325 /**
326  * Get next item without moving iterator.
327  * @return next file
328  */
peekNext() const329 TaggedFile* TaggedFileOfDirectoryIterator::peekNext() const
330 {
331   if (!m_model)
332     return nullptr;
333   return m_nextFile;
334 }
335 
336 /**
337  * Get first tagged file in directory.
338  * @param index of the directory or a file in it
339  * @return first tagged file in directory, 0 if none.
340  */
first(const QPersistentModelIndex & index)341 TaggedFile* TaggedFileOfDirectoryIterator::first(
342     const QPersistentModelIndex& index)
343 {
344   TaggedFileOfDirectoryIterator it(index);
345   if (it.hasNext())
346     return it.peekNext();
347   return nullptr;
348 }
349 
350 
351 /**
352  * Constructor.
353  *
354  * @param index of the directory or a file in it
355  * @param selectModel selection model
356  * @param allIfNoneSelected treat all files as selected when nothing is
357  * selected
358  */
SelectedTaggedFileOfDirectoryIterator(const QPersistentModelIndex & index,const QItemSelectionModel * selectModel,bool allIfNoneSelected)359 SelectedTaggedFileOfDirectoryIterator::SelectedTaggedFileOfDirectoryIterator(
360   const QPersistentModelIndex& index,
361   const QItemSelectionModel* selectModel,
362   bool allIfNoneSelected)
363   : m_row(0), m_model(index.model()),
364     m_parentIdx(m_model && m_model->hasChildren(index)
365                 ? index : QPersistentModelIndex(index.parent())),
366     m_nextFile(nullptr),
367     m_selectModel(selectModel),
368     m_allSelected(!m_selectModel ||
369                   (allIfNoneSelected && !m_selectModel->hasSelection()))
370 {
371   next();
372 }
373 
374 /**
375  * Check if a next item exists.
376  * @return true if there is a next file
377  */
hasNext() const378 bool SelectedTaggedFileOfDirectoryIterator::hasNext() const
379 {
380   return m_model && m_nextFile != nullptr;
381 }
382 
383 /**
384  * Advance iterator and return next item.
385  * @return next file
386  */
next()387 TaggedFile* SelectedTaggedFileOfDirectoryIterator::next() {
388   if (!m_model)
389     return nullptr;
390   TaggedFile* result = m_nextFile;
391   m_nextFile = nullptr;
392   while (m_row < m_model->rowCount(m_parentIdx)) {
393     QModelIndex index = m_model->index(m_row++, 0, m_parentIdx);
394     if ((m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr &&
395         (m_allSelected || m_selectModel->isSelected(index)))
396       break;
397     else
398       m_nextFile = nullptr;
399   }
400   return result;
401 }
402 
403 /**
404  * Get next item without moving iterator.
405  * @return next file
406  */
peekNext() const407 TaggedFile* SelectedTaggedFileOfDirectoryIterator::peekNext() const
408 {
409   if (!m_model)
410     return nullptr;
411   return m_nextFile;
412 }
413 
414 
415 /**
416  * Constructor.
417  *
418  * @param selectModel selection model
419  */
TaggedFileOfSelectedDirectoriesIterator(const QItemSelectionModel * selectModel)420 TaggedFileOfSelectedDirectoriesIterator::TaggedFileOfSelectedDirectoriesIterator(
421   const QItemSelectionModel* selectModel) : m_model(nullptr), m_dirIdx(0), m_row(0),
422   m_nextFile(nullptr)
423 {
424   if (selectModel &&
425       (m_model = qobject_cast<const FileProxyModel*>(selectModel->model()))
426       != nullptr) {
427     const auto indexes = selectModel->selectedRows();
428     for (const QModelIndex& index : indexes) {
429       if (m_model->isDir(index)) {
430         m_dirIndexes.append(getIndexesOfDirWithSubDirs(index));
431       }
432     }
433   }
434   next();
435 }
436 
437 /**
438  * Get indexes of directory and recursively all subdirectories.
439  * @param dirIndex index of directory
440  * @return list with dirIndex and its subdirectories.
441  */
442 QList<QPersistentModelIndex>
getIndexesOfDirWithSubDirs(const QModelIndex & dirIndex)443 TaggedFileOfSelectedDirectoriesIterator::getIndexesOfDirWithSubDirs(
444   const QModelIndex& dirIndex) {
445   QList<QPersistentModelIndex> dirs;
446   dirs.append(dirIndex);
447   for (int dirsPos = 0; dirsPos < dirs.size(); ++dirsPos) {
448     QPersistentModelIndex parentIndex(dirs.at(dirsPos));
449     for (int row = 0; row < m_model->rowCount(parentIndex); ++row) {
450       QModelIndex index(m_model->index(row, 0, parentIndex));
451       if (m_model->isDir(index)) {
452         dirs.append(index);
453       }
454     }
455   }
456   return dirs;
457 }
458 
459 /**
460  * Check if a next item exists.
461  * @return true if there is a next file
462  */
hasNext() const463 bool TaggedFileOfSelectedDirectoriesIterator::hasNext() const
464 {
465   return m_model && m_nextFile != nullptr;
466 }
467 
468 /**
469  * Advance iterator and return next item.
470  * @return next file
471  */
next()472 TaggedFile* TaggedFileOfSelectedDirectoriesIterator::next()
473 {
474   if (!m_model)
475     return nullptr;
476   TaggedFile* result = m_nextFile;
477   m_nextFile = nullptr;
478   while (!m_nextFile) {
479     if (m_dirIdx >= m_dirIndexes.size())
480       break;
481     QPersistentModelIndex parentIdx(m_dirIndexes.at(m_dirIdx));
482     while (m_row < m_model->rowCount(parentIdx)) {
483       QModelIndex index = m_model->index(m_row++, 0, parentIdx);
484       if ((m_nextFile = FileProxyModel::getTaggedFileOfIndex(index)) != nullptr)
485         break;
486     }
487     if (m_row >= m_model->rowCount(parentIdx)) {
488       ++m_dirIdx;
489       m_row = 0;
490     }
491   }
492   return result;
493 }
494 
495 /**
496  * Get next item without moving iterator.
497  * @return next file
498  */
peekNext() const499 TaggedFile* TaggedFileOfSelectedDirectoriesIterator::peekNext() const
500 {
501   if (!m_model)
502     return nullptr;
503   return m_nextFile;
504 }
505