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 ¤t, 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 }