File indexing completed on 2024-04-21 04:51:16

0001 /*
0002 SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "projectsortproxymodel.h"
0009 #include "abstractprojectitem.h"
0010 
0011 #include <QItemSelectionModel>
0012 
0013 ProjectSortProxyModel::ProjectSortProxyModel(QObject *parent)
0014     : QSortFilterProxyModel(parent)
0015 {
0016     m_collator.setLocale(QLocale()); // Locale used for sorting → OK
0017     m_collator.setCaseSensitivity(Qt::CaseInsensitive);
0018     m_collator.setNumericMode(true);
0019     m_selection = new QItemSelectionModel(this);
0020     connect(m_selection, &QItemSelectionModel::selectionChanged, this, &ProjectSortProxyModel::onCurrentRowChanged);
0021     setDynamicSortFilter(true);
0022 }
0023 
0024 // Responsible for item sorting!
0025 bool ProjectSortProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0026 {
0027     if (filterAcceptsRowItself(sourceRow, sourceParent)) {
0028         return true;
0029     }
0030     // accept if any of the children is accepted on it's own merits
0031     return hasAcceptedChildren(sourceRow, sourceParent);
0032 }
0033 
0034 bool ProjectSortProxyModel::filterAcceptsRowItself(int sourceRow, const QModelIndex &sourceParent) const
0035 {
0036     if (m_usageFilter != UsageFilter::All) {
0037         // Column 8 contains the usage
0038         QModelIndex indexTag = sourceModel()->index(sourceRow, 8, sourceParent);
0039         int usageCount = sourceModel()->data(indexTag).toInt();
0040         if ((usageCount > 0 && m_usageFilter == UsageFilter::Unused) || (usageCount == 0 && m_usageFilter == UsageFilter::Used)) {
0041             return false;
0042         }
0043     }
0044     bool result = false;
0045     if (!m_searchRating.isEmpty()) {
0046         // Column 7 contains the rating
0047         QModelIndex indexTag = sourceModel()->index(sourceRow, 7, sourceParent);
0048         if (!m_searchRating.contains(sourceModel()->data(indexTag).toInt())) {
0049             return false;
0050         }
0051         result = true;
0052     }
0053     if (!m_searchType.isEmpty()) {
0054         // Column 3 contains the item type (video, image, title, etc)
0055         QModelIndex indexTag = sourceModel()->index(sourceRow, 3, sourceParent);
0056         if (!m_searchType.contains(sourceModel()->data(indexTag).toInt())) {
0057             return false;
0058         }
0059         result = true;
0060     }
0061     if (!m_searchTag.isEmpty()) {
0062         // Column 4 contains the item tag data
0063         QModelIndex indexTag = sourceModel()->index(sourceRow, 4, sourceParent);
0064         auto tagData = sourceModel()->data(indexTag);
0065         bool found = false;
0066         for (const QString &tag : m_searchTag) {
0067             if (tag == QLatin1Char('#')) {
0068                 // a single # means we are looking for clips without tags
0069                 if (tagData.toString().isEmpty()) {
0070                     // a single # means we are looking for clips without tags
0071                     found = true;
0072                     break;
0073                 }
0074             } else if (tagData.toString().contains(tag, Qt::CaseInsensitive)) {
0075                 found = true;
0076                 break;
0077             }
0078         }
0079         if (!found) {
0080             return false;
0081         }
0082         result = true;
0083     }
0084 
0085     if (result && m_searchString.isEmpty()) {
0086         return true;
0087     }
0088     for (int i = 0; i < 3; i++) {
0089         QModelIndex index0 = sourceModel()->index(sourceRow, i, sourceParent);
0090         if (!index0.isValid()) {
0091             return false;
0092         }
0093         auto data = sourceModel()->data(index0);
0094         if (data.toString().contains(m_searchString, Qt::CaseInsensitive)) {
0095             result = true;
0096             break;
0097         }
0098     }
0099     return result;
0100 }
0101 
0102 bool ProjectSortProxyModel::hasAcceptedChildren(int sourceRow, const QModelIndex &source_parent) const
0103 {
0104     QModelIndex item = sourceModel()->index(sourceRow, 0, source_parent);
0105     if (!item.isValid()) {
0106         return false;
0107     }
0108 
0109     // check if there are children
0110     int childCount = item.model()->rowCount(item);
0111     if (childCount == 0) {
0112         return false;
0113     }
0114 
0115     for (int i = 0; i < childCount; ++i) {
0116         if (filterAcceptsRowItself(i, item)) {
0117             return true;
0118         }
0119         // recursive call -> NOTICE that this is depth-first searching, you're probably better off with breadth first search...
0120         if (hasAcceptedChildren(i, item)) {
0121             return true;
0122         }
0123     }
0124     return false;
0125 }
0126 
0127 bool ProjectSortProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0128 {
0129     // Check item type (folder or clip) as defined in projectitemmodel
0130     int leftType = sourceModel()->data(left, AbstractProjectItem::ItemTypeRole).toInt();
0131     int rightType = sourceModel()->data(right, AbstractProjectItem::ItemTypeRole).toInt();
0132     if (leftType == rightType) {
0133         // Special case, sequences folder always at top
0134         if (leftType == AbstractProjectItem::FolderItem) {
0135             if (sourceModel()->data(left, AbstractProjectItem::SequenceFolder).toBool()) {
0136                 return true;
0137             }
0138             if (sourceModel()->data(right, AbstractProjectItem::SequenceFolder).toBool()) {
0139                 return false;
0140             }
0141         }
0142         // Let the normal alphabetical sort happen
0143         const QVariant leftData = sourceModel()->data(left, Qt::DisplayRole);
0144         const QVariant rightData = sourceModel()->data(right, Qt::DisplayRole);
0145 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0146         if (leftData.type() == QVariant::DateTime) {
0147 #else
0148         if (leftData.typeId() == QMetaType::QDateTime) {
0149 #endif
0150             return leftData.toDateTime() < rightData.toDateTime();
0151         }
0152 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0153         if (leftData.type() == QVariant::Int) {
0154 #else
0155         if (leftData.typeId() == QMetaType::Int) {
0156 #endif
0157             return leftData.toInt() < rightData.toInt();
0158         }
0159         return m_collator.compare(leftData.toString(), rightData.toString()) < 0;
0160     }
0161     if (sortOrder() == Qt::AscendingOrder) {
0162         return leftType < rightType;
0163     }
0164     return leftType > rightType;
0165 }
0166 
0167 QItemSelectionModel *ProjectSortProxyModel::selectionModel()
0168 {
0169     return m_selection;
0170 }
0171 
0172 void ProjectSortProxyModel::slotSetSearchString(const QString &str)
0173 {
0174     m_searchString = str;
0175     invalidateFilter();
0176 }
0177 
0178 void ProjectSortProxyModel::slotSetFilters(const QStringList &tagFilters, const QList<int> rateFilters, const QList<int> typeFilters, UsageFilter unusedFilter)
0179 {
0180     m_searchType = typeFilters;
0181     m_searchRating = rateFilters;
0182     m_searchTag = tagFilters;
0183     m_usageFilter = unusedFilter;
0184     invalidateFilter();
0185 }
0186 
0187 void ProjectSortProxyModel::slotClearSearchFilters()
0188 {
0189     m_searchTag.clear();
0190     m_searchRating.clear();
0191     m_searchType.clear();
0192     m_usageFilter = UsageFilter::All;
0193     invalidateFilter();
0194 }
0195 
0196 void ProjectSortProxyModel::onCurrentRowChanged(const QItemSelection &current, const QItemSelection &previous)
0197 {
0198     Q_UNUSED(previous)
0199     // Warning: the "current" parameter only represents the item that was newly selected, but not all selected items
0200     QModelIndexList indexes = m_selection->selectedIndexes();
0201     if (indexes.isEmpty()) {
0202         // No item selected
0203         Q_EMIT selectModel(QModelIndex());
0204         return;
0205     }
0206     if (indexes.contains(m_selection->currentIndex())) {
0207         // Select current item
0208         Q_EMIT selectModel(m_selection->currentIndex());
0209     } else {
0210         QModelIndexList newlySelected = current.indexes();
0211         if (!newlySelected.isEmpty()) {
0212             QModelIndex ix = newlySelected.takeLast();
0213             while (ix.column() != 0 && !newlySelected.isEmpty()) {
0214                 ix = newlySelected.takeLast();
0215             }
0216             if (ix.column() == 0) {
0217                 Q_EMIT selectModel(ix);
0218                 return;
0219             }
0220         } else {
0221             if (!indexes.isEmpty()) {
0222                 QModelIndex ix = indexes.takeLast();
0223                 while (ix.column() != 0 && !indexes.isEmpty()) {
0224                     ix = indexes.takeLast();
0225                 }
0226                 if (ix.column() == 0) {
0227                     Q_EMIT selectModel(ix);
0228                     return;
0229                 }
0230             }
0231         }
0232     }
0233 }
0234 
0235 void ProjectSortProxyModel::slotDataChanged(const QModelIndex &ix1, const QModelIndex &ix2, const QVector<int> &roles)
0236 {
0237     Q_EMIT dataChanged(mapFromSource(ix1), mapFromSource(ix2), roles);
0238 }
0239 
0240 void ProjectSortProxyModel::selectAll(const QModelIndex &rootIndex)
0241 {
0242     QModelIndex topLeft = index(0, 0, rootIndex);
0243     QModelIndex bottomRight = index(rowCount(rootIndex) - 1, columnCount(rootIndex) - 1, rootIndex);
0244     QItemSelection selection(topLeft, bottomRight);
0245     m_selection->select(selection, QItemSelectionModel::Select);
0246 }