1 /**
2  * \file frametablemodel.cpp
3  * Model for table with frames.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 01 May 2011
8  *
9  * Copyright (C) 2011-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "frametablemodel.h"
28 #include <algorithm>
29 #include "coretaggedfileiconprovider.h"
30 #include "fileconfig.h"
31 #include "pictureframe.h"
32 #include "framenotice.h"
33 
34 namespace {
35 
getRoleHash()36 QHash<int,QByteArray> getRoleHash()
37 {
38   QHash<int, QByteArray> roles;
39   roles[Qt::CheckStateRole] = "checkState";
40   roles[FrameTableModel::FrameTypeRole] = "frameType";
41   roles[FrameTableModel::NameRole] = "name";
42   roles[FrameTableModel::ValueRole] = "value";
43   roles[FrameTableModel::ModifiedRole] = "modified";
44   roles[FrameTableModel::TruncatedRole] = "truncated";
45   roles[FrameTableModel::InternalNameRole] = "internalName";
46   roles[FrameTableModel::FieldIdsRole] = "fieldIds";
47   roles[FrameTableModel::FieldValuesRole] = "fieldValues";
48   roles[FrameTableModel::CompletionsRole] = "completions";
49   roles[FrameTableModel::NoticeRole] = "notice";
50   return roles;
51 }
52 
53 }
54 
55 /**
56  * Constructor.
57  * @param id3v1  true if model for ID3v1 frames
58  * @param colorProvider colorProvider
59  * @param parent parent widget
60  */
FrameTableModel(bool id3v1,CoreTaggedFileIconProvider * colorProvider,QObject * parent)61 FrameTableModel::FrameTableModel(
62     bool id3v1, CoreTaggedFileIconProvider* colorProvider, QObject* parent)
63   : QAbstractTableModel(parent), m_markedRows(0), m_changedFrames(0),
64     m_colorProvider(colorProvider), m_id3v1(id3v1), m_emptyHeaders(false)
65 {
66   setObjectName(QLatin1String("FrameTableModel"));
67 }
68 
69 /**
70  * Get item flags for index.
71  * @param index model index
72  * @return item flags
73  */
flags(const QModelIndex & index) const74 Qt::ItemFlags FrameTableModel::flags(const QModelIndex& index) const
75 {
76   Qt::ItemFlags theFlags = QAbstractTableModel::flags(index);
77   if (index.isValid()) {
78     if (index.column() == CI_Enable) {
79       theFlags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
80     } else if (index.column() == CI_Value) {
81       theFlags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
82     }
83   }
84   return theFlags;
85 }
86 
87 /**
88  * Get data for a given role.
89  * @param index model index
90  * @param role item data role
91  * @return data for role
92  */
data(const QModelIndex & index,int role) const93 QVariant FrameTableModel::data(const QModelIndex& index, int role) const
94 {
95   if (!index.isValid() ||
96       index.row() < 0 || index.row() >= static_cast<int>(frames().size()) ||
97       index.column() < 0 || index.column() >= CI_NumColumns)
98     return QVariant();
99   auto it = frameAt(index.row());
100   bool isModified = false, isTruncated = false;
101   if ((role == Qt::BackgroundRole && index.column() == CI_Enable) ||
102       role == ModifiedRole) {
103     isModified = FileConfig::instance().markChanges() &&
104       (it->isValueChanged() ||
105       (static_cast<unsigned>((*it).getType()) < sizeof(m_changedFrames) * 8 &&
106        (m_changedFrames & (1ULL << (*it).getType())) != 0));
107   }
108   if (((role == Qt::BackgroundRole || role == Qt::ToolTipRole) &&
109        index.column() == CI_Value) ||
110       (role == TruncatedRole || role == NoticeRole)) {
111     isTruncated = (static_cast<unsigned>(index.row()) < sizeof(m_markedRows) * 8 &&
112         (m_markedRows & (1ULL << index.row())) != 0) || it->isMarked();
113   }
114   if (role == Qt::DisplayRole || role == Qt::EditRole) {
115     if (index.column() == CI_Enable) {
116       QString displayName = Frame::getDisplayName(it->getName());
117       if (it->getValue() != Frame::differentRepresentation()) {
118         if (it->getType() == Frame::FT_Picture) {
119           QVariant fieldValue = it->getFieldValue(Frame::ID_PictureType);
120           if (fieldValue.isValid()) {
121             auto pictureType =
122                 static_cast<PictureFrame::PictureType>(fieldValue.toInt());
123             if (pictureType != PictureFrame::PT_Other) {
124               QString typeName = PictureFrame::getPictureTypeName(pictureType);
125               if (!typeName.isEmpty()) {
126                 displayName += QLatin1String(": ");
127                 displayName += typeName;
128               }
129             }
130           }
131         } else if (it->getType() == Frame::FT_Other) {
132           if (it->getInternalName().startsWith(QLatin1String("RVA2"))) {
133             QVariant fieldValue = it->getFieldValue(Frame::ID_Id);
134             if (fieldValue.isValid()) {
135               auto identifier = fieldValue.toString();
136               if (!identifier.isEmpty()) {
137                 displayName = tr("Volume");
138                 displayName += QLatin1String(": ");
139                 displayName += identifier;
140               }
141             }
142           } else if (it->getInternalName().startsWith(QLatin1String("UFID"))) {
143             QVariant fieldValue = it->getFieldValue(Frame::ID_Owner);
144             if (fieldValue.isValid()) {
145               auto owner = fieldValue.toString();
146               if (!owner.isEmpty()) {
147                 // Shorten the owner so that it is visible in the frame type column.
148                 // For example http://musicbrainz.org -> musicbrainz
149                 //             http://www.cddb.com/id3/taginfo.html -> taginfo
150                 //             http://www.id3.org/dummy/ufid.html -> ufid
151                 int endPos = owner.lastIndexOf(QLatin1Char('.'));
152                 if (endPos != -1) {
153                   int startPos = owner.lastIndexOf(QLatin1Char('.'), endPos - 1);
154                   int slashPos = owner.lastIndexOf(QLatin1Char('/'), endPos - 1);
155                   if (slashPos != -1 && slashPos > startPos) {
156                     startPos = slashPos;
157                   }
158                   if (startPos != -1) {
159                     owner = owner.mid(startPos + 1, endPos - startPos - 1);
160                   }
161                 }
162                 displayName = tr("File ID");
163                 displayName += QLatin1String(": ");
164                 displayName += owner;
165               }
166             }
167           }
168         }
169       }
170       return displayName;
171     } else if (index.column() == CI_Value)
172       return it->getValue();
173   } else if (role == Qt::CheckStateRole && index.column() == CI_Enable) {
174     return m_frameSelected.at(index.row()) ? Qt::Checked : Qt::Unchecked;
175   } else if (role == Qt::BackgroundRole) {
176     if (m_colorProvider) {
177       if (index.column() == CI_Enable) {
178         return m_colorProvider->colorForContext(
179               isModified ? ColorContext::Marked : ColorContext::None);
180       } else if (index.column() == CI_Value) {
181         return m_colorProvider->colorForContext(
182               isTruncated ? ColorContext::Error : ColorContext::None);
183       }
184     }
185   } else if (role == Qt::ToolTipRole) {
186     QString toolTip;
187     if (isTruncated && index.column() == CI_Value) {
188       FrameNotice notice = it->isMarked() ? it->getNotice()
189                                           : FrameNotice::Truncated;
190       toolTip = notice.getDescription();
191     }
192     return toolTip;
193   } else if (role == FrameTypeRole) {
194     return it->getType();
195   } else if (role == NameRole) {
196     return Frame::getDisplayName(it->getName());
197   } else if (role == ValueRole) {
198     return it->getValue();
199   } else if (role == ModifiedRole) {
200     return isModified;
201   } else if (role == TruncatedRole) {
202     return isTruncated;
203   } else if (role == InternalNameRole) {
204     return it->getInternalName();
205   } else if (role == FieldIdsRole) {
206     QVariantList result;
207     const Frame::FieldList& fields = it->getFieldList();
208     for (auto fit = fields.constBegin(); fit != fields.constEnd(); ++fit) {
209       result.append(fit->m_id);
210     }
211     return result;
212   } else if (role == FieldValuesRole) {
213     QVariantList result;
214     const Frame::FieldList& fields = it->getFieldList();
215     for (auto fit = fields.constBegin(); fit != fields.constEnd(); ++fit) {
216       result.append(fit->m_value);
217     }
218     return result;
219   } else if (role == CompletionsRole) {
220 #if QT_VERSION >= 0x050e00
221     const QSet<QString> completions =
222         getCompletionsForType(it->getExtendedType());
223     QStringList result(completions.constBegin(), completions.constEnd());
224 #else
225     QStringList result = getCompletionsForType(it->getExtendedType()).toList();
226 #endif
227     result.sort();
228     return result;
229   } else if (role == NoticeRole) {
230     QString toolTip;
231     if (isTruncated) {
232       FrameNotice notice = it->isMarked() ? it->getNotice()
233                                           : FrameNotice::Truncated;
234       toolTip = notice.getDescription();
235     }
236     return toolTip;
237   }
238   return QVariant();
239 }
240 
241 /**
242  * Set data for a given role.
243  * @param index model index
244  * @param value data value
245  * @param role item data role
246  * @return true if successful
247  */
setData(const QModelIndex & index,const QVariant & value,int role)248 bool FrameTableModel::setData(const QModelIndex& index,
249                               const QVariant& value, int role)
250 {
251   if (!index.isValid() ||
252       index.row() < 0 || index.row() >= static_cast<int>(frames().size()) ||
253       index.column() < 0 || index.column() >= CI_NumColumns)
254     return false;
255   if ((role == Qt::EditRole && index.column() == CI_Value) ||
256       role == ValueRole) {
257     QString valueStr(value.toString());
258     auto it = frameAt(index.row());
259     if (valueStr != (*it).getValue()) {
260       auto& frame = const_cast<Frame&>(*it);
261       if (valueStr.isNull()) valueStr = QLatin1String("");
262       frame.setValueIfChanged(valueStr);
263       emit dataChanged(index, index);
264 
265       // Automatically set the checkbox when a value is changed
266       if (!m_frameSelected.at(index.row())) {
267         m_frameSelected[index.row()] = true;
268         QModelIndex checkIndex(index.sibling(index.row(), CI_Enable));
269         emit dataChanged(checkIndex, checkIndex);
270       }
271     }
272     return true;
273   } else if (role == Qt::CheckStateRole && index.column() == CI_Enable) {
274     bool isChecked(value.toInt() == Qt::Checked);
275     if (isChecked != m_frameSelected.at(index.row())) {
276       m_frameSelected[index.row()] = isChecked;
277       emit dataChanged(index, index);
278     }
279     return true;
280   }
281   return false;
282 }
283 
284 /**
285  * Get data for header section.
286  * @param section column or row
287  * @param orientation horizontal or vertical
288  * @param role item data role
289  * @return header data for role
290  */
headerData(int section,Qt::Orientation orientation,int role) const291 QVariant FrameTableModel::headerData(
292     int section, Qt::Orientation orientation, int role) const
293 {
294   if (role != Qt::DisplayRole || m_emptyHeaders)
295     return QVariant();
296   if (orientation == Qt::Horizontal) {
297     return section == CI_Enable ? tr("Name") : tr("Data");
298   }
299   return section + 1;
300 }
301 
302 /**
303  * Get number of rows.
304  * @param parent parent model index, invalid for table models
305  * @return number of rows,
306  * if parent is valid number of children (0 for table models)
307  */
rowCount(const QModelIndex & parent) const308 int FrameTableModel::rowCount(const QModelIndex& parent) const
309 {
310   return parent.isValid() ? 0 : static_cast<int>(frames().size());
311 }
312 
313 /**
314  * Get number of columns.
315  * @param parent parent model index, invalid for table models
316  * @return number of columns,
317  * if parent is valid number of children (0 for table models)
318  */
columnCount(const QModelIndex & parent) const319 int FrameTableModel::columnCount(const QModelIndex& parent) const
320 {
321   return parent.isValid() ? 0 : CI_NumColumns;
322 }
323 
324 /**
325  * Insert rows.
326  * @param row rows are inserted before this row, if 0 at the begin,
327  * if rowCount() at the end
328  * @param count number of rows to insert
329  * @param parent parent model index, invalid for table models
330  * @return true if successful
331  */
insertRows(int,int count,const QModelIndex &)332 bool FrameTableModel::insertRows(int, int count, const QModelIndex&)
333 {
334   for (int i = 0; i < count; ++i)
335     insertFrame(Frame());
336   return true;
337 }
338 
339 /**
340  * Insert a frame.
341  * @param frame frame to insert
342  * @return true if successful
343  */
insertFrame(const Frame & frame)344 void FrameTableModel::insertFrame(const Frame& frame)
345 {
346   auto it = m_frames.upper_bound(frame);
347   int row = rowOf(it);
348   beginInsertRows(QModelIndex(), row, row);
349   it = m_frames.insert(it, frame);
350   updateFrameRowMapping();
351   resizeFrameSelected();
352   endInsertRows();
353 }
354 
355 /**
356  * Remove rows.
357  * @param row rows are removed starting with this row
358  * @param count number of rows to remove
359  * @param parent parent model index, invalid for table models
360  * @return true if successful
361  */
removeRows(int row,int count,const QModelIndex &)362 bool FrameTableModel::removeRows(int row, int count,
363                         const QModelIndex&)
364 {
365   if (count > 0) {
366     beginRemoveRows(QModelIndex(), row, row + count - 1);
367     for (int i = row; i < row + count; ++i) {
368       m_frames.erase(frameAt(i));
369     }
370     updateFrameRowMapping();
371     resizeFrameSelected();
372     endRemoveRows();
373   }
374   return true;
375 }
376 
377 /**
378  * Map role identifiers to role property names in scripting languages.
379  * @return hash mapping role identifiers to names.
380  */
roleNames() const381 QHash<int,QByteArray> FrameTableModel::roleNames() const
382 {
383   static QHash<int, QByteArray> roles = getRoleHash();
384   return roles;
385 }
386 
387 /**
388  * Get the frame at a specific position in the collection.
389  * @param row position of frame
390  * @return iterator to frame
391  */
frameAt(int row) const392 FrameCollection::iterator FrameTableModel::frameAt(int row) const {
393   return row >= 0 && row < m_frameOfRow.size()
394       ? m_frameOfRow.at(row) : frames().end();
395 }
396 
397 /**
398  * Get the row corresponding to a frame iterator.
399  * @param frameIt frame iterator
400  * @return row number, number of rows if not found.
401  */
rowOf(FrameCollection::iterator frameIt) const402 int FrameTableModel::rowOf(FrameCollection::iterator frameIt) const {
403   int row = 0;
404   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
405     if (frameIt == *it)
406       break;
407     ++row;
408   }
409   return row;
410 }
411 
412 /**
413  * Mark rows.
414  * @param rowMask mask with bits of rows to mark
415  */
markRows(quint64 rowMask)416 void FrameTableModel::markRows(quint64 rowMask)
417 {
418   quint64 changedBits = m_markedRows ^ rowMask;
419   m_markedRows = rowMask;
420 
421   // Emit a change signal for all indexes affected by the change.
422   if (!changedBits)
423     return;
424 
425   quint64 mask;
426   int row;
427   for (mask = 1ULL, row = 0;
428        static_cast<unsigned>(row) < sizeof(changedBits) * 8;
429        mask <<= 1, ++row) {
430     if ((changedBits & mask) != 0) {
431       // Include both the columns for Qt::BackgroundRole and TruncatedRole.
432       emit dataChanged(index(row, 0), index(row, 1));
433     }
434   }
435 }
436 
437 /**
438  * Mark changed frames.
439  * @param frameMask mask with bits of frame types to mark
440  */
markChangedFrames(quint64 frameMask)441 void FrameTableModel::markChangedFrames(quint64 frameMask)
442 {
443   quint64 changedBits = m_changedFrames ^ frameMask;
444   m_changedFrames = frameMask;
445 
446   // Emit a change signal for all indexes affected by the change.
447   if (!FileConfig::instance().markChanges() || !changedBits)
448     return;
449 
450   const FrameCollection& frameCollection = frames();
451   auto it = frameCollection.cbegin();
452   int row = 0;
453   for (; it != frameCollection.cend(); ++it, ++row) {
454     if (it->isValueChanged() ||
455         (static_cast<unsigned>((*it).getType()) < sizeof(changedBits) * 8 &&
456          (changedBits & (1ULL << (*it).getType())) != 0)) {
457       QModelIndex idx = index(row, CI_Enable);
458       emit dataChanged(idx, idx);
459     }
460   }
461 }
462 
463 /**
464  * Get frame for index.
465  * @param index model index
466  * @return frame, 0 if no frame.
467  */
getFrameOfIndex(const QModelIndex & index) const468 const Frame* FrameTableModel::getFrameOfIndex(const QModelIndex& index) const
469 {
470   if (index.isValid() && index.row() < static_cast<int>(frames().size())) {
471     auto it = frameAt(index.row());
472     return &(*it);
473   }
474   return nullptr;
475 }
476 
477 /**
478  * Get row with frame with a specific frame index.
479  * @param index frame index
480  * @return row number, -1 if not found.
481  */
getRowWithFrameIndex(int index) const482 int FrameTableModel::getRowWithFrameIndex(int index) const
483 {
484   int row = 0;
485   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
486     if ((*it)->getIndex() == index) {
487       return row;
488     }
489     ++row;
490   }
491   return -1;
492 }
493 
494 /**
495  * Get row with frame with a specific frame name.
496  * @param name name of frame
497  * @return row number, -1 if not found.
498  */
getRowWithFrameName(const QString & name) const499 int FrameTableModel::getRowWithFrameName(const QString& name) const
500 {
501   int row = 0;
502   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
503     if ((*it)->getName() == name) {
504       return row;
505     }
506     ++row;
507   }
508   return -1;
509 }
510 
511 /**
512  * Get filter with enabled frames.
513  *
514  * @param allDisabledToAllEnabled true to enable all if all are disabled
515  *
516  * @return filter with enabled frames.
517  */
getEnabledFrameFilter(bool allDisabledToAllEnabled) const518 FrameFilter FrameTableModel::getEnabledFrameFilter(
519   bool allDisabledToAllEnabled) const
520 {
521   FrameFilter filter;
522   filter.enableAll();
523   bool allDisabled = true;
524   int numberRows = rowCount();
525   int row = 0;
526   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
527     if (row >= numberRows) break;
528     if (!m_frameSelected.at(row)) {
529       filter.enable((*it)->getType(), (*it)->getName(), false);
530     } else {
531       allDisabled = false;
532     }
533     ++row;
534   }
535   if (allDisabledToAllEnabled && allDisabled) {
536     filter.enableAll();
537   }
538   return filter;
539 }
540 
541 /**
542  * Get enabled frames.
543  * @return frame collection with enabled frames.
544  */
getEnabledFrames() const545 FrameCollection FrameTableModel::getEnabledFrames() const
546 {
547   FrameCollection enabledFrames;
548   const int numberRows = m_frameSelected.size();
549   int row = 0;
550   for (auto it = m_frameOfRow.constBegin(); it != m_frameOfRow.constEnd(); ++it) {
551     if (row >= numberRows) break;
552     if (m_frameSelected.at(row)) {
553       enabledFrames.insert(**it);
554     }
555     ++row;
556   }
557   return enabledFrames;
558 }
559 
560 /**
561  * Clear frame collection.
562  */
clearFrames()563 void FrameTableModel::clearFrames()
564 {
565   const int numFrames = static_cast<int>(m_frames.size());
566   if (numFrames > 0) {
567     beginRemoveRows(QModelIndex(), 0, numFrames - 1);
568     m_frames.clear();
569     updateFrameRowMapping();
570     m_frameSelected.clear();
571     endRemoveRows();
572   }
573 }
574 
575 /**
576  * Transfer frames to frame collection.
577  * @param src frames to move into frame collection, will be cleared
578  */
transferFrames(FrameCollection & src)579 void FrameTableModel::transferFrames(FrameCollection& src)
580 {
581   int oldNumFrames = static_cast<int>(m_frames.size());
582   int newNumFrames = static_cast<int>(src.size());
583   int numRowsChanged = qMin(oldNumFrames, newNumFrames);
584   if (newNumFrames < oldNumFrames)
585     beginRemoveRows(QModelIndex(), newNumFrames, oldNumFrames - 1);
586   else if (newNumFrames > oldNumFrames)
587     beginInsertRows(QModelIndex(), oldNumFrames, newNumFrames - 1);
588 
589   m_frames.clear();
590   src.swap(m_frames);
591   updateFrameRowMapping();
592   resizeFrameSelected();
593 
594   if (newNumFrames < oldNumFrames)
595     endRemoveRows();
596   else if (newNumFrames > oldNumFrames)
597     endInsertRows();
598   if (numRowsChanged > 0)
599     emit dataChanged(index(0, 0), index(numRowsChanged - 1, CI_NumColumns - 1));
600 }
601 
602 /**
603  * Start filtering different values.
604  */
beginFilterDifferent()605 void FrameTableModel::beginFilterDifferent()
606 {
607   m_differentValues.clear();
608 }
609 
610 /**
611  * End filtering different values.
612  */
endFilterDifferent()613 void FrameTableModel::endFilterDifferent()
614 {
615 }
616 
617 /**
618  * Get the different values which have been filtered for a frame type.
619  * @param type frame type
620  * @return different values.
621  */
getCompletionsForType(Frame::ExtendedType type) const622 QSet<QString> FrameTableModel::getCompletionsForType(
623     Frame::ExtendedType type) const
624 {
625   return m_differentValues.value(type);
626 }
627 
628 /**
629  * Set values which are different inactive.
630  *
631  * @param others frames to compare, will be modified
632  */
filterDifferent(FrameCollection & others)633 void FrameTableModel::filterDifferent(FrameCollection& others)
634 {
635   int oldNumFrames = static_cast<int>(m_frames.size());
636 
637   m_frames.filterDifferent(others, &m_differentValues);
638   updateFrameRowMapping();
639   resizeFrameSelected();
640 
641   if (oldNumFrames > 0)
642     emit dataChanged(index(0, 0), index(oldNumFrames - 1, CI_NumColumns - 1));
643   int newNumFrames = static_cast<int>(m_frames.size());
644   if (newNumFrames > oldNumFrames) {
645     beginInsertRows(QModelIndex(), oldNumFrames, newNumFrames - 1);
646     endInsertRows();
647   }
648 }
649 
650 /**
651  * Set the check state of all frames in the table.
652  *
653  * @param checked true to check the frames
654  */
setAllCheckStates(bool checked)655 void FrameTableModel::setAllCheckStates(bool checked)
656 {
657   const int numRows = rowCount();
658   m_frameSelected.fill(checked, 0, numRows);
659   emit dataChanged(index(0, CI_Enable), index(numRows - 1, CI_Enable));
660 }
661 
662 /**
663  * Select all frames in the table.
664  */
selectAllFrames()665 void FrameTableModel::selectAllFrames()
666 {
667   setAllCheckStates(true);
668 }
669 
670 /**
671  * Deselect all frames in the table.
672  */
deselectAllFrames()673 void FrameTableModel::deselectAllFrames()
674 {
675   setAllCheckStates(false);
676 }
677 
678 /**
679  * Select changed frames in the table.
680  */
selectChangedFrames()681 void FrameTableModel::selectChangedFrames()
682 {
683   int row = 0;
684   auto it = m_frameOfRow.constBegin();
685   for (; row < m_frameSelected.size() && it != m_frameOfRow.constEnd();
686        ++row, ++it) {
687     if ((*it)->isValueChanged()) {
688       m_frameSelected[row] = true;
689       QModelIndex idx = index(row, CI_Enable);
690       emit dataChanged(idx, idx);
691     }
692   }
693 }
694 
695 /**
696  * Resize the bit array with the frame selection to match the frames size.
697  */
resizeFrameSelected()698 void FrameTableModel::resizeFrameSelected()
699 {
700   // If all bits are set, set also the new bits.
701   int oldSize = m_frameSelected.size();
702   int newSize = static_cast<int>(frames().size());
703   bool setNewBits = newSize > oldSize && oldSize > 0 &&
704       m_frameSelected.count(true) == oldSize;
705 
706   m_frameSelected.resize(newSize);
707 
708   if (setNewBits) {
709     for (int i = oldSize; i < newSize; ++i) {
710       m_frameSelected.setBit(i, true);
711     }
712   }
713 }
714 
715 /**
716  * Update the frame to row mapping.
717  */
updateFrameRowMapping()718 void FrameTableModel::updateFrameRowMapping()
719 {
720   const FrameCollection& frameCollection = frames();
721   m_frameOfRow.resize(static_cast<int>(frameCollection.size()));
722   auto frameIt = frameCollection.cbegin();
723   auto rowIt = m_frameOfRow.begin(); // clazy:exclude=detaching-member
724   for (; frameIt != frameCollection.cend(); ++frameIt, ++rowIt) {
725     *rowIt = frameIt;
726   }
727   if (!m_frameTypeSeqNr.isEmpty()) {
728     const QVector<int>& frameTypeSeqNr = m_frameTypeSeqNr;
729     std::stable_sort(m_frameOfRow.begin(), m_frameOfRow.end(), // clazy:exclude=detaching-member
730                      [&frameTypeSeqNr](FrameCollection::iterator lhs,
731                                        FrameCollection::iterator rhs) {
732       int lhsType = lhs->getType();
733       int rhsType = rhs->getType();
734       int lhsSeqNr = frameTypeSeqNr.at(lhsType);
735       int rhsSeqNr = frameTypeSeqNr.at(rhsType);
736       return lhsSeqNr < rhsSeqNr ||
737              (lhsType == Frame::FT_Other && lhsType == rhsType &&
738               lhs->getInternalName() < rhs->getInternalName());
739     });
740   }
741 }
742 
743 /**
744  * Set order of frames in frame table.
745  * @param frameTypes ordered sequence of frame types
746  * @remark This order is not used for ID3v1 frames.
747  * @see TagConfig::quickAccessFrameOrder().
748  */
setFrameOrder(const QList<int> & frameTypes)749 void FrameTableModel::setFrameOrder(const QList<int>& frameTypes)
750 {
751   if (frameTypes.isEmpty()) {
752     m_frameTypeSeqNr.clear();
753     return;
754   } else if (frameTypes.size() != Frame::FT_LastFrame + 1) {
755     qWarning("FrameTableModel::setFrameOrder: Invalid parameter size");
756     m_frameTypeSeqNr.clear();
757     return;
758   }
759   m_frameTypeSeqNr.resize(Frame::FT_UnknownFrame + 1);
760   m_frameTypeSeqNr[Frame::FT_UnknownFrame] = Frame::FT_UnknownFrame;
761   m_frameTypeSeqNr[Frame::FT_Other] = Frame::FT_Other;
762 
763   int seqNr = 0;
764   auto it = frameTypes.constBegin();
765   for (; it != frameTypes.constEnd(); ++it, ++seqNr) {
766     int frameType = *it;
767     if (frameType < 0 || frameType > Frame::FT_LastFrame) {
768       qWarning("FrameTableModel::setFrameOrder: Invalid frame type %d",
769                frameType);
770       m_frameTypeSeqNr.clear();
771       return;
772     }
773     m_frameTypeSeqNr[frameType] = seqNr;
774   }
775 }
776