File indexing completed on 2024-11-10 05:02:45
0001 /* 0002 SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "slidefiltermodel.h" 0008 0009 #include "slidemodel.h" 0010 0011 #include <QDateTime> 0012 #include <QDir> 0013 #include <QFileInfo> 0014 #include <QRandomGenerator> 0015 0016 #include <KIO/OpenFileManagerWindowJob> 0017 0018 #include <algorithm> 0019 0020 namespace 0021 { 0022 inline QString getLocalFilePath(const QModelIndex &modelIndex) 0023 { 0024 return modelIndex.data(ImageRoles::PathRole).toUrl().toLocalFile(); 0025 } 0026 0027 inline QString getFilePathWithDir(const QFileInfo &fileInfo) 0028 { 0029 return fileInfo.canonicalPath().append(QDir::separator()); 0030 } 0031 } 0032 0033 SlideFilterModel::SlideFilterModel(const QBindable<bool> &usedInConfig, 0034 const QBindable<SortingMode::Mode> &sortingMode, 0035 const QBindable<bool> &slideshowFoldersFirst, 0036 QObject *parent) 0037 : QSortFilterProxyModel{parent} 0038 , m_SortingMode(sortingMode.makeBinding()) 0039 , m_SortingFoldersFirst(slideshowFoldersFirst.makeBinding()) 0040 , m_usedInConfig(usedInConfig.makeBinding()) 0041 , m_random(m_randomDevice()) 0042 { 0043 srand(time(nullptr)); 0044 setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); 0045 m_usedInConfigNotifier = m_usedInConfig.addNotifier([this] { 0046 invalidateRowsFilter(); 0047 }); 0048 0049 auto sortCallback = [this] { 0050 if (m_SortingMode == SortingMode::Random && !m_usedInConfig) { 0051 buildRandomOrder(); 0052 } 0053 QSortFilterProxyModel::invalidate(); 0054 sort(0); 0055 }; 0056 m_SortingModeNotifier = m_SortingMode.addNotifier(sortCallback); 0057 m_slideshowFoldersFirstNotifier = m_SortingFoldersFirst.addNotifier(sortCallback); 0058 } 0059 0060 QHash<int, QByteArray> SlideFilterModel::roleNames() const 0061 { 0062 if (sourceModel()) { 0063 return sourceModel()->roleNames(); 0064 } 0065 0066 return QSortFilterProxyModel::roleNames(); 0067 } 0068 0069 bool SlideFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0070 { 0071 auto index = sourceModel()->index(source_row, 0, source_parent); 0072 return m_usedInConfig || index.data(ImageRoles::ToggleRole).toBool(); 0073 } 0074 0075 void SlideFilterModel::setSourceModel(QAbstractItemModel *sourceModel) 0076 { 0077 if (this->sourceModel()) { 0078 disconnect(this->sourceModel(), nullptr, this, nullptr); 0079 // If the new model and the old model is the same model, QSortFilterProxyModel will not invalidate the filter 0080 QSortFilterProxyModel::setSourceModel(nullptr); 0081 } 0082 if (sourceModel) { 0083 connect(sourceModel, &QAbstractItemModel::modelReset, this, &SlideFilterModel::buildRandomOrder); 0084 connect(sourceModel, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &, int first, int last) { 0085 if (m_SortingMode != SortingMode::Random || m_usedInConfig) { 0086 return; 0087 } 0088 const int old_count = m_randomOrder.size(); 0089 if (first < old_count /* Not appended to end */) { 0090 // Increase the existing row numbers that >= first to make space for new items 0091 for (auto &row : m_randomOrder) { 0092 if (row >= first) { 0093 row += last - first + 1; 0094 } 0095 } 0096 } 0097 // Then append new row numbers 0098 m_randomOrder.resize(this->sourceModel()->rowCount()); 0099 std::iota(std::next(m_randomOrder.begin(), old_count), m_randomOrder.end(), first); // first to last 0100 std::shuffle(std::next(m_randomOrder.begin(), old_count), m_randomOrder.end(), m_random); 0101 }); 0102 connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this](const QModelIndex &, int first, int last) { 0103 if (m_SortingMode != SortingMode::Random || m_usedInConfig) { 0104 return; 0105 } 0106 0107 const int old_count = m_randomOrder.size(); 0108 m_randomOrder.erase(std::remove_if(m_randomOrder.begin(), 0109 m_randomOrder.end(), 0110 [first, last](int v) { 0111 return v >= first && v <= last; 0112 }), 0113 m_randomOrder.end()); 0114 0115 if (last + 1 < old_count /* Not the last one */) { 0116 // Decrease the remaining row numbers that > last 0117 for (auto &row : m_randomOrder) { 0118 if (row > last) { 0119 row -= last - first + 1; 0120 } 0121 } 0122 } 0123 }); 0124 } 0125 // Update random order before QSortFilterProxyModel sorts 0126 QSortFilterProxyModel::setSourceModel(sourceModel); 0127 if (m_SortingMode == SortingMode::Random && !m_usedInConfig) { 0128 buildRandomOrder(); 0129 } 0130 } 0131 0132 bool SlideFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const 0133 { 0134 constexpr Qt::CaseSensitivity cs = Qt::CaseInsensitive; 0135 0136 switch (m_SortingMode) { 0137 case SortingMode::Random: 0138 if (m_usedInConfig) { 0139 return source_left.row() < source_right.row(); 0140 } 0141 return m_randomOrder.indexOf(source_left.row()) < m_randomOrder.indexOf(source_right.row()); 0142 case SortingMode::Alphabetical: 0143 if (m_SortingFoldersFirst) { 0144 QFileInfo leftFile(getLocalFilePath(source_left)); 0145 QFileInfo rightFile(getLocalFilePath(source_right)); 0146 QString leftFilePath = getFilePathWithDir(leftFile); 0147 QString rightFilePath = getFilePathWithDir(rightFile); 0148 0149 if (leftFilePath == rightFilePath) { 0150 return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) < 0; 0151 } else if (leftFilePath.startsWith(rightFilePath, cs)) { 0152 return true; 0153 } else if (rightFilePath.startsWith(leftFilePath, cs)) { 0154 return false; 0155 } else { 0156 return QString::compare(leftFilePath, rightFilePath, cs) < 0; 0157 } 0158 } else { 0159 QFileInfo leftFile(getLocalFilePath(source_left)); 0160 QFileInfo rightFile(getLocalFilePath(source_right)); 0161 return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) < 0; 0162 } 0163 case SortingMode::AlphabeticalReversed: 0164 if (m_SortingFoldersFirst) { 0165 QFileInfo leftFile(getLocalFilePath(source_left)); 0166 QFileInfo rightFile(getLocalFilePath(source_right)); 0167 QString leftFilePath = getFilePathWithDir(leftFile); 0168 QString rightFilePath = getFilePathWithDir(rightFile); 0169 0170 if (leftFilePath == rightFilePath) { 0171 return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) > 0; 0172 } else if (leftFilePath.startsWith(rightFilePath, cs)) { 0173 return true; 0174 } else if (rightFilePath.startsWith(leftFilePath, cs)) { 0175 return false; 0176 } else { 0177 return QString::compare(leftFilePath, rightFilePath, cs) > 0; 0178 } 0179 } else { 0180 QFileInfo leftFile(getLocalFilePath(source_left)); 0181 QFileInfo rightFile(getLocalFilePath(source_right)); 0182 return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) > 0; 0183 } 0184 case SortingMode::Modified: // oldest first 0185 { 0186 QFileInfo leftFile(getLocalFilePath(source_left)); 0187 QFileInfo rightFile(getLocalFilePath(source_right)); 0188 return leftFile.lastModified() < rightFile.lastModified(); 0189 } 0190 case SortingMode::ModifiedReversed: // newest first 0191 { 0192 QFileInfo leftFile(getLocalFilePath(source_left)); 0193 QFileInfo rightFile(getLocalFilePath(source_right)); 0194 return !(leftFile.lastModified() < rightFile.lastModified()); 0195 } 0196 } 0197 Q_UNREACHABLE(); 0198 } 0199 0200 void SlideFilterModel::invalidate() 0201 { 0202 if (m_SortingMode == SortingMode::Random && !m_usedInConfig) { 0203 std::shuffle(m_randomOrder.begin(), m_randomOrder.end(), m_random); 0204 } 0205 QSortFilterProxyModel::invalidate(); 0206 sort(0); 0207 } 0208 0209 void SlideFilterModel::invalidateFilter() 0210 { 0211 QSortFilterProxyModel::invalidateFilter(); 0212 } 0213 0214 int SlideFilterModel::indexOf(const QString &path) 0215 { 0216 if (!sourceModel()) 0217 return -1; 0218 0219 auto sourceIndex = sourceModel()->index(static_cast<SlideModel *>(sourceModel())->indexOf(path), 0); 0220 return mapFromSource(sourceIndex).row(); 0221 } 0222 0223 void SlideFilterModel::openContainingFolder(int rowIndex) 0224 { 0225 KIO::highlightInFileManager({index(rowIndex, 0).data(ImageRoles::PathRole).toUrl()}); 0226 } 0227 0228 void SlideFilterModel::buildRandomOrder() 0229 { 0230 if (sourceModel()) { 0231 m_randomOrder.resize(sourceModel()->rowCount()); 0232 std::iota(m_randomOrder.begin(), m_randomOrder.end(), 0); 0233 std::shuffle(m_randomOrder.begin(), m_randomOrder.end(), m_random); 0234 } 0235 }