File indexing completed on 2024-05-19 04:56:08
0001 /** 0002 * \file taggedfilesystemmodel.cpp 0003 * Filesystem model which additional columns. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 08-Aug-2021 0008 * 0009 * Copyright (C) 2021-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 "taggedfilesystemmodel.h" 0028 #include "coretaggedfileiconprovider.h" 0029 #include "filesystemmodel.h" 0030 #include "itaggedfilefactory.h" 0031 #include "tagconfig.h" 0032 #include "saferename.h" 0033 0034 /** Only defined for generation of translation files */ 0035 #define NAME_FOR_PO QT_TRANSLATE_NOOP("QFileSystemModel", "Name") 0036 /** Only defined for generation of translation files */ 0037 #define SIZE_FOR_PO QT_TRANSLATE_NOOP("QFileSystemModel", "Size") 0038 /** Only defined for generation of translation files */ 0039 #define TYPE_FOR_PO QT_TRANSLATE_NOOP("QFileSystemModel", "Type") 0040 /** Only defined for generation of translation files */ 0041 #define KIND_FOR_PO QT_TRANSLATE_NOOP("QFileSystemModel", "Kind") 0042 /** Only defined for generation of translation files */ 0043 #define DATE_MODIFIED_FOR_PO \ 0044 QT_TRANSLATE_NOOP("QFileSystemModel", "Date Modified") 0045 0046 QList<ITaggedFileFactory*> TaggedFileSystemModel::s_taggedFileFactories; 0047 0048 TaggedFileSystemModel::TaggedFileSystemModel( 0049 CoreTaggedFileIconProvider* iconProvider, QObject* parent) 0050 : FileSystemModel(parent), m_iconProvider(iconProvider) 0051 { 0052 setObjectName(QLatin1String("TaggedFileSystemModel")); 0053 connect(this, &QAbstractItemModel::rowsInserted, 0054 this, &TaggedFileSystemModel::updateInsertedRows); 0055 m_tagFrameColumnTypes 0056 << Frame::FT_Title << Frame::FT_Artist << Frame::FT_Album 0057 << Frame::FT_Comment << Frame::FT_Date << Frame::FT_Track 0058 << Frame::FT_Genre; 0059 } 0060 0061 TaggedFileSystemModel::~TaggedFileSystemModel() 0062 { 0063 clearTaggedFileStore(); 0064 } 0065 0066 QModelIndex TaggedFileSystemModel::sibling(int row, int column, 0067 const QModelIndex&idx) const 0068 { 0069 if (row == idx.row() && 0070 column >= NUM_FILESYSTEM_COLUMNS && 0071 column < NUM_FILESYSTEM_COLUMNS + m_tagFrameColumnTypes.size()) { 0072 return createIndex(row, column, idx.internalPointer()); 0073 } 0074 return FileSystemModel::sibling(row, column, idx); 0075 } 0076 0077 int TaggedFileSystemModel::columnCount(const QModelIndex &parent) const 0078 { 0079 return parent.column() > 0 0080 ? 0 : NUM_FILESYSTEM_COLUMNS + m_tagFrameColumnTypes.size(); 0081 } 0082 0083 /** 0084 * Get data for a given role. 0085 * @param index model index 0086 * @param role item data role 0087 * @return data for role 0088 */ 0089 QVariant TaggedFileSystemModel::data(const QModelIndex& index, int role) const 0090 { 0091 if (index.isValid()) { 0092 if (role == TaggedFileRole) { 0093 return retrieveTaggedFileVariant(index); 0094 } 0095 if (role == Qt::DecorationRole && index.column() == 0) { 0096 if (TaggedFile* taggedFile = m_taggedFiles.value(index, nullptr)) { 0097 return m_iconProvider->iconForTaggedFile(taggedFile); 0098 } 0099 } else if (role == Qt::BackgroundRole && index.column() == 0) { 0100 if (TaggedFile* taggedFile = m_taggedFiles.value(index, nullptr)) { 0101 if (QVariant color = m_iconProvider->backgroundForTaggedFile(taggedFile); 0102 !color.isNull()) 0103 return color; 0104 } 0105 } else if (role == IconIdRole && index.column() == 0) { 0106 TaggedFile* taggedFile = m_taggedFiles.value(index, nullptr); 0107 return taggedFile 0108 ? m_iconProvider->iconIdForTaggedFile(taggedFile) 0109 : QByteArray(""); 0110 } else if (role == TruncatedRole && index.column() == 0) { 0111 TaggedFile* taggedFile = m_taggedFiles.value(index, nullptr); 0112 return taggedFile && 0113 ((TagConfig::instance().markTruncations() && 0114 taggedFile->getTruncationFlags(Frame::Tag_Id3v1) != 0) || 0115 taggedFile->isMarked()); 0116 } else if (role == IsDirRole && index.column() == 0) { 0117 return isDir(index); 0118 } else if ((role == Qt::DisplayRole || role == Qt::EditRole) && 0119 index.column() >= NUM_FILESYSTEM_COLUMNS && 0120 index.column() < 0121 NUM_FILESYSTEM_COLUMNS + m_tagFrameColumnTypes.size()) { 0122 QPersistentModelIndex taggedFileIdx = index.sibling(index.row(), 0); 0123 if (auto it = m_taggedFiles.constFind(taggedFileIdx); 0124 it != m_taggedFiles.constEnd()) { 0125 if (TaggedFile* taggedFile = *it) { 0126 Frame::Type type = m_tagFrameColumnTypes.at(index.column() - 0127 NUM_FILESYSTEM_COLUMNS); 0128 if (Frame frame; taggedFile->getFrame(Frame::Tag_2, type, frame)) { 0129 QString value = frame.getValue(); 0130 if (type == Frame::FT_Track) { 0131 bool ok; 0132 int intValue = value.toInt(&ok); 0133 if (ok) { 0134 return intValue; 0135 } 0136 } 0137 return value; 0138 } 0139 } 0140 } 0141 return QVariant(); 0142 } else if (index.column() >= NUM_FILESYSTEM_COLUMNS) { 0143 return QVariant(); 0144 } 0145 } 0146 return FileSystemModel::data(index, role); 0147 } 0148 0149 /** 0150 * Set data for a given role. 0151 * @param index model index 0152 * @param value data value 0153 * @param role item data role 0154 * @return true if successful 0155 */ 0156 bool TaggedFileSystemModel::setData(const QModelIndex& index, 0157 const QVariant& value, int role) 0158 { 0159 if (index.isValid()) { 0160 if (role == TaggedFileRole) { 0161 return storeTaggedFileVariant(index, value); 0162 } 0163 if ((role == Qt::DisplayRole || role == Qt::EditRole) && 0164 index.column() >= NUM_FILESYSTEM_COLUMNS && 0165 index.column() < NUM_FILESYSTEM_COLUMNS + m_tagFrameColumnTypes.size()) { 0166 QPersistentModelIndex taggedFileIdx = index.sibling(index.row(), 0); 0167 if (auto it = m_taggedFiles.constFind(taggedFileIdx); 0168 it != m_taggedFiles.constEnd()) { 0169 if (TaggedFile* taggedFile = *it) { 0170 if (Frame frame; 0171 taggedFile->getFrame( 0172 Frame::Tag_2, 0173 m_tagFrameColumnTypes.at(index.column() - 0174 NUM_FILESYSTEM_COLUMNS), 0175 frame)) { 0176 frame.setValue(value.toString()); 0177 return taggedFile->setFrame(Frame::Tag_2, frame); 0178 } 0179 } 0180 } 0181 return false; 0182 } 0183 if (index.column() >= NUM_FILESYSTEM_COLUMNS) { 0184 return false; 0185 } 0186 } 0187 return FileSystemModel::setData(index, value, role); 0188 } 0189 0190 /** 0191 * Get data for header section. 0192 * @param section column or row 0193 * @param orientation horizontal or vertical 0194 * @param role item data role 0195 * @return header data for role 0196 */ 0197 QVariant TaggedFileSystemModel::headerData( 0198 int section, Qt::Orientation orientation, int role) const 0199 { 0200 if (orientation == Qt::Horizontal && role == Qt::DisplayRole && 0201 section >= NUM_FILESYSTEM_COLUMNS && 0202 section < NUM_FILESYSTEM_COLUMNS + m_tagFrameColumnTypes.size()) { 0203 return Frame::ExtendedType( 0204 m_tagFrameColumnTypes.at(section - NUM_FILESYSTEM_COLUMNS)) 0205 .getTranslatedName(); 0206 } 0207 return FileSystemModel::headerData(section, orientation, role); 0208 } 0209 0210 /** 0211 * Rename file or directory of @a index to @a newName. 0212 * @return true if ok 0213 */ 0214 bool TaggedFileSystemModel::rename(const QModelIndex& index, 0215 const QString& newName) 0216 { 0217 if (Utils::hasIllegalFileNameCharacters(newName)) 0218 return false; 0219 0220 return setData(index, newName); 0221 } 0222 0223 /** 0224 * Called from tagged file to notify modification state changes. 0225 * @param index model index 0226 * @param modified true if file is modified 0227 */ 0228 void TaggedFileSystemModel::notifyModificationChanged(const QModelIndex& index, 0229 bool modified) 0230 { 0231 emit fileModificationChanged(index, modified); 0232 } 0233 0234 /** 0235 * Called from tagged file to notify changes in extra model data, e.g. the 0236 * information on which the CoreTaggedFileIconProvider depends. 0237 * @param index model index 0238 */ 0239 void TaggedFileSystemModel::notifyModelDataChanged(const QModelIndex& index) 0240 { 0241 emit dataChanged(index, index); 0242 } 0243 0244 /** 0245 * Update the TaggedFile contents for rows inserted into the model. 0246 * @param parent parent model index 0247 * @param start starting row 0248 * @param end ending row 0249 */ 0250 void TaggedFileSystemModel::updateInsertedRows(const QModelIndex& parent, 0251 int start, int end) { 0252 const QAbstractItemModel* model = parent.model(); 0253 if (!model) 0254 return; 0255 for (int row = start; row <= end; ++row) { 0256 QModelIndex index(model->index(row, 0, parent)); 0257 initTaggedFileData(index); 0258 } 0259 } 0260 0261 /** 0262 * Reset internal data of the model. 0263 * Is called from endResetModel(). 0264 */ 0265 void TaggedFileSystemModel::resetInternalData() 0266 { 0267 FileSystemModel::resetInternalData(); 0268 clearTaggedFileStore(); 0269 } 0270 0271 /** 0272 * Retrieve tagged file for an index. 0273 * @param index model index 0274 * @return QVariant with tagged file, invalid QVariant if not found. 0275 */ 0276 QVariant TaggedFileSystemModel::retrieveTaggedFileVariant( 0277 const QPersistentModelIndex& index) const { 0278 if (m_taggedFiles.contains(index)) 0279 return QVariant::fromValue(m_taggedFiles.value(index)); 0280 return QVariant(); 0281 } 0282 0283 /** 0284 * Store tagged file from variant with index. 0285 * @param index model index 0286 * @param value QVariant containing tagged file 0287 * @return true if index and value valid 0288 */ 0289 bool TaggedFileSystemModel::storeTaggedFileVariant( 0290 const QPersistentModelIndex& index, const QVariant& value) { 0291 if (index.isValid()) { 0292 if (value.isValid()) { 0293 if (value.canConvert<TaggedFile*>()) { 0294 TaggedFile* oldItem = m_taggedFiles.value(index, nullptr); 0295 delete oldItem; 0296 m_taggedFiles.insert(index, value.value<TaggedFile*>()); 0297 return true; 0298 } 0299 } else { 0300 if (TaggedFile* oldFile = m_taggedFiles.value(index, nullptr)) { 0301 m_taggedFiles.remove(index); 0302 delete oldFile; 0303 } 0304 } 0305 } 0306 return false; 0307 } 0308 0309 /** 0310 * Clear store with tagged files. 0311 */ 0312 void TaggedFileSystemModel::clearTaggedFileStore() { 0313 qDeleteAll(m_taggedFiles); 0314 m_taggedFiles.clear(); 0315 } 0316 0317 /** 0318 * Initialize tagged file for model index. 0319 * @param index model index 0320 */ 0321 void TaggedFileSystemModel::initTaggedFileData(const QModelIndex& index) { 0322 QVariant dat = data(index, TaggedFileRole); 0323 if (dat.isValid() || isDir(index)) 0324 return; 0325 0326 dat.setValue(createTaggedFile(fileName(index), index)); 0327 setData(index, dat, TaggedFileRole); 0328 } 0329 0330 0331 /** 0332 * Get tagged file data of model index. 0333 * 0334 * @param index model index 0335 * @param taggedFile a TaggedFile pointer is returned here 0336 * 0337 * @return true if index has a tagged file, *taggedFile is set to the pointer. 0338 */ 0339 bool TaggedFileSystemModel::getTaggedFileOfIndex(const QModelIndex& index, 0340 TaggedFile** taggedFile) { 0341 if (!(index.isValid() && index.model() != nullptr)) 0342 return false; 0343 QVariant data(index.model()->data(index, TaggedFileRole)); 0344 if (!data.canConvert<TaggedFile*>()) 0345 return false; 0346 *taggedFile = data.value<TaggedFile*>(); 0347 return true; 0348 } 0349 0350 /** 0351 * Get tagged file of model index. 0352 * 0353 * @param index model index 0354 * 0355 * @return tagged file, 0 is returned if the index does not contain a 0356 * TaggedFile or if has a TaggedFile which is null. 0357 */ 0358 TaggedFile* TaggedFileSystemModel::getTaggedFileOfIndex( 0359 const QModelIndex& index) { 0360 if (!(index.isValid() && index.model() != nullptr)) 0361 return nullptr; 0362 QVariant data(index.model()->data(index, TaggedFileRole)); 0363 if (!data.canConvert<TaggedFile*>()) 0364 return nullptr; 0365 return data.value<TaggedFile*>(); 0366 } 0367 0368 /** 0369 * Create a tagged file with a given feature. 0370 * 0371 * @param feature tagged file feature 0372 * @param fileName filename 0373 * @param idx model index 0374 * 0375 * @return tagged file, 0 if feature not found or type not supported. 0376 */ 0377 TaggedFile* TaggedFileSystemModel::createTaggedFile( 0378 TaggedFile::Feature feature, 0379 const QString& fileName, 0380 const QPersistentModelIndex& idx) { 0381 TaggedFile* taggedFile = nullptr; 0382 const auto factories = s_taggedFileFactories; 0383 for (ITaggedFileFactory* factory : factories) { 0384 const auto keys = factory->taggedFileKeys(); 0385 for (const QString& key : keys) { 0386 if ((factory->taggedFileFeatures(key) & feature) != 0 && 0387 (taggedFile = factory->createTaggedFile(key, fileName, idx, 0388 feature)) 0389 != nullptr) { 0390 return taggedFile; 0391 } 0392 } 0393 } 0394 return nullptr; 0395 } 0396 0397 /** 0398 * Create a tagged file. 0399 * 0400 * @param fileName filename 0401 * @param idx model index 0402 * 0403 * @return tagged file, 0 if not found or type not supported. 0404 */ 0405 TaggedFile* TaggedFileSystemModel::createTaggedFile( 0406 const QString& fileName, 0407 const QPersistentModelIndex& idx) { 0408 TaggedFile* taggedFile = nullptr; 0409 const auto factories = s_taggedFileFactories; 0410 for (ITaggedFileFactory* factory : factories) { 0411 const auto keys = factory->taggedFileKeys(); 0412 for (const QString& key : keys) { 0413 taggedFile = factory->createTaggedFile(key, fileName, idx); 0414 if (taggedFile) { 0415 return taggedFile; 0416 } 0417 } 0418 } 0419 return nullptr; 0420 }