File indexing completed on 2024-05-19 04:56:09
0001 /** 0002 * \file trackdatamodel.cpp 0003 * Model for table with track data. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 15 May 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 "trackdatamodel.h" 0028 #include "frametablemodel.h" 0029 #include "coretaggedfileiconprovider.h" 0030 0031 /** 0032 * Constructor. 0033 * @param colorProvider colorProvider 0034 * @param parent parent widget 0035 */ 0036 TrackDataModel::TrackDataModel(CoreTaggedFileIconProvider* colorProvider, 0037 QObject* parent) 0038 : QAbstractTableModel(parent), 0039 m_colorProvider(colorProvider), m_maxDiff(0), m_diffCheckEnabled(false) 0040 { 0041 setObjectName(QLatin1String("TrackDataModel")); 0042 } 0043 0044 /** 0045 * Get item flags for index. 0046 * @param index model index 0047 * @return item flags 0048 */ 0049 Qt::ItemFlags TrackDataModel::flags(const QModelIndex& index) const 0050 { 0051 Qt::ItemFlags theFlags = QAbstractTableModel::flags(index); 0052 if (index.isValid()) { 0053 theFlags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled; 0054 if (static_cast<int>(m_frameTypes.at(index.column()).getType()) < 0055 FT_FirstTrackProperty) { 0056 theFlags |= Qt::ItemIsEditable; 0057 } 0058 if (index.column() == 0) { 0059 theFlags |= Qt::ItemIsUserCheckable; 0060 } 0061 } 0062 return theFlags; 0063 } 0064 0065 /** 0066 * Get data for a given role. 0067 * @param index model index 0068 * @param role item data role 0069 * @return data for role 0070 */ 0071 QVariant TrackDataModel::data(const QModelIndex& index, int role) const 0072 { 0073 if (!index.isValid() || 0074 index.row() < 0 || 0075 index.row() >= m_trackDataVector.size() || 0076 index.column() < 0 || 0077 index.column() >= m_frameTypes.size()) 0078 return QVariant(); 0079 0080 if (role == Qt::DisplayRole || role == Qt::EditRole) { 0081 const ImportTrackData& trackData = m_trackDataVector.at(index.row()); 0082 Frame::ExtendedType type = m_frameTypes.at(index.column()); 0083 if (auto typeOrProperty = static_cast<int>(type.getType()); 0084 typeOrProperty < FT_FirstTrackProperty) { 0085 if (QString value(trackData.getValue(type)); !value.isNull()) 0086 return value; 0087 } else { 0088 switch (typeOrProperty) { 0089 case FT_FilePath: 0090 return trackData.getAbsFilename(); 0091 case FT_FileName: 0092 return trackData.getFilename(); 0093 case FT_Duration: 0094 if (int duration = trackData.getFileDuration()) { 0095 return TaggedFile::formatTime(duration); 0096 } 0097 break; 0098 case FT_ImportDuration: 0099 if (int duration = trackData.getImportDuration()) { 0100 return TaggedFile::formatTime(duration); 0101 } 0102 break; 0103 default: 0104 ; 0105 } 0106 } 0107 } else if (role == FrameTableModel::FrameTypeRole) { 0108 return m_frameTypes.at(index.column()).getType(); 0109 } else if (role == Qt::BackgroundRole) { 0110 if (index.column() == 0 && m_diffCheckEnabled) { 0111 const ImportTrackData& trackData = m_trackDataVector.at(index.row()); 0112 if (int diff = trackData.getTimeDifference(); 0113 diff >= 0 && m_colorProvider) { 0114 return m_colorProvider->colorForContext(diff > m_maxDiff 0115 ? ColorContext::Error : ColorContext::None); 0116 } 0117 } 0118 } else if (role == Qt::CheckStateRole && index.column() == 0) { 0119 return m_trackDataVector.at(index.row()).isEnabled() 0120 ? Qt::Checked : Qt::Unchecked; 0121 } 0122 return QVariant(); 0123 } 0124 0125 /** 0126 * Set data for a given role. 0127 * @param index model index 0128 * @param value data value 0129 * @param role item data role 0130 * @return true if successful 0131 */ 0132 bool TrackDataModel::setData(const QModelIndex& index, 0133 const QVariant& value, int role) 0134 { 0135 if (!index.isValid() || 0136 index.row() < 0 || 0137 index.row() >= m_trackDataVector.size() || 0138 index.column() < 0 || 0139 index.column() >= m_frameTypes.size()) 0140 return false; 0141 0142 if (role == Qt::EditRole) { 0143 ImportTrackData& trackData = m_trackDataVector[index.row()]; 0144 Frame::ExtendedType type = m_frameTypes.at(index.column()); 0145 if (static_cast<int>(type.getType()) >= FT_FirstTrackProperty) 0146 return false; 0147 0148 trackData.setValue(type, value.toString()); 0149 return true; 0150 } 0151 if (role == Qt::CheckStateRole && index.column() == 0) { 0152 if (bool isChecked(value.toInt() == Qt::Checked); 0153 isChecked != m_trackDataVector.at(index.row()).isEnabled()) { 0154 m_trackDataVector[index.row()].setEnabled(isChecked); 0155 emit dataChanged(index, index); 0156 } 0157 return true; 0158 } 0159 return false; 0160 } 0161 0162 /** 0163 * Get data for header section. 0164 * @param section column or row 0165 * @param orientation horizontal or vertical 0166 * @param role item data role 0167 * @return header data for role 0168 */ 0169 QVariant TrackDataModel::headerData( 0170 int section, Qt::Orientation orientation, int role) const 0171 { 0172 if (role != Qt::DisplayRole) 0173 return QVariant(); 0174 if (orientation == Qt::Horizontal && section < m_frameTypes.size()) { 0175 Frame::ExtendedType type = m_frameTypes.at(section); 0176 if (auto typeOrProperty = static_cast<int>(type.getType()); 0177 typeOrProperty < FT_FirstTrackProperty) { 0178 return typeOrProperty == Frame::FT_Track 0179 ? tr("Track") // shorter header for track number 0180 : Frame::getDisplayName(type.getName()); 0181 } else { 0182 switch (typeOrProperty) { 0183 case FT_FilePath: 0184 return tr("Absolute path to file"); 0185 case FT_FileName: 0186 return tr("Filename"); 0187 case FT_Duration: 0188 return tr("Duration"); 0189 case FT_ImportDuration: 0190 return tr("Length"); 0191 default: 0192 ; 0193 } 0194 } 0195 } else if (orientation == Qt::Vertical && section < m_trackDataVector.size()) { 0196 if (int fileDuration = m_trackDataVector.at(section).getFileDuration(); 0197 fileDuration > 0) { 0198 return TaggedFile::formatTime(fileDuration); 0199 } 0200 } 0201 return section + 1; 0202 } 0203 0204 /** 0205 * Get number of rows. 0206 * @param parent parent model index, invalid for table models 0207 * @return number of rows, 0208 * if parent is valid number of children (0 for table models) 0209 */ 0210 int TrackDataModel::rowCount(const QModelIndex& parent) const 0211 { 0212 return parent.isValid() ? 0 : m_trackDataVector.size(); 0213 } 0214 0215 /** 0216 * Get number of columns. 0217 * @param parent parent model index, invalid for table models 0218 * @return number of columns, 0219 * if parent is valid number of children (0 for table models) 0220 */ 0221 int TrackDataModel::columnCount(const QModelIndex& parent) const 0222 { 0223 return parent.isValid() ? 0 : m_frameTypes.size(); 0224 } 0225 0226 /** 0227 * Insert rows. 0228 * @param row rows are inserted before this row, if 0 at the begin, 0229 * if rowCount() at the end 0230 * @param count number of rows to insert 0231 * @return true if successful 0232 */ 0233 bool TrackDataModel::insertRows(int row, int count, const QModelIndex&) 0234 { 0235 if (count > 0) { 0236 beginInsertRows(QModelIndex(), row, row + count - 1); 0237 m_trackDataVector.insert(row, count, ImportTrackData()); 0238 endInsertRows(); 0239 } 0240 return true; 0241 } 0242 0243 /** 0244 * Remove rows. 0245 * @param row rows are removed starting with this row 0246 * @param count number of rows to remove 0247 * @return true if successful 0248 */ 0249 bool TrackDataModel::removeRows(int row, int count, 0250 const QModelIndex&) 0251 { 0252 if (count > 0) { 0253 beginRemoveRows(QModelIndex(), row, row + count - 1); 0254 m_trackDataVector.remove(row, count); 0255 endRemoveRows(); 0256 } 0257 return true; 0258 } 0259 0260 /** 0261 * Insert columns. 0262 * @param column columns are inserted before this column, if 0 at the begin, 0263 * if columnCount() at the end 0264 * @param count number of columns to insert 0265 * @return true if successful 0266 */ 0267 bool TrackDataModel::insertColumns(int column, int count, 0268 const QModelIndex&) 0269 { 0270 if (count > 0) { 0271 beginInsertColumns(QModelIndex(), column, column + count - 1); 0272 for (int i = 0; i < count; ++i) 0273 m_frameTypes.insert(column, Frame::ExtendedType()); 0274 endInsertColumns(); 0275 } 0276 return true; 0277 } 0278 0279 /** 0280 * Remove columns. 0281 * @param column columns are removed starting with this column 0282 * @param count number of columns to remove 0283 * @return true if successful 0284 */ 0285 bool TrackDataModel::removeColumns(int column, int count, 0286 const QModelIndex&) 0287 { 0288 if (count > 0) { 0289 beginRemoveColumns(QModelIndex(), column, column + count - 1); 0290 for (int i = 0; i < count; ++i) 0291 m_frameTypes.removeAt(column); 0292 endRemoveColumns(); 0293 } 0294 return true; 0295 } 0296 0297 /** 0298 * Set the check state of all tracks in the table. 0299 * 0300 * @param checked true to check the tracks 0301 */ 0302 void TrackDataModel::setAllCheckStates(bool checked) 0303 { 0304 for (int row = 0; row < rowCount(); ++row) { 0305 m_trackDataVector[row].setEnabled(checked); 0306 } 0307 } 0308 0309 /** 0310 * Set time difference check configuration. 0311 * 0312 * @param enable true to enable check 0313 * @param maxDiff maximum allowed time difference 0314 */ 0315 void TrackDataModel::setTimeDifferenceCheck(bool enable, int maxDiff) { 0316 bool changed = m_diffCheckEnabled != enable || m_maxDiff != maxDiff; 0317 m_diffCheckEnabled = enable; 0318 m_maxDiff = maxDiff; 0319 if (changed) 0320 emit dataChanged(index(0,0), index(rowCount() - 1, 0)); 0321 } 0322 0323 /** 0324 * Calculate accuracy of imported track data. 0325 * @return accuracy in percent, -1 if unknown. 0326 */ 0327 int TrackDataModel::calculateAccuracy() const 0328 { 0329 int numImportTracks = 0, numTracks = 0, numMismatches = 0, numMatches = 0; 0330 for (auto it = m_trackDataVector.constBegin(); 0331 it != m_trackDataVector.constEnd(); 0332 ++it) { 0333 const ImportTrackData& trackData = *it; 0334 if (int diff = trackData.getTimeDifference(); diff >= 0) { 0335 if (diff > 3) { 0336 ++numMismatches; 0337 } else { 0338 ++numMatches; 0339 } 0340 } else { 0341 // no durations available => try to match using file name and title 0342 QSet<QString> titleWords = trackData.getTitleWords(); 0343 if (int numWords = titleWords.size(); numWords > 0) { 0344 QSet<QString> fileWords = trackData.getFilenameWords(); 0345 if (fileWords.size() < numWords) { 0346 numWords = fileWords.size(); 0347 } 0348 if (int wordMatch = numWords > 0 0349 ? 100 * (fileWords & titleWords).size() / numWords : 0; 0350 wordMatch < 75) { 0351 ++numMismatches; 0352 } else { 0353 ++numMatches; 0354 } 0355 } 0356 } 0357 if (trackData.getImportDuration() != 0 || !trackData.getTitle().isEmpty()) { 0358 ++numImportTracks; 0359 } 0360 if (trackData.getFileDuration() != 0) { 0361 ++numTracks; 0362 } 0363 } 0364 0365 if (numTracks > 0 && numImportTracks > 0 && 0366 (numMatches > 0 || numMismatches > 0)) { 0367 return numMatches * 100 / numTracks; 0368 } 0369 return -1; 0370 } 0371 0372 0373 /** 0374 * Get frame for index. 0375 * @param index model index 0376 * @return frame, 0 if no frame. 0377 */ 0378 const Frame* TrackDataModel::getFrameOfIndex(const QModelIndex& index) const 0379 { 0380 if (!index.isValid() || 0381 index.row() < 0 || 0382 index.row() >= m_trackDataVector.size() || 0383 index.column() < 0 || 0384 index.column() >= m_frameTypes.size()) 0385 return nullptr; 0386 0387 const ImportTrackData& trackData = m_trackDataVector.at(index.row()); 0388 Frame::ExtendedType type = m_frameTypes.at(index.column()); 0389 if (static_cast<int>(type.getType()) >= FT_FirstTrackProperty) 0390 return nullptr; 0391 0392 auto it = trackData.findByExtendedType(type); 0393 return it != trackData.cend() ? &(*it) : nullptr; 0394 } 0395 0396 /** 0397 * Set track data. 0398 * @param trackDataVector track data 0399 */ 0400 void TrackDataModel::setTrackData(const ImportTrackDataVector& trackDataVector) 0401 { 0402 static constexpr int initFrameTypes[] = { 0403 FT_ImportDuration, FT_FileName, FT_FilePath, 0404 Frame::FT_Track, Frame::FT_Title, 0405 Frame::FT_Artist, Frame::FT_Album, Frame::FT_Date, Frame::FT_Genre, 0406 Frame::FT_Comment 0407 }; 0408 0409 QList<Frame::ExtendedType> newFrameTypes; 0410 for (auto initFrameType : initFrameTypes) { 0411 newFrameTypes.append( // clazy:exclude=reserve-candidates 0412 Frame::ExtendedType(static_cast<Frame::Type>(initFrameType), QLatin1String(""))); 0413 } 0414 0415 for (auto tit = trackDataVector.constBegin(); 0416 tit != trackDataVector.constEnd(); 0417 ++tit) { 0418 for (auto fit = tit->cbegin(); fit != tit->cend(); ++fit) { 0419 if (Frame::ExtendedType type = fit->getExtendedType(); 0420 type.getType() > Frame::FT_LastV1Frame && 0421 !newFrameTypes.contains(type)) { 0422 newFrameTypes.append(type); 0423 } 0424 } 0425 } 0426 0427 int oldNumTypes = m_frameTypes.size(); 0428 int newNumTypes = newFrameTypes.size(); 0429 int numColumnsChanged = qMin(oldNumTypes, newNumTypes); 0430 if (newNumTypes < oldNumTypes) 0431 beginRemoveColumns(QModelIndex(), newNumTypes, oldNumTypes - 1); 0432 else if (newNumTypes > oldNumTypes) 0433 beginInsertColumns(QModelIndex(), oldNumTypes, newNumTypes - 1); 0434 0435 m_frameTypes = newFrameTypes; 0436 0437 if (newNumTypes < oldNumTypes) 0438 endRemoveColumns(); 0439 else if (newNumTypes > oldNumTypes) 0440 endInsertColumns(); 0441 0442 int oldNumTracks = m_trackDataVector.size(); 0443 int newNumTracks = trackDataVector.size(); 0444 int numRowsChanged = qMin(oldNumTracks, newNumTracks); 0445 if (newNumTracks < oldNumTracks) 0446 beginRemoveRows(QModelIndex(), newNumTracks, oldNumTracks - 1); 0447 else if (newNumTracks > oldNumTracks) 0448 beginInsertRows(QModelIndex(), oldNumTracks, newNumTracks - 1); 0449 0450 m_trackDataVector = trackDataVector; 0451 0452 if (newNumTracks < oldNumTracks) 0453 endRemoveRows(); 0454 else if (newNumTracks > oldNumTracks) 0455 endInsertRows(); 0456 0457 0458 if (numRowsChanged > 0) 0459 emit dataChanged( 0460 index(0, 0), index(numRowsChanged - 1, numColumnsChanged - 1)); 0461 } 0462 0463 /** 0464 * Get track data. 0465 * @return track data 0466 */ 0467 ImportTrackDataVector TrackDataModel::getTrackData() const 0468 { 0469 return m_trackDataVector; 0470 } 0471 0472 /** 0473 * Get the frame type for a column. 0474 * @param column model column 0475 * @return frame type of Frame::Type or TrackDataModel::TrackProperties, 0476 * -1 if column invalid. 0477 */ 0478 int TrackDataModel::frameTypeForColumn(int column) const 0479 { 0480 return column < m_frameTypes.size() ? m_frameTypes.at(column).getType() : -1; 0481 } 0482 0483 /** 0484 * Get column for a frame type. 0485 * @param frameType frame type of Frame::Type or 0486 * TrackDataModel::TrackProperties. 0487 * @return model column, -1 if not found. 0488 */ 0489 int TrackDataModel::columnForFrameType(int frameType) const 0490 { 0491 return m_frameTypes.indexOf( 0492 Frame::ExtendedType(static_cast<Frame::Type>(frameType), QLatin1String(""))); 0493 }