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