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