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 }