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 }