1 /*
2     SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "slidefiltermodel.h"
8 
9 #include "backgroundlistmodel.h"
10 #include "slidemodel.h"
11 
12 #include <QRandomGenerator>
13 #include <QFileInfo>
14 #include <QDir>
15 
16 #include <algorithm>
17 
SlideFilterModel(QObject * parent)18 SlideFilterModel::SlideFilterModel(QObject *parent)
19     : QSortFilterProxyModel{parent}
20     , m_SortingMode{Image::Random}
21     , m_SortingFoldersFirst{false}
22     , m_usedInConfig{false}
23     , m_random(m_randomDevice())
24 {
25     srand(time(nullptr));
26     setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
27     connect(this, &SlideFilterModel::usedInConfigChanged, this, &SlideFilterModel::invalidateFilter);
28 }
29 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const30 bool SlideFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
31 {
32     auto index = sourceModel()->index(source_row, 0, source_parent);
33     return m_usedInConfig || index.data(BackgroundListModel::ToggleRole).toBool();
34 }
35 
setSourceModel(QAbstractItemModel * sourceModel)36 void SlideFilterModel::setSourceModel(QAbstractItemModel *sourceModel)
37 {
38     if (this->sourceModel()) {
39         disconnect(this->sourceModel(), nullptr, this, nullptr);
40     }
41     QSortFilterProxyModel::setSourceModel(sourceModel);
42     if (m_SortingMode == Image::Random && !m_usedInConfig) {
43         buildRandomOrder();
44     }
45     if (sourceModel) {
46         connect(sourceModel, &QAbstractItemModel::modelReset, this, &SlideFilterModel::buildRandomOrder);
47         connect(sourceModel, &QAbstractItemModel::rowsInserted, this, [this] {
48             if (m_SortingMode != Image::Random || m_usedInConfig) {
49                 return;
50             }
51             const int old_count = m_randomOrder.size();
52             m_randomOrder.resize(this->sourceModel()->rowCount());
53             std::iota(m_randomOrder.begin() + old_count, m_randomOrder.end(), old_count);
54             std::shuffle(m_randomOrder.begin() + old_count, m_randomOrder.end(), m_random);
55         });
56         connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this] {
57             if (m_SortingMode != Image::Random || m_usedInConfig) {
58                 return;
59             }
60             m_randomOrder.erase(std::remove_if(m_randomOrder.begin(),
61                                                m_randomOrder.end(),
62                                                [this](const int v) {
63                                                    return v >= this->sourceModel()->rowCount();
64                                                }),
65                                 m_randomOrder.end());
66         });
67     }
68 }
69 
lessThan(const QModelIndex & source_left,const QModelIndex & source_right) const70 bool SlideFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
71 {
72     Qt::CaseSensitivity cs = Qt::CaseInsensitive;
73 
74     switch (m_SortingMode) {
75     case Image::Random:
76         if (m_usedInConfig) {
77             return source_left.row() < source_right.row();
78         }
79         return m_randomOrder.indexOf(source_left.row()) < m_randomOrder.indexOf(source_right.row());
80     case Image::Alphabetical:
81         if (m_SortingFoldersFirst) {
82             QFileInfo leftFile(getLocalFilePath(source_left));
83             QFileInfo rightFile(getLocalFilePath(source_right));
84             QString leftFilePath = getFilePathWithDir(leftFile);
85             QString rightFilePath = getFilePathWithDir(rightFile);
86 
87             if (leftFilePath == rightFilePath) {
88                 return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) < 0;
89             } else if (leftFilePath.startsWith(rightFilePath, cs)) {
90                 return true;
91             } else if (rightFilePath.startsWith(leftFilePath, cs)) {
92                 return false;
93             } else {
94                 return QString::compare(leftFilePath, rightFilePath, cs) < 0;
95             }
96         } else {
97             QFileInfo leftFile(getLocalFilePath(source_left));
98             QFileInfo rightFile(getLocalFilePath(source_right));
99             return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) < 0;
100         }
101     case Image::AlphabeticalReversed:
102         if (m_SortingFoldersFirst) {
103             QFileInfo leftFile(getLocalFilePath(source_left));
104             QFileInfo rightFile(getLocalFilePath(source_right));
105             QString leftFilePath = getFilePathWithDir(leftFile);
106             QString rightFilePath = getFilePathWithDir(rightFile);
107 
108             if (leftFilePath == rightFilePath) {
109                 return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) > 0;
110             } else if (leftFilePath.startsWith(rightFilePath, cs)) {
111                 return true;
112             } else if (rightFilePath.startsWith(leftFilePath, cs)) {
113                 return false;
114             } else {
115                 return QString::compare(leftFilePath, rightFilePath, cs) > 0;
116             }
117         } else {
118             QFileInfo leftFile(getLocalFilePath(source_left));
119             QFileInfo rightFile(getLocalFilePath(source_right));
120             return QString::compare(leftFile.fileName(), rightFile.fileName(), cs) > 0;
121         }
122     case Image::Modified: // oldest first
123     {
124         QFileInfo leftFile(getLocalFilePath(source_left));
125         QFileInfo rightFile(getLocalFilePath(source_right));
126         return leftFile.lastModified() < rightFile.lastModified();
127     }
128     case Image::ModifiedReversed: // newest first
129     {
130         QFileInfo leftFile(getLocalFilePath(source_left));
131         QFileInfo rightFile(getLocalFilePath(source_right));
132         return !(leftFile.lastModified() < rightFile.lastModified());
133     }
134     }
135     Q_UNREACHABLE();
136 }
137 
setSortingMode(Image::SlideshowMode slideshowMode,bool slideshowFoldersFirst)138 void SlideFilterModel::setSortingMode(Image::SlideshowMode slideshowMode, bool slideshowFoldersFirst)
139 {
140     m_SortingMode = slideshowMode;
141     m_SortingFoldersFirst = slideshowFoldersFirst;
142     if (m_SortingMode == Image::Random && !m_usedInConfig) {
143         buildRandomOrder();
144     }
145     QSortFilterProxyModel::invalidate();
146 }
147 
invalidate()148 void SlideFilterModel::invalidate()
149 {
150     if (m_SortingMode == Image::Random && !m_usedInConfig) {
151         std::shuffle(m_randomOrder.begin(), m_randomOrder.end(), m_random);
152     }
153     QSortFilterProxyModel::invalidate();
154 }
155 
invalidateFilter()156 void SlideFilterModel::invalidateFilter()
157 {
158     QSortFilterProxyModel::invalidateFilter();
159 }
160 
indexOf(const QString & path)161 int SlideFilterModel::indexOf(const QString &path)
162 {
163     auto sourceIndex = sourceModel()->index(static_cast<SlideModel *>(sourceModel())->indexOf(path), 0);
164     return mapFromSource(sourceIndex).row();
165 }
166 
openContainingFolder(int rowIndex)167 void SlideFilterModel::openContainingFolder(int rowIndex)
168 {
169     auto sourceIndex = mapToSource(index(rowIndex, 0));
170     static_cast<SlideModel *>(sourceModel())->openContainingFolder(sourceIndex.row());
171 }
172 
buildRandomOrder()173 void SlideFilterModel::buildRandomOrder()
174 {
175     if (sourceModel()) {
176         m_randomOrder.resize(sourceModel()->rowCount());
177         std::iota(m_randomOrder.begin(), m_randomOrder.end(), 0);
178         std::shuffle(m_randomOrder.begin(), m_randomOrder.end(), m_random);
179     }
180 }
181 
getLocalFilePath(const QModelIndex & modelIndex) const182 QString SlideFilterModel::getLocalFilePath(const QModelIndex& modelIndex) const
183 {
184     return modelIndex.data(BackgroundListModel::PathRole).toUrl().toLocalFile();
185 }
186 
getFilePathWithDir(const QFileInfo & fileInfo) const187 QString SlideFilterModel::getFilePathWithDir(const QFileInfo& fileInfo) const
188 {
189     return fileInfo.canonicalPath().append(QDir::separator());
190 }
191