File indexing completed on 2024-05-19 04:56:04

0001 /**
0002  * \file frametablemodel.cpp
0003  * Model for table with frames.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 01 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 "frametablemodel.h"
0028 #include <algorithm>
0029 #include "coretaggedfileiconprovider.h"
0030 #include "fileconfig.h"
0031 #include "pictureframe.h"
0032 #include "framenotice.h"
0033 
0034 namespace {
0035 
0036 QHash<int,QByteArray> getRoleHash()
0037 {
0038   QHash<int, QByteArray> roles;
0039   roles[Qt::CheckStateRole] = "checkState";
0040   roles[FrameTableModel::FrameTypeRole] = "frameType";
0041   roles[FrameTableModel::NameRole] = "name";
0042   roles[FrameTableModel::ValueRole] = "value";
0043   roles[FrameTableModel::ModifiedRole] = "modified";
0044   roles[FrameTableModel::TruncatedRole] = "truncated";
0045   roles[FrameTableModel::InternalNameRole] = "internalName";
0046   roles[FrameTableModel::FieldIdsRole] = "fieldIds";
0047   roles[FrameTableModel::FieldValuesRole] = "fieldValues";
0048   roles[FrameTableModel::CompletionsRole] = "completions";
0049   roles[FrameTableModel::NoticeRole] = "notice";
0050   roles[FrameTableModel::NoticeWarningRole] = "noticeWarning";
0051   return roles;
0052 }
0053 
0054 }
0055 
0056 /**
0057  * Constructor.
0058  * @param id3v1  true if model for ID3v1 frames
0059  * @param colorProvider colorProvider
0060  * @param parent parent widget
0061  */
0062 FrameTableModel::FrameTableModel(
0063     bool id3v1, CoreTaggedFileIconProvider* colorProvider, QObject* parent)
0064   : QAbstractTableModel(parent), m_markedRows(0), m_changedFrames(0),
0065     m_colorProvider(colorProvider), m_id3v1(id3v1), m_emptyHeaders(false)
0066 {
0067   setObjectName(QLatin1String("FrameTableModel"));
0068 }
0069 
0070 /**
0071  * Get item flags for index.
0072  * @param index model index
0073  * @return item flags
0074  */
0075 Qt::ItemFlags FrameTableModel::flags(const QModelIndex& index) const
0076 {
0077   Qt::ItemFlags theFlags = QAbstractTableModel::flags(index);
0078   if (index.isValid()) {
0079     if (index.column() == CI_Enable) {
0080       theFlags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
0081     } else if (index.column() == CI_Value) {
0082       theFlags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
0083     }
0084   }
0085   return theFlags;
0086 }
0087 
0088 /**
0089  * Get data for a given role.
0090  * @param index model index
0091  * @param role item data role
0092  * @return data for role
0093  */
0094 QVariant FrameTableModel::data(const QModelIndex& index, int role) const
0095 {
0096   if (!index.isValid() ||
0097       index.row() < 0 || index.row() >= static_cast<int>(frames().size()) ||
0098       index.column() < 0 || index.column() >= CI_NumColumns)
0099     return QVariant();
0100   auto it = frameAt(index.row());
0101   bool isModified = false, isTruncated = false;
0102   if ((role == Qt::BackgroundRole && index.column() == CI_Enable) ||
0103       role == ModifiedRole) {
0104     Frame::ExtendedType extendedType = it->getExtendedType();
0105     Frame::Type type = extendedType.getType();
0106     isModified = FileConfig::instance().markChanges() &&
0107       (it->isValueChanged() ||
0108        (type != Frame::FT_Other &&
0109         static_cast<unsigned>(type) < sizeof(m_changedFrames) * 8 &&
0110         (m_changedFrames & (1ULL << type)) != 0) ||
0111        (type == Frame::FT_Other &&
0112         m_changedOtherFrameNames.contains(extendedType.getInternalName())));
0113   }
0114   if (((role == Qt::BackgroundRole || role == Qt::ToolTipRole) &&
0115        index.column() == CI_Value) ||
0116       (role == TruncatedRole || role == NoticeRole || role == NoticeWarningRole)) {
0117     isTruncated = (static_cast<unsigned>(index.row()) < sizeof(m_markedRows) * 8 &&
0118         (m_markedRows & (1ULL << index.row())) != 0) || it->isMarked();
0119   }
0120   if (role == Qt::DisplayRole || role == Qt::EditRole) {
0121     if (index.column() == CI_Enable) {
0122       QString displayName = Frame::getDisplayName(it->getName());
0123       if (it->getValue() != Frame::differentRepresentation()) {
0124         if (it->getType() == Frame::FT_Picture) {
0125           if (QVariant fieldValue = it->getFieldValue(Frame::ID_PictureType);
0126               fieldValue.isValid()) {
0127             if (auto pictureType =
0128                   static_cast<PictureFrame::PictureType>(fieldValue.toInt());
0129                 pictureType != PictureFrame::PT_Other) {
0130               if (QString typeName = PictureFrame::getPictureTypeName(pictureType);
0131                   !typeName.isEmpty()) {
0132                 displayName += QLatin1String(": ");
0133                 displayName += typeName;
0134               }
0135             }
0136           }
0137         } else if (it->getType() == Frame::FT_Other) {
0138           if (it->getInternalName().startsWith(QLatin1String("RVA2"))) {
0139             if (QVariant fieldValue = it->getFieldValue(Frame::ID_Id);
0140                 fieldValue.isValid()) {
0141               if (auto identifier = fieldValue.toString();
0142                   !identifier.isEmpty()) {
0143                 displayName = tr("Volume");
0144                 displayName += QLatin1String(": ");
0145                 displayName += identifier;
0146               }
0147             }
0148           } else if (it->getInternalName().startsWith(QLatin1String("UFID"))) {
0149             if (QVariant fieldValue = it->getFieldValue(Frame::ID_Owner);
0150                 fieldValue.isValid()) {
0151               if (auto owner = fieldValue.toString(); !owner.isEmpty()) {
0152                 // Shorten the owner so that it is visible in the frame type column.
0153                 // For example http://musicbrainz.org -> musicbrainz
0154                 //             http://www.cddb.com/id3/taginfo.html -> taginfo
0155                 //             http://www.id3.org/dummy/ufid.html -> ufid
0156                 if (int endPos = owner.lastIndexOf(QLatin1Char('.'));
0157                     endPos != -1) {
0158                   int startPos = owner.lastIndexOf(QLatin1Char('.'), endPos - 1);
0159                   if (int slashPos = owner.lastIndexOf(QLatin1Char('/'), endPos - 1);
0160                       slashPos != -1 && slashPos > startPos) {
0161                     startPos = slashPos;
0162                   }
0163                   if (startPos != -1) {
0164                     owner = owner.mid(startPos + 1, endPos - startPos - 1);
0165                   }
0166                 }
0167                 displayName = tr("File ID");
0168                 displayName += QLatin1String(": ");
0169                 displayName += owner;
0170               }
0171             }
0172           }
0173         }
0174       }
0175       return displayName;
0176     }
0177     if (index.column() == CI_Value)
0178       return it->getValue();
0179   } else if (role == Qt::CheckStateRole && index.column() == CI_Enable) {
0180     return m_frameSelected.at(index.row()) ? Qt::Checked : Qt::Unchecked;
0181   } else if (role == Qt::BackgroundRole) {
0182     if (m_colorProvider) {
0183       if (index.column() == CI_Enable) {
0184         return m_colorProvider->colorForContext(
0185               isModified ? ColorContext::Marked : ColorContext::None);
0186       }
0187       if (index.column() == CI_Value) {
0188         return m_colorProvider->colorForContext(
0189           isTruncated ? ColorContext::Error : ColorContext::None);
0190       }
0191     }
0192   } else if (role == Qt::ToolTipRole) {
0193     QString toolTip;
0194     if (isTruncated && index.column() == CI_Value) {
0195       FrameNotice notice = it->isMarked() ? it->getNotice()
0196                                           : FrameNotice::Truncated;
0197       toolTip = notice.getDescription();
0198     }
0199     return toolTip;
0200   } else if (role == FrameTypeRole) {
0201     return it->getType();
0202   } else if (role == NameRole) {
0203     return Frame::getDisplayName(it->getName());
0204   } else if (role == ValueRole) {
0205     return it->getValue();
0206   } else if (role == ModifiedRole) {
0207     return isModified;
0208   } else if (role == TruncatedRole) {
0209     return isTruncated;
0210   } else if (role == InternalNameRole) {
0211     return it->getInternalName();
0212   } else if (role == FieldIdsRole) {
0213     QVariantList result;
0214     const Frame::FieldList& fields = it->getFieldList();
0215     for (auto fit = fields.constBegin(); fit != fields.constEnd(); ++fit) {
0216       result.append(fit->m_id);
0217     }
0218     return result;
0219   } else if (role == FieldValuesRole) {
0220     QVariantList result;
0221     const Frame::FieldList& fields = it->getFieldList();
0222     for (auto fit = fields.constBegin(); fit != fields.constEnd(); ++fit) {
0223       result.append(fit->m_value);
0224     }
0225     return result;
0226   } else if (role == CompletionsRole) {
0227 #if QT_VERSION >= 0x050e00
0228     const QSet<QString> completions =
0229         getCompletionsForType(it->getExtendedType());
0230     QStringList result(completions.constBegin(), completions.constEnd());
0231 #else
0232     QStringList result = getCompletionsForType(it->getExtendedType()).toList();
0233 #endif
0234     result.sort();
0235     return result;
0236   } else if (role == NoticeRole) {
0237     QString toolTip;
0238     if (isTruncated) {
0239       FrameNotice notice = it->isMarked() ? it->getNotice()
0240                                           : FrameNotice::Truncated;
0241       toolTip = notice.getDescription();
0242     }
0243     return toolTip;
0244   } else if (role == NoticeWarningRole) {
0245     return isTruncated ? (it->isMarked() ? it->getNotice().getWarning()
0246                                          : FrameNotice::Truncated)
0247                        : FrameNotice::None;
0248   }
0249   return QVariant();
0250 }
0251 
0252 /**
0253  * Set data for a given role.
0254  * @param index model index
0255  * @param value data value
0256  * @param role item data role
0257  * @return true if successful
0258  */
0259 bool FrameTableModel::setData(const QModelIndex& index,
0260                               const QVariant& value, int role)
0261 {
0262   if (!index.isValid() ||
0263       index.row() < 0 || index.row() >= static_cast<int>(frames().size()) ||
0264       index.column() < 0 || index.column() >= CI_NumColumns)
0265     return false;
0266   if ((role == Qt::EditRole && index.column() == CI_Value) ||
0267       role == ValueRole) {
0268     QString valueStr(value.toString());
0269     if (auto it = frameAt(index.row()); valueStr != it->getValue()) {
0270       auto& frame = const_cast<Frame&>(*it);
0271       if (valueStr.isNull()) valueStr = QLatin1String("");
0272       frame.setValueIfChanged(valueStr);
0273       emit dataChanged(index, index);
0274 
0275       // Automatically set the checkbox when a value is changed
0276       if (!m_frameSelected.at(index.row())) {
0277         m_frameSelected[index.row()] = true;
0278         QModelIndex checkIndex(index.sibling(index.row(), CI_Enable));
0279         emit dataChanged(checkIndex, checkIndex);
0280       }
0281     }
0282     return true;
0283   }
0284   if (role == Qt::CheckStateRole && index.column() == CI_Enable) {
0285     if (bool isChecked(value.toInt() == Qt::Checked);
0286       isChecked != m_frameSelected.at(index.row())) {
0287       m_frameSelected[index.row()] = isChecked;
0288       emit dataChanged(index, index);
0289     }
0290     return true;
0291   }
0292   return false;
0293 }
0294 
0295 /**
0296  * Get data for header section.
0297  * @param section column or row
0298  * @param orientation horizontal or vertical
0299  * @param role item data role
0300  * @return header data for role
0301  */
0302 QVariant FrameTableModel::headerData(
0303     int section, Qt::Orientation orientation, int role) const
0304 {
0305   if (role != Qt::DisplayRole || m_emptyHeaders)
0306     return QVariant();
0307   if (orientation == Qt::Horizontal) {
0308     return section == CI_Enable ? tr("Name") : tr("Data");
0309   }
0310   return section + 1;
0311 }
0312 
0313 /**
0314  * Get number of rows.
0315  * @param parent parent model index, invalid for table models
0316  * @return number of rows,
0317  * if parent is valid number of children (0 for table models)
0318  */
0319 int FrameTableModel::rowCount(const QModelIndex& parent) const
0320 {
0321   return parent.isValid() ? 0 : static_cast<int>(frames().size());
0322 }
0323 
0324 /**
0325  * Get number of columns.
0326  * @param parent parent model index, invalid for table models
0327  * @return number of columns,
0328  * if parent is valid number of children (0 for table models)
0329  */
0330 int FrameTableModel::columnCount(const QModelIndex& parent) const
0331 {
0332   return parent.isValid() ? 0 : CI_NumColumns;
0333 }
0334 
0335 /**
0336  * Insert rows.
0337  * @param count number of rows to insert
0338  * @return true if successful
0339  */
0340 bool FrameTableModel::insertRows(int, int count, const QModelIndex&)
0341 {
0342   for (int i = 0; i < count; ++i)
0343     insertFrame(Frame());
0344   return true;
0345 }
0346 
0347 /**
0348  * Insert a frame.
0349  * @param frame frame to insert
0350  * @return true if successful
0351  */
0352 void FrameTableModel::insertFrame(const Frame& frame)
0353 {
0354   auto it = m_frames.upper_bound(frame);
0355   int row = rowOf(it);
0356   beginInsertRows(QModelIndex(), row, row);
0357   it = m_frames.insert(it, frame);
0358   updateFrameRowMapping();
0359   resizeFrameSelected();
0360   endInsertRows();
0361 }
0362 
0363 /**
0364  * Remove rows.
0365  * @param row rows are removed starting with this row
0366  * @param count number of rows to remove
0367  * @return true if successful
0368  */
0369 bool FrameTableModel::removeRows(int row, int count,
0370                         const QModelIndex&)
0371 {
0372   if (count > 0) {
0373     beginRemoveRows(QModelIndex(), row, row + count - 1);
0374     for (int i = row; i < row + count; ++i) {
0375       m_frames.erase(frameAt(i));
0376     }
0377     updateFrameRowMapping();
0378     resizeFrameSelected();
0379     endRemoveRows();
0380   }
0381   return true;
0382 }
0383 
0384 /**
0385  * Map role identifiers to role property names in scripting languages.
0386  * @return hash mapping role identifiers to names.
0387  */
0388 QHash<int,QByteArray> FrameTableModel::roleNames() const
0389 {
0390   static QHash<int, QByteArray> roles = getRoleHash();
0391   return roles;
0392 }
0393 
0394 /**
0395  * Get the frame at a specific position in the collection.
0396  * @param row position of frame
0397  * @return iterator to frame
0398  */
0399 FrameCollection::iterator FrameTableModel::frameAt(int row) const {
0400   return row >= 0 && row < m_frameOfRow.size()
0401       ? m_frameOfRow.at(row) : frames().end();
0402 }
0403 
0404 /**
0405  * Get the row corresponding to a frame iterator.
0406  * @param frameIt frame iterator
0407  * @return row number, number of rows if not found.
0408  */
0409 int FrameTableModel::rowOf(FrameCollection::iterator frameIt) const {
0410   int row = 0;
0411   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
0412     if (frameIt == *it)
0413       break;
0414     ++row;
0415   }
0416   return row;
0417 }
0418 
0419 /**
0420  * Mark rows.
0421  * @param rowMask mask with bits of rows to mark
0422  */
0423 void FrameTableModel::markRows(quint64 rowMask)
0424 {
0425   quint64 changedBits = m_markedRows ^ rowMask;
0426   m_markedRows = rowMask;
0427 
0428   // Emit a change signal for all indexes affected by the change.
0429   if (!changedBits)
0430     return;
0431 
0432   quint64 mask;
0433   int row;
0434   for (mask = 1ULL, row = 0;
0435        static_cast<unsigned>(row) < sizeof(changedBits) * 8;
0436        mask <<= 1, ++row) {
0437     if ((changedBits & mask) != 0) {
0438       // Include both the columns for Qt::BackgroundRole and TruncatedRole.
0439       emit dataChanged(index(row, 0), index(row, 1));
0440     }
0441   }
0442 }
0443 
0444 /**
0445  * Mark changed frames.
0446  * @param types frame types to mark
0447  */
0448 void FrameTableModel::markChangedFrames(const QList<Frame::ExtendedType>& types)
0449 {
0450   quint64 mask = 0;
0451   QSet<QString> changedOtherFrameNames;
0452   changedOtherFrameNames.clear();
0453   for (const auto& extendedType : types) {
0454     Frame::Type type = extendedType.getType();
0455     mask |= 1ULL << type;
0456     if (type == Frame::FT_Other) {
0457       if (const QString internalName = extendedType.getInternalName();
0458           !internalName.isEmpty()) {
0459         changedOtherFrameNames.insert(internalName);
0460       }
0461     }
0462   }
0463 
0464   quint64 changedBits = m_changedFrames ^ mask;
0465   m_changedFrames = mask;
0466   QSet<QString> addedNames = changedOtherFrameNames - m_changedOtherFrameNames;
0467   QSet<QString> removedNames = m_changedOtherFrameNames - changedOtherFrameNames;
0468   m_changedOtherFrameNames.swap(changedOtherFrameNames);
0469 
0470   // Emit a change signal for all indexes affected by the change.
0471   if (!FileConfig::instance().markChanges() ||
0472       (!changedBits && addedNames.isEmpty() && removedNames.isEmpty()))
0473     return;
0474 
0475   const FrameCollection& frameCollection = frames();
0476   auto it = frameCollection.cbegin();
0477   int row = 0;
0478   for (; it != frameCollection.cend(); ++it, ++row) {
0479     Frame::ExtendedType extendedType = it->getExtendedType();
0480     if (Frame::Type type = extendedType.getType(); type != Frame::FT_Other) {
0481       if (it->isValueChanged() ||
0482           (static_cast<unsigned>(type) < sizeof(changedBits) * 8 &&
0483            (changedBits & (1ULL << type)) != 0)) {
0484         QModelIndex idx = index(row, CI_Enable);
0485         emit dataChanged(idx, idx);
0486       }
0487     } else {
0488       if (const QString name = extendedType.getInternalName();
0489           it->isValueChanged() ||
0490           addedNames.contains(name) || removedNames.contains(name)) {
0491         QModelIndex idx = index(row, CI_Enable);
0492         emit dataChanged(idx, idx);
0493       }
0494     }
0495   }
0496 }
0497 
0498 /**
0499  * Get frame for index.
0500  * @param index model index
0501  * @return frame, 0 if no frame.
0502  */
0503 const Frame* FrameTableModel::getFrameOfIndex(const QModelIndex& index) const
0504 {
0505   if (index.isValid() && index.row() < static_cast<int>(frames().size())) {
0506     auto it = frameAt(index.row());
0507     return &(*it);
0508   }
0509   return nullptr;
0510 }
0511 
0512 /**
0513  * Get row with frame with a specific frame index.
0514  * @param index frame index
0515  * @return row number, -1 if not found.
0516  */
0517 int FrameTableModel::getRowWithFrameIndex(int index) const
0518 {
0519   int row = 0;
0520   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
0521     if ((*it)->getIndex() == index) {
0522       return row;
0523     }
0524     ++row;
0525   }
0526   return -1;
0527 }
0528 
0529 /**
0530  * Get row with frame with a specific frame name.
0531  * @param name name of frame
0532  * @return row number, -1 if not found.
0533  */
0534 int FrameTableModel::getRowWithFrameName(const QString& name) const
0535 {
0536   int row = 0;
0537   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
0538     if ((*it)->getName() == name) {
0539       return row;
0540     }
0541     ++row;
0542   }
0543   return -1;
0544 }
0545 
0546 /**
0547  * Get filter with enabled frames.
0548  *
0549  * @param allDisabledToAllEnabled true to enable all if all are disabled
0550  *
0551  * @return filter with enabled frames.
0552  */
0553 FrameFilter FrameTableModel::getEnabledFrameFilter(
0554   bool allDisabledToAllEnabled) const
0555 {
0556   FrameFilter filter;
0557   filter.enableAll();
0558   bool allDisabled = true;
0559   int numberRows = rowCount();
0560   int row = 0;
0561   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
0562     if (row >= numberRows) break;
0563     if (!m_frameSelected.at(row)) {
0564       filter.enable((*it)->getType(), (*it)->getName(), false);
0565     } else {
0566       allDisabled = false;
0567     }
0568     ++row;
0569   }
0570   if (allDisabledToAllEnabled && allDisabled) {
0571     filter.enableAll();
0572   }
0573   return filter;
0574 }
0575 
0576 /**
0577  * Get enabled frames.
0578  * @return frame collection with enabled frames.
0579  */
0580 FrameCollection FrameTableModel::getEnabledFrames() const
0581 {
0582   FrameCollection enabledFrames;
0583   const int numberRows = m_frameSelected.size();
0584   int row = 0;
0585   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
0586     if (row >= numberRows) break;
0587     if (m_frameSelected.at(row)) {
0588       enabledFrames.insert(**it);
0589     }
0590     ++row;
0591   }
0592   return enabledFrames;
0593 }
0594 
0595 /**
0596  * Clear frame collection.
0597  */
0598 void FrameTableModel::clearFrames()
0599 {
0600   if (const int numFrames = static_cast<int>(m_frames.size()); numFrames > 0) {
0601     beginRemoveRows(QModelIndex(), 0, numFrames - 1);
0602     m_frames.clear();
0603     updateFrameRowMapping();
0604     m_frameSelected.clear();
0605     endRemoveRows();
0606   }
0607 }
0608 
0609 /**
0610  * Transfer frames to frame collection.
0611  * @param src frames to move into frame collection, will be cleared
0612  */
0613 void FrameTableModel::transferFrames(FrameCollection& src)
0614 {
0615   int oldNumFrames = static_cast<int>(m_frames.size());
0616   int newNumFrames = static_cast<int>(src.size());
0617   int numRowsChanged = qMin(oldNumFrames, newNumFrames);
0618   if (newNumFrames < oldNumFrames)
0619     beginRemoveRows(QModelIndex(), newNumFrames, oldNumFrames - 1);
0620   else if (newNumFrames > oldNumFrames)
0621     beginInsertRows(QModelIndex(), oldNumFrames, newNumFrames - 1);
0622 
0623   m_frames.clear();
0624   src.swap(m_frames);
0625   updateFrameRowMapping();
0626   resizeFrameSelected();
0627 
0628   if (newNumFrames < oldNumFrames)
0629     endRemoveRows();
0630   else if (newNumFrames > oldNumFrames)
0631     endInsertRows();
0632   if (numRowsChanged > 0)
0633     emit dataChanged(index(0, 0), index(numRowsChanged - 1, CI_NumColumns - 1));
0634 }
0635 
0636 /**
0637  * Start filtering different values.
0638  */
0639 void FrameTableModel::beginFilterDifferent()
0640 {
0641   m_differentValues.clear();
0642 }
0643 
0644 /**
0645  * End filtering different values.
0646  */
0647 void FrameTableModel::endFilterDifferent()
0648 {
0649 }
0650 
0651 /**
0652  * Get the different values which have been filtered for a frame type.
0653  * @param type frame type
0654  * @return different values.
0655  */
0656 QSet<QString> FrameTableModel::getCompletionsForType(
0657     Frame::ExtendedType type) const
0658 {
0659   return m_differentValues.value(type);
0660 }
0661 
0662 /**
0663  * Set values which are different inactive.
0664  *
0665  * @param others frames to compare, will be modified
0666  */
0667 void FrameTableModel::filterDifferent(FrameCollection& others)
0668 {
0669   int oldNumFrames = static_cast<int>(m_frames.size());
0670 
0671   m_frames.filterDifferent(others, &m_differentValues);
0672   updateFrameRowMapping();
0673   resizeFrameSelected();
0674 
0675   if (oldNumFrames > 0)
0676     emit dataChanged(index(0, 0), index(oldNumFrames - 1, CI_NumColumns - 1));
0677   if (int newNumFrames = static_cast<int>(m_frames.size());
0678       newNumFrames > oldNumFrames) {
0679     beginInsertRows(QModelIndex(), oldNumFrames, newNumFrames - 1);
0680     endInsertRows();
0681   }
0682 }
0683 
0684 /**
0685  * Set the check state of all frames in the table.
0686  *
0687  * @param checked true to check the frames
0688  */
0689 void FrameTableModel::setAllCheckStates(bool checked)
0690 {
0691   const int numRows = rowCount();
0692   m_frameSelected.fill(checked, 0, numRows);
0693   emit dataChanged(index(0, CI_Enable), index(numRows - 1, CI_Enable));
0694 }
0695 
0696 /**
0697  * Select all frames in the table.
0698  */
0699 void FrameTableModel::selectAllFrames()
0700 {
0701   setAllCheckStates(true);
0702 }
0703 
0704 /**
0705  * Deselect all frames in the table.
0706  */
0707 void FrameTableModel::deselectAllFrames()
0708 {
0709   setAllCheckStates(false);
0710 }
0711 
0712 /**
0713  * Select changed frames in the table.
0714  */
0715 void FrameTableModel::selectChangedFrames()
0716 {
0717   int row = 0;
0718   auto it = m_frameOfRow.constBegin();
0719   for (; row < m_frameSelected.size() && it != m_frameOfRow.constEnd();
0720        ++row, ++it) {
0721     if ((*it)->isValueChanged()) {
0722       m_frameSelected[row] = true;
0723       QModelIndex idx = index(row, CI_Enable);
0724       emit dataChanged(idx, idx);
0725     }
0726   }
0727 }
0728 
0729 /**
0730  * Resize the bit array with the frame selection to match the frames size.
0731  */
0732 void FrameTableModel::resizeFrameSelected()
0733 {
0734   // If all bits are set, set also the new bits.
0735   int oldSize = m_frameSelected.size();
0736   int newSize = static_cast<int>(frames().size());
0737   bool setNewBits = newSize > oldSize && oldSize > 0 &&
0738       m_frameSelected.count(true) == oldSize;
0739 
0740   m_frameSelected.resize(newSize);
0741 
0742   if (setNewBits) {
0743     for (int i = oldSize; i < newSize; ++i) {
0744       m_frameSelected.setBit(i, true);
0745     }
0746   }
0747 }
0748 
0749 /**
0750  * Update the frame to row mapping.
0751  */
0752 void FrameTableModel::updateFrameRowMapping()
0753 {
0754   const FrameCollection& frameCollection = frames();
0755   m_frameOfRow.resize(static_cast<int>(frameCollection.size()));
0756   auto frameIt = frameCollection.cbegin();
0757   auto rowIt = m_frameOfRow.begin(); // clazy:exclude=detaching-member
0758   for (; frameIt != frameCollection.cend(); ++frameIt, ++rowIt) {
0759     *rowIt = frameIt;
0760   }
0761   if (!m_frameTypeSeqNr.isEmpty()) {
0762     const QVector<int>& frameTypeSeqNr = m_frameTypeSeqNr;
0763     std::stable_sort(m_frameOfRow.begin(), m_frameOfRow.end(), // clazy:exclude=detaching-member
0764                      [&frameTypeSeqNr](FrameCollection::iterator lhs,
0765                                        FrameCollection::iterator rhs) {
0766       int lhsType = lhs->getType();
0767       int rhsType = rhs->getType();
0768       int lhsSeqNr = frameTypeSeqNr.at(lhsType);
0769       int rhsSeqNr = frameTypeSeqNr.at(rhsType);
0770       return lhsSeqNr < rhsSeqNr ||
0771              (lhsType == Frame::FT_Other && lhsType == rhsType &&
0772               lhs->getInternalName() < rhs->getInternalName());
0773     });
0774   }
0775 }
0776 
0777 /**
0778  * Set order of frames in frame table.
0779  * @param frameTypes ordered sequence of frame types
0780  * @remark This order is not used for ID3v1 frames.
0781  * @see TagConfig::quickAccessFrameOrder().
0782  */
0783 void FrameTableModel::setFrameOrder(const QList<int>& frameTypes)
0784 {
0785   if (frameTypes.isEmpty()) {
0786     m_frameTypeSeqNr.clear();
0787     return;
0788   }
0789   if (frameTypes.size() < Frame::FT_Custom1) {
0790     qWarning("FrameTableModel::setFrameOrder: Invalid parameter size");
0791     m_frameTypeSeqNr.clear();
0792     return;
0793   }
0794   m_frameTypeSeqNr.resize(Frame::FT_UnknownFrame + 1);
0795   m_frameTypeSeqNr[Frame::FT_UnknownFrame] = Frame::FT_UnknownFrame;
0796   m_frameTypeSeqNr[Frame::FT_Other] = Frame::FT_Other;
0797 
0798   int seqNr = 0;
0799   auto it = frameTypes.constBegin();
0800   for (; it != frameTypes.constEnd(); ++it, ++seqNr) {
0801     int frameType = *it;
0802     if (frameType < 0 || frameType > Frame::FT_LastFrame) {
0803       qWarning("FrameTableModel::setFrameOrder: Invalid frame type %d",
0804                frameType);
0805       m_frameTypeSeqNr.clear();
0806       return;
0807     }
0808     m_frameTypeSeqNr[frameType] = seqNr;
0809   }
0810   while (seqNr <= Frame::FT_LastFrame) {
0811     m_frameTypeSeqNr[seqNr] = seqNr;
0812     ++seqNr;
0813   }
0814 }