File indexing completed on 2024-05-19 04:56:02
0001 /** 0002 * \file fileproxymodel.cpp 0003 * Proxy for filesystem model which filters files. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 22-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 "fileproxymodel.h" 0028 #include <QTimer> 0029 #include <QRegularExpression> 0030 #include "taggedfilesystemmodel.h" 0031 #include "itaggedfilefactory.h" 0032 #include "config.h" 0033 0034 namespace { 0035 0036 QHash<int,QByteArray> getRoleHash() 0037 { 0038 QHash<int, QByteArray> roles; 0039 roles[FileSystemModel::FileNameRole] = "fileName"; 0040 roles[FileSystemModel::FilePathRole] = "filePath"; 0041 roles[TaggedFileSystemModel::IconIdRole] = "iconId"; 0042 roles[TaggedFileSystemModel::TruncatedRole] = "truncated"; 0043 roles[TaggedFileSystemModel::IsDirRole] = "isDir"; 0044 roles[Qt::CheckStateRole] = "checkState"; 0045 return roles; 0046 } 0047 0048 } 0049 0050 /** 0051 * Constructor. 0052 * 0053 * @param parent parent object 0054 */ 0055 FileProxyModel::FileProxyModel(QObject* parent) 0056 : QSortFilterProxyModel(parent), 0057 m_fsModel(nullptr), 0058 m_loadTimer(new QTimer(this)), m_sortTimer(new QTimer(this)), 0059 m_numModifiedFiles(0), m_isLoading(false) 0060 { 0061 setObjectName(QLatin1String("FileProxyModel")); 0062 m_loadTimer->setSingleShot(true); 0063 m_loadTimer->setInterval(1000); 0064 connect(m_loadTimer, &QTimer::timeout, this, &FileProxyModel::onDirectoryLoaded); 0065 m_sortTimer->setSingleShot(true); 0066 m_sortTimer->setInterval(100); 0067 connect(m_sortTimer, &QTimer::timeout, this, &FileProxyModel::emitSortingFinished); 0068 } 0069 0070 /** 0071 * Destructor. 0072 */ 0073 FileProxyModel::~FileProxyModel() 0074 { 0075 } 0076 0077 /** 0078 * Map role identifiers to role property names in scripting languages. 0079 * @return hash mapping role identifiers to names. 0080 */ 0081 QHash<int,QByteArray> FileProxyModel::roleNames() const 0082 { 0083 static QHash<int, QByteArray> roles = getRoleHash(); 0084 return roles; 0085 } 0086 0087 /** 0088 * Get file information of model index. 0089 * @return file information 0090 */ 0091 QFileInfo FileProxyModel::fileInfo(const QModelIndex& index) const 0092 { 0093 if (m_fsModel) { 0094 QModelIndex sourceIndex(mapToSource(index)); 0095 return m_fsModel->fileInfo(sourceIndex); 0096 } 0097 return QFileInfo(); 0098 } 0099 0100 /** 0101 * Get file path of model index. 0102 * @return path to file or directory 0103 */ 0104 QString FileProxyModel::filePath(const QModelIndex& index) const 0105 { 0106 if (m_fsModel) { 0107 QModelIndex sourceIndex(mapToSource(index)); 0108 return m_fsModel->filePath(sourceIndex); 0109 } 0110 return QString(); 0111 } 0112 0113 /** 0114 * Get file name of model index. 0115 * @return name of file or directory 0116 */ 0117 QString FileProxyModel::fileName(const QModelIndex& index) const 0118 { 0119 if (m_fsModel) { 0120 QModelIndex sourceIndex(mapToSource(index)); 0121 return m_fsModel->fileName(sourceIndex); 0122 } 0123 return QString(); 0124 } 0125 0126 /** 0127 * Check if model index represents directory. 0128 * @return true if directory 0129 */ 0130 bool FileProxyModel::isDir(const QModelIndex& index) const 0131 { 0132 if (m_fsModel) { 0133 QModelIndex sourceIndex(mapToSource(index)); 0134 return m_fsModel->isDir(sourceIndex); 0135 } 0136 return false; 0137 } 0138 0139 /** 0140 * Delete file of index. 0141 * @return true if ok 0142 */ 0143 bool FileProxyModel::remove(const QModelIndex& index) const 0144 { 0145 if (m_fsModel) { 0146 QModelIndex sourceIndex(mapToSource(index)); 0147 return m_fsModel->remove(sourceIndex); 0148 } 0149 return false; 0150 } 0151 0152 /** 0153 * Delete directory of index. 0154 * @return true if ok 0155 */ 0156 bool FileProxyModel::rmdir(const QModelIndex& index) const 0157 { 0158 if (m_fsModel) { 0159 QModelIndex sourceIndex(mapToSource(index)); 0160 return m_fsModel->rmdir(sourceIndex); 0161 } 0162 return false; 0163 } 0164 0165 /** 0166 * Create a directory with @a name in the @a parent model index. 0167 * @return index of created directory. 0168 */ 0169 QModelIndex FileProxyModel::mkdir(const QModelIndex& parent, const QString& name) const 0170 { 0171 if (m_fsModel) { 0172 QModelIndex sourceIndex(mapToSource(parent)); 0173 return mapFromSource(m_fsModel->mkdir(sourceIndex, name)); 0174 } 0175 return QModelIndex(); 0176 } 0177 0178 /** 0179 * Rename file or directory of @a index to @a newName. 0180 * @return true if ok 0181 */ 0182 bool FileProxyModel::rename(const QModelIndex& index, const QString& newName) 0183 { 0184 if (m_fsModel) { 0185 QModelIndex sourceIndex(mapToSource(index)); 0186 return m_fsModel->rename(sourceIndex, newName); 0187 } 0188 return false; 0189 } 0190 0191 /** 0192 * Get index for given path and column. 0193 * @param path path to file or directory 0194 * @param column model column 0195 * @return model index, invalid if not found. 0196 */ 0197 QModelIndex FileProxyModel::index(const QString& path, int column) const 0198 { 0199 if (m_fsModel) { 0200 if (QModelIndex sourceIndex = m_fsModel->index(path, column); 0201 sourceIndex.isValid()) { 0202 return mapFromSource(sourceIndex); 0203 } 0204 } 0205 return QModelIndex(); 0206 } 0207 0208 /** 0209 * Check if row should be included in model. 0210 * 0211 * @param srcRow source row 0212 * @param srcParent source parent 0213 * 0214 * @return true to include row. 0215 */ 0216 bool FileProxyModel::filterAcceptsRow( 0217 int srcRow, const QModelIndex& srcParent) const 0218 { 0219 if (QAbstractItemModel* srcModel = sourceModel()) { 0220 QModelIndex srcIndex(srcModel->index(srcRow, 0, srcParent)); 0221 if (!m_filteredOut.isEmpty()) { 0222 if (m_filteredOut.contains(srcIndex)) 0223 return false; 0224 } 0225 QString item(srcIndex.data().toString()); 0226 if (item == QLatin1String(".") || item == QLatin1String("..")) 0227 return false; 0228 if (!m_fsModel) 0229 return true; 0230 if (m_fsModel->isDir(srcIndex)) 0231 return passesExcludeFolderFilters(m_fsModel->filePath(srcIndex)); 0232 if (m_extensions.isEmpty()) 0233 return true; 0234 for (auto it = m_extensions.constBegin(); it != m_extensions.constEnd(); ++it) { 0235 if (item.endsWith(*it, Qt::CaseInsensitive)) 0236 return true; 0237 } 0238 } 0239 return false; 0240 } 0241 0242 /** 0243 * Get item flags. 0244 * @param index index of item 0245 * @return default flags plus drag enabled depending on 0246 * setExclusiveDraggableIndex(). 0247 */ 0248 Qt::ItemFlags FileProxyModel::flags(const QModelIndex& index) const 0249 { 0250 Qt::ItemFlags itemFlags = QSortFilterProxyModel::flags(index); 0251 0252 if (index.isValid()) { 0253 if (!m_exclusiveDraggableIndex.isValid() || 0254 index == m_exclusiveDraggableIndex) { 0255 itemFlags |= Qt::ItemIsDragEnabled; 0256 } else { 0257 itemFlags &= ~Qt::ItemIsDragEnabled; 0258 } 0259 } 0260 if (index.column() < TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS) { 0261 // Prevent inplace editing (i.e. renaming) of files and directories. 0262 itemFlags &= ~Qt::ItemIsEditable; 0263 } else { 0264 itemFlags |= Qt::ItemIsEditable; 0265 } 0266 0267 return itemFlags; 0268 } 0269 0270 /** 0271 * Set source model. 0272 * @param sourceModel source model, must be TaggedFileSystemModel 0273 */ 0274 void FileProxyModel::setSourceModel(QAbstractItemModel* sourceModel) 0275 { 0276 auto fsModel = qobject_cast<TaggedFileSystemModel*>(sourceModel); 0277 Q_ASSERT_X(fsModel != nullptr , "setSourceModel", 0278 "sourceModel is not TaggedFileSystemModel"); 0279 if (fsModel != m_fsModel) { 0280 if (m_fsModel) { 0281 m_isLoading = false; 0282 disconnect(m_fsModel, &FileSystemModel::rootPathChanged, 0283 this, &FileProxyModel::onStartLoading); 0284 disconnect(m_fsModel, &FileSystemModel::directoryLoaded, 0285 this, &FileProxyModel::onDirectoryLoaded); 0286 disconnect(m_fsModel, &TaggedFileSystemModel::fileModificationChanged, 0287 this, &FileProxyModel::onFileModificationChanged); 0288 } 0289 m_fsModel = fsModel; 0290 if (m_fsModel) { 0291 connect(m_fsModel, &FileSystemModel::rootPathChanged, 0292 this, &FileProxyModel::onStartLoading); 0293 connect(m_fsModel, &FileSystemModel::directoryLoaded, 0294 this, &FileProxyModel::onDirectoryLoaded); 0295 connect(m_fsModel, &TaggedFileSystemModel::fileModificationChanged, 0296 this, &FileProxyModel::onFileModificationChanged); 0297 } 0298 } 0299 QSortFilterProxyModel::setSourceModel(sourceModel); 0300 } 0301 0302 /** 0303 * Called when directoryLoaded() is emitted. 0304 */ 0305 void FileProxyModel::onDirectoryLoaded() 0306 { 0307 m_loadTimer->stop(); 0308 m_sortTimer->start(); 0309 } 0310 0311 /** 0312 * Emit sortingFinished(). 0313 */ 0314 void FileProxyModel::emitSortingFinished() 0315 { 0316 m_isLoading = false; 0317 emit sortingFinished(); 0318 } 0319 0320 /** 0321 * Count items in model. 0322 * @param rootIndex index of root item 0323 * @param folderCount the folder count is returned here 0324 * @param fileCount the file count is returned here 0325 */ 0326 void FileProxyModel::countItems(const QModelIndex& rootIndex, 0327 int& folderCount, int& fileCount) const 0328 { 0329 folderCount = 0; 0330 fileCount = 0; 0331 QModelIndexList todo; 0332 todo.append(rootIndex); 0333 while (!todo.isEmpty()) { 0334 QModelIndex parent = todo.takeFirst(); 0335 for (int row = 0, numRows = rowCount(parent); row < numRows; ++row) { 0336 if (QModelIndex idx = index(row, 0, parent); !hasChildren(idx)) { 0337 ++fileCount; 0338 } else { 0339 ++folderCount; 0340 todo.append(idx); 0341 } 0342 } 0343 } 0344 } 0345 0346 /** 0347 * Called when loading the directory starts. 0348 */ 0349 void FileProxyModel::onStartLoading() 0350 { 0351 m_isLoading = true; 0352 // Last resort timeout for the case that directoryLoaded() would not be 0353 // fired and for empty directories with Qt < 4.7 0354 m_loadTimer->start(); 0355 } 0356 0357 /** 0358 * Check if more data is available. 0359 * @param parent parent index of items to fetch 0360 * @return true if more data available. 0361 */ 0362 bool FileProxyModel::canFetchMore(const QModelIndex& parent) const 0363 { 0364 if (QString path = filePath(parent); 0365 !passesIncludeFolderFilters(path) || !passesExcludeFolderFilters(path)) 0366 return false; 0367 0368 return QSortFilterProxyModel::canFetchMore(parent); 0369 } 0370 0371 /** 0372 * Fetches any available data. 0373 * @param parent parent index of items to fetch 0374 */ 0375 void FileProxyModel::fetchMore(const QModelIndex& parent) 0376 { 0377 onStartLoading(); 0378 QSortFilterProxyModel::fetchMore(parent); 0379 } 0380 0381 /** 0382 * Sort model. 0383 * 0384 * This method will directly call FileSystemModel::sort() on the 0385 * sourceModel() to take advantage of that specialized behavior. This 0386 * will change the order in the source model. 0387 * 0388 * @param column column to sort 0389 * @param order ascending or descending order 0390 */ 0391 void FileProxyModel::sort(int column, Qt::SortOrder order) 0392 { 0393 if (QAbstractItemModel* srcModel = nullptr; 0394 rowCount() > 0 && (srcModel = sourceModel()) != nullptr) { 0395 if (column < TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS) { 0396 if (sortColumn() >= TaggedFileSystemModel::NUM_FILESYSTEM_COLUMNS) { 0397 // restore the source model order 0398 QSortFilterProxyModel::sort(-1, order); 0399 } 0400 srcModel->sort(column, order); 0401 } else { 0402 QSortFilterProxyModel::sort(column, order); 0403 } 0404 } 0405 } 0406 0407 /** 0408 * Sets the name filters to apply against the existing files. 0409 * @param filters list of strings containing wildcards like "*.mp3" 0410 */ 0411 void FileProxyModel::setNameFilters(const QStringList& filters) 0412 { 0413 QRegularExpression wildcardRe(QLatin1String("\\.\\w+")); 0414 QSet<QString> exts; 0415 for (const QString& filter : filters) { 0416 auto it = wildcardRe.globalMatch(filter); 0417 while (it.hasNext()) { 0418 auto match = it.next(); 0419 int pos = match.capturedStart(); 0420 int len = match.capturedLength(); 0421 exts.insert(filter.mid(pos, len).toLower()); 0422 } 0423 } 0424 QStringList oldExtensions(m_extensions); 0425 #if QT_VERSION >= 0x050e00 0426 m_extensions = QStringList(exts.constBegin(), exts.constEnd()); 0427 #else 0428 m_extensions = exts.toList(); 0429 #endif 0430 if (m_extensions != oldExtensions) { 0431 invalidateFilter(); 0432 } 0433 } 0434 0435 /** 0436 * Filter out a model index. 0437 * @param index source model index which has to be filtered out 0438 */ 0439 void FileProxyModel::filterOutIndex(const QPersistentModelIndex& index) 0440 { 0441 m_filteredOut.insert(index); 0442 } 0443 0444 /** 0445 * Reset internal data of the model. 0446 * Is called from endResetModel(). 0447 */ 0448 void FileProxyModel::resetInternalData() 0449 { 0450 QSortFilterProxyModel::resetInternalData(); 0451 m_filteredOut.clear(); 0452 m_loadTimer->stop(); 0453 m_sortTimer->stop(); 0454 m_numModifiedFiles = 0; 0455 m_isLoading = false; 0456 } 0457 0458 /** 0459 * Stop filtering out indexes. 0460 */ 0461 void FileProxyModel::disableFilteringOutIndexes() 0462 { 0463 m_filteredOut.clear(); 0464 invalidateFilter(); 0465 } 0466 0467 /** 0468 * Check if index filter is active. 0469 * @return true if indexes are filtered out 0470 */ 0471 bool FileProxyModel::isFilteringOutIndexes() const 0472 { 0473 return !m_filteredOut.isEmpty(); 0474 } 0475 0476 /** 0477 * Make filter changes active after adding indexes to be filtered out. 0478 */ 0479 void FileProxyModel::applyFilteringOutIndexes() 0480 { 0481 invalidateFilter(); 0482 } 0483 0484 /** 0485 * Set filters for included and excluded folders. 0486 * @param includeFolders wildcard expressions for folders to be included 0487 * @param excludeFolders wildcard expressions for folders to be excluded 0488 */ 0489 void FileProxyModel::setFolderFilters(const QStringList& includeFolders, 0490 const QStringList& excludeFolders) 0491 { 0492 QList<QRegularExpression> oldIncludeFolderFilters, oldExcludeFolderFilters; 0493 m_includeFolderFilters.swap(oldIncludeFolderFilters); 0494 m_excludeFolderFilters.swap(oldExcludeFolderFilters); 0495 for (QString filter : includeFolders) { 0496 filter.replace(QLatin1Char('\\'), QLatin1Char('/')); 0497 #if QT_VERSION >= 0x050f00 0498 filter = QRegularExpression::wildcardToRegularExpression(filter); 0499 #else 0500 filter = FileSystemModel::wildcardToRegularExpression(filter); 0501 #endif 0502 m_includeFolderFilters.append( 0503 QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption)); 0504 } 0505 0506 for (QString filter : excludeFolders) { 0507 filter.replace(QLatin1Char('\\'), QLatin1Char('/')); 0508 #if QT_VERSION >= 0x050f00 0509 filter = QRegularExpression::wildcardToRegularExpression(filter); 0510 #else 0511 filter = FileSystemModel::wildcardToRegularExpression(filter); 0512 #endif 0513 m_excludeFolderFilters.append( 0514 QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption)); 0515 } 0516 0517 if (m_includeFolderFilters != oldIncludeFolderFilters || 0518 m_excludeFolderFilters != oldExcludeFolderFilters) { 0519 invalidateFilter(); 0520 } 0521 } 0522 0523 /** 0524 * Check if a directory path passes the include folder filters. 0525 * @param dirPath absolute path to directory 0526 * @return true if path passes filters. 0527 */ 0528 bool FileProxyModel::passesIncludeFolderFilters(const QString& dirPath) const 0529 { 0530 if (!m_includeFolderFilters.isEmpty()) { 0531 bool included = false; 0532 for (auto it = m_includeFolderFilters.constBegin(); 0533 it != m_includeFolderFilters.constEnd(); 0534 ++it) { 0535 if (it->match(dirPath).hasMatch()) { 0536 included = true; 0537 break; 0538 } 0539 } 0540 if (!included) { 0541 return false; 0542 } 0543 } 0544 0545 return true; 0546 } 0547 0548 /** 0549 * Check if a directory path passes the include folder filters. 0550 * @param dirPath absolute path to directory 0551 * @return true if path passes filters. 0552 */ 0553 bool FileProxyModel::passesExcludeFolderFilters(const QString& dirPath) const 0554 { 0555 if (!m_excludeFolderFilters.isEmpty()) { 0556 for (auto it = m_excludeFolderFilters.constBegin(); 0557 it != m_excludeFolderFilters.constEnd(); 0558 ++it) { 0559 if (it->match(dirPath).hasMatch()) { 0560 return false; 0561 } 0562 } 0563 } 0564 0565 return true; 0566 } 0567 0568 /** 0569 * Get tagged file of model index. 0570 * 0571 * @param index model index 0572 * 0573 * @return tagged file, 0 is returned if the index does not contain a 0574 * TaggedFile or if has a TaggedFile which is null. 0575 */ 0576 TaggedFile* FileProxyModel::getTaggedFileOfIndex(const QModelIndex& index) { 0577 return TaggedFileSystemModel::getTaggedFileOfIndex(index); 0578 } 0579 0580 /** 0581 * Get directory path if model index is of directory. 0582 * 0583 * @param index model index 0584 * 0585 * @return directory path, null if not directory 0586 */ 0587 QString FileProxyModel::getPathIfIndexOfDir(const QModelIndex& index) { 0588 const auto model = 0589 qobject_cast<const FileProxyModel*>(index.model()); 0590 if (!model || !model->isDir(index)) 0591 return QString(); 0592 0593 return model->filePath(index); 0594 } 0595 0596 /** 0597 * Read tagged file with ID3v2.4.0. 0598 * 0599 * @param taggedFile tagged file 0600 * 0601 * @return tagged file (can be newly created tagged file). 0602 */ 0603 TaggedFile* FileProxyModel::readWithId3V24(TaggedFile* taggedFile) 0604 { 0605 const QPersistentModelIndex& index = taggedFile->getIndex(); 0606 if (TaggedFile* tagLibFile = TaggedFileSystemModel::createTaggedFile( 0607 TaggedFile::TF_ID3v24, taggedFile->getFilename(), index)) { 0608 if (index.isValid()) { 0609 QVariant data; 0610 data.setValue(tagLibFile); 0611 // setData() will not invalidate the model, so this should be safe. 0612 if (auto setDataModel = const_cast<QAbstractItemModel*>( 0613 index.model())) { 0614 setDataModel->setData(index, data, TaggedFileSystemModel::TaggedFileRole); 0615 } 0616 } 0617 taggedFile = tagLibFile; 0618 taggedFile->readTags(false); 0619 } 0620 return taggedFile; 0621 } 0622 /** 0623 * Read tagged file with ID3v2.3.0. 0624 * 0625 * @param taggedFile tagged file 0626 * 0627 * @return tagged file (can be newly created tagged file). 0628 */ 0629 TaggedFile* FileProxyModel::readWithId3V23(TaggedFile* taggedFile) 0630 { 0631 const QPersistentModelIndex& index = taggedFile->getIndex(); 0632 if (TaggedFile* id3libFile = TaggedFileSystemModel::createTaggedFile( 0633 TaggedFile::TF_ID3v23, taggedFile->getFilename(), index)) { 0634 if (index.isValid()) { 0635 QVariant data; 0636 data.setValue(id3libFile); 0637 // setData() will not invalidate the model, so this should be safe. 0638 if (auto setDataModel = const_cast<QAbstractItemModel*>(index.model())) { 0639 setDataModel->setData(index, data, TaggedFileSystemModel::TaggedFileRole); 0640 } 0641 } 0642 taggedFile = id3libFile; 0643 taggedFile->readTags(false); 0644 } 0645 return taggedFile; 0646 } 0647 0648 /** 0649 * Read file with ID3v2.4 if it has an ID3v2.4 or ID3v2.2 tag. 0650 * ID3v2.2 files are also read with ID3v2.4 because id3lib corrupts 0651 * images in ID3v2.2 tags. 0652 * 0653 * @param taggedFile tagged file 0654 * 0655 * @return tagged file (can be new TagLibFile). 0656 */ 0657 TaggedFile* FileProxyModel::readWithId3V24IfId3V24(TaggedFile* taggedFile) 0658 { 0659 if (taggedFile && 0660 (taggedFile->taggedFileFeatures() & 0661 (TaggedFile::TF_ID3v23 | TaggedFile::TF_ID3v24)) == 0662 TaggedFile::TF_ID3v23 && 0663 !taggedFile->isChanged() && 0664 taggedFile->isTagInformationRead() && taggedFile->hasTag(Frame::Tag_Id3v2)) { 0665 if (QString id3v2Version = taggedFile->getTagFormat(Frame::Tag_Id3v2); 0666 id3v2Version.isNull() || id3v2Version == QLatin1String("ID3v2.2.0")) { 0667 taggedFile = readWithId3V24(taggedFile); 0668 } 0669 } 0670 return taggedFile; 0671 } 0672 0673 /** 0674 * Read tagged file with Ogg FLAC. 0675 * 0676 * @param taggedFile tagged file 0677 * 0678 * @return tagged file (can be newly created tagged file). 0679 */ 0680 TaggedFile* FileProxyModel::readWithOggFlac(TaggedFile* taggedFile) 0681 { 0682 const QPersistentModelIndex& index = taggedFile->getIndex(); 0683 if (TaggedFile* tagLibFile = TaggedFileSystemModel::createTaggedFile( 0684 TaggedFile::TF_OggFlac, taggedFile->getFilename(), index)) { 0685 if (index.isValid()) { 0686 QVariant data; 0687 data.setValue(tagLibFile); 0688 // setData() will not invalidate the model, so this should be safe. 0689 if (auto setDataModel = const_cast<QAbstractItemModel*>(index.model())) { 0690 setDataModel->setData(index, data, TaggedFileSystemModel::TaggedFileRole); 0691 } 0692 } 0693 taggedFile = tagLibFile; 0694 taggedFile->readTags(false); 0695 } 0696 return taggedFile; 0697 } 0698 0699 /** 0700 * Try to read Ogg file with invalid tag detail info as an Ogg FLAC file. 0701 * 0702 * @param taggedFile tagged file 0703 * 0704 * @return tagged file (can be new TagLibFile). 0705 */ 0706 TaggedFile* FileProxyModel::readWithOggFlacIfInvalidOgg(TaggedFile* taggedFile) 0707 { 0708 if (taggedFile && 0709 (taggedFile->taggedFileFeatures() & 0710 (TaggedFile::TF_OggPictures | TaggedFile::TF_OggFlac)) == 0711 TaggedFile::TF_OggPictures && 0712 !taggedFile->isChanged() && 0713 taggedFile->isTagInformationRead()) { 0714 TaggedFile::DetailInfo info; 0715 taggedFile->getDetailInfo(info); 0716 if (!info.valid) { 0717 taggedFile = readWithOggFlac(taggedFile); 0718 } 0719 } 0720 return taggedFile; 0721 } 0722 0723 /** 0724 * Call readTags() on tagged file. 0725 * Reread file with other metadata plugin if it is not supported by current 0726 * plugin. 0727 * 0728 * @param taggedFile tagged file 0729 * 0730 * @return tagged file (can be new TaggedFile). 0731 */ 0732 TaggedFile* FileProxyModel::readTagsFromTaggedFile(TaggedFile* taggedFile) 0733 { 0734 taggedFile->readTags(false); 0735 taggedFile = readWithId3V24IfId3V24(taggedFile); 0736 taggedFile = readWithOggFlacIfInvalidOgg(taggedFile); 0737 return taggedFile; 0738 } 0739 0740 /** 0741 * Called when the source model emits fileModificationChanged(). 0742 * @param srcIndex source model index 0743 * @param modified true if file is modified 0744 */ 0745 void FileProxyModel::onFileModificationChanged(const QModelIndex& srcIndex, 0746 bool modified) 0747 { 0748 auto index = mapFromSource(srcIndex); 0749 emit fileModificationChanged(index, modified); 0750 emit dataChanged(index, index); 0751 bool lastIsModified = isModified(); 0752 if (modified) { 0753 ++m_numModifiedFiles; 0754 } else if (m_numModifiedFiles > 0) { 0755 --m_numModifiedFiles; 0756 } 0757 if (bool newIsModified = isModified(); newIsModified != lastIsModified) { 0758 emit modifiedChanged(newIsModified); 0759 } 0760 } 0761 0762 /** 0763 * Get icon provider. 0764 * @return icon provider. 0765 */ 0766 CoreTaggedFileIconProvider* FileProxyModel::getIconProvider() const 0767 { 0768 if (m_fsModel) { 0769 return m_fsModel->getIconProvider(); 0770 } 0771 return nullptr; 0772 } 0773 0774 /** 0775 * Access to tagged file factories. 0776 * @return reference to tagged file factories. 0777 */ 0778 QList<ITaggedFileFactory*>& FileProxyModel::taggedFileFactories() 0779 { 0780 return TaggedFileSystemModel::taggedFileFactories(); 0781 } 0782 0783 /** 0784 * Create name-file pattern pairs for all supported types. 0785 * The order is the same as in createFilterString(). 0786 * 0787 * @return pairs containing name, pattern, e.g. ("MP3", "*.mp3"), ..., 0788 * ("All Files", "*"). 0789 */ 0790 QList<QPair<QString, QString> > FileProxyModel::createNameFilters() 0791 { 0792 QStringList extensions; 0793 const auto factories = taggedFileFactories(); 0794 for (ITaggedFileFactory* factory : factories) { 0795 const auto keys = factory->taggedFileKeys(); 0796 for (const QString& key : keys) { 0797 extensions.append(factory->supportedFileExtensions(key)); 0798 } 0799 } 0800 // remove duplicates 0801 extensions.sort(); 0802 QString lastExt(QLatin1String("")); 0803 for (auto it = extensions.begin(); it != extensions.end();) { 0804 if (*it == lastExt) { 0805 it = extensions.erase(it); 0806 } else { 0807 lastExt = *it; 0808 ++it; 0809 } 0810 } 0811 0812 QString allPatterns; 0813 QList<QPair<QString, QString> > nameFilters; 0814 for (auto it = extensions.constBegin(); 0815 it != extensions.constEnd(); 0816 ++it) { 0817 QString text = it->mid(1).toUpper(); 0818 QString pattern = QLatin1Char('*') + *it; 0819 if (!allPatterns.isEmpty()) { 0820 allPatterns += QLatin1Char(' '); 0821 } 0822 allPatterns += pattern; 0823 nameFilters.append(qMakePair(text, pattern)); 0824 } 0825 if (!allPatterns.isEmpty()) { 0826 // Add extensions for playlists. 0827 allPatterns += QLatin1String(" *.m3u *.pls *.xspf"); 0828 nameFilters.prepend(qMakePair(tr("All Supported Files"), allPatterns)); 0829 } 0830 nameFilters.append(qMakePair(tr("All Files"), QString(QLatin1Char('*')))); 0831 return nameFilters; 0832 }