1/*
2 SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
3 SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
4 SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
5
6 Separated from Dolphin by Nick Shaforostoff <shafff@ukr.net>
7
8 SPDX-License-Identifier: LGPL-2.0-only
9*/
10
11#include "kdirsortfilterproxymodel.h"
12
13#include "defaults-kfile.h"
14
15#include <KConfigGroup>
16#include <KLocalizedString>
17#include <KSharedConfig>
18#include <kdirmodel.h>
19#include <kfileitem.h>
20
21#include <QCollator>
22
23class Q_DECL_HIDDEN KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate
24{
25public:
26 KDirSortFilterProxyModelPrivate();
27
28 int compare(const QString &, const QString &, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive);
29 void slotNaturalSortingChanged();
30
31 bool m_sortFoldersFirst;
32 bool m_sortHiddenFilesLast;
33 bool m_naturalSorting;
34 QCollator m_collator;
35};
36
37KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::KDirSortFilterProxyModelPrivate()
38 : m_sortFoldersFirst(true)
39 , m_sortHiddenFilesLast(DefaultHiddenFilesLast)
40{
41 slotNaturalSortingChanged();
42}
43
44int KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::compare(const QString &a, const QString &b, Qt::CaseSensitivity caseSensitivity)
45{
46 int result;
47
48 if (m_naturalSorting) {
49 m_collator.setCaseSensitivity(caseSensitivity);
50 result = m_collator.compare(s1: a, s2: b);
51 } else {
52 result = QString::compare(s1: a, s2: b, cs: caseSensitivity);
53 }
54
55 if (caseSensitivity == Qt::CaseSensitive || result != 0) {
56 // Only return the result, if the strings are not equal. If they are equal by a case insensitive
57 // comparison, still a deterministic sort order is required. A case sensitive
58 // comparison is done as fallback.
59 return result;
60 }
61
62 return QString::compare(s1: a, s2: b, cs: Qt::CaseSensitive);
63}
64
65void KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::slotNaturalSortingChanged()
66{
67 KConfigGroup g(KSharedConfig::openConfig(), QStringLiteral("KDE"));
68 m_naturalSorting = g.readEntry(key: "NaturalSorting", defaultValue: true);
69 m_collator.setNumericMode(m_naturalSorting);
70}
71
72KDirSortFilterProxyModel::KDirSortFilterProxyModel(QObject *parent)
73 : KCategorizedSortFilterProxyModel(parent)
74 , d(new KDirSortFilterProxyModelPrivate)
75{
76 setDynamicSortFilter(true);
77
78 // sort by the user visible string for now
79 setSortCaseSensitivity(Qt::CaseInsensitive);
80 sort(column: KDirModel::Name, order: Qt::AscendingOrder);
81}
82
83Qt::DropActions KDirSortFilterProxyModel::supportedDragOptions() const
84{
85 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction;
86}
87
88KDirSortFilterProxyModel::~KDirSortFilterProxyModel() = default;
89
90bool KDirSortFilterProxyModel::hasChildren(const QModelIndex &parent) const
91{
92 const QModelIndex sourceParent = mapToSource(proxyIndex: parent);
93 return sourceModel()->hasChildren(parent: sourceParent);
94}
95
96bool KDirSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const
97{
98 const QModelIndex sourceParent = mapToSource(proxyIndex: parent);
99 return sourceModel()->canFetchMore(parent: sourceParent);
100}
101
102int KDirSortFilterProxyModel::pointsForPermissions(const QFileInfo &info)
103{
104 int points = 0;
105
106 const QFile::Permission permissionsCheck[] = {QFile::ReadUser,
107 QFile::WriteUser,
108 QFile::ExeUser,
109 QFile::ReadGroup,
110 QFile::WriteGroup,
111 QFile::ExeGroup,
112 QFile::ReadOther,
113 QFile::WriteOther,
114 QFile::ExeOther};
115
116 for (QFile::Permission perm : permissionsCheck) {
117 points += info.permission(permissions: perm) ? 1 : 0;
118 }
119
120 return points;
121}
122
123void KDirSortFilterProxyModel::setSortFoldersFirst(bool foldersFirst)
124{
125 if (d->m_sortFoldersFirst == foldersFirst) {
126 return;
127 }
128
129 d->m_sortFoldersFirst = foldersFirst;
130 invalidate();
131}
132
133bool KDirSortFilterProxyModel::sortFoldersFirst() const
134{
135 return d->m_sortFoldersFirst;
136}
137
138void KDirSortFilterProxyModel::setSortHiddenFilesLast(bool hiddenFilesLast)
139{
140 if (d->m_sortHiddenFilesLast == hiddenFilesLast) {
141 return;
142 }
143
144 d->m_sortHiddenFilesLast = hiddenFilesLast;
145 invalidate();
146}
147
148bool KDirSortFilterProxyModel::sortHiddenFilesLast() const
149{
150 return d->m_sortHiddenFilesLast;
151}
152
153bool KDirSortFilterProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
154{
155 KDirModel *dirModel = static_cast<KDirModel *>(sourceModel());
156
157 const KFileItem leftFileItem = dirModel->itemForIndex(index: left);
158 const KFileItem rightFileItem = dirModel->itemForIndex(index: right);
159
160 const bool isLessThan = (sortOrder() == Qt::AscendingOrder);
161
162 // Show hidden files and folders last
163 if (d->m_sortHiddenFilesLast) {
164 const bool leftItemIsHidden = leftFileItem.isHidden();
165 const bool rightItemIsHidden = rightFileItem.isHidden();
166 if (leftItemIsHidden && !rightItemIsHidden) {
167 return !isLessThan;
168 } else if (!leftItemIsHidden && rightItemIsHidden) {
169 return isLessThan;
170 }
171 }
172
173 // Folders go before files if the corresponding setting is set.
174 if (d->m_sortFoldersFirst) {
175 const bool leftItemIsDir = leftFileItem.isDir();
176 const bool rightItemIsDir = rightFileItem.isDir();
177 if (leftItemIsDir && !rightItemIsDir) {
178 return isLessThan;
179 } else if (!leftItemIsDir && rightItemIsDir) {
180 return !isLessThan;
181 }
182 }
183
184 switch (left.column()) {
185 case KDirModel::Name: {
186 int result = d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity());
187 if (result == 0) {
188 // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
189 result = d->compare(a: leftFileItem.name(lowerCase: sortCaseSensitivity() == Qt::CaseInsensitive),
190 b: rightFileItem.name(lowerCase: sortCaseSensitivity() == Qt::CaseInsensitive),
191 caseSensitivity: sortCaseSensitivity());
192 if (result == 0) {
193 // If KFileItem::text() is also not unique most probably a search protocol is used
194 // that allows showing the same file names from different directories
195 result = d->compare(a: leftFileItem.url().toString(), b: rightFileItem.url().toString(), caseSensitivity: sortCaseSensitivity());
196 }
197 }
198
199 return result < 0;
200 }
201
202 case KDirModel::Size: {
203 // If we have two folders, what we have to measure is the number of
204 // items that contains each other
205 if (leftFileItem.isDir() && rightFileItem.isDir()) {
206 QVariant leftValue = dirModel->data(index: left, role: KDirModel::ChildCountRole);
207 int leftCount = (leftValue.typeId() == QMetaType::Int) ? leftValue.toInt() : KDirModel::ChildCountUnknown;
208
209 QVariant rightValue = dirModel->data(index: right, role: KDirModel::ChildCountRole);
210 int rightCount = (rightValue.typeId() == QMetaType::Int) ? rightValue.toInt() : KDirModel::ChildCountUnknown;
211
212 // In the case they two have the same child items, we sort them by
213 // their names. So we have always everything ordered. We also check
214 // if we are taking in count their cases.
215 if (leftCount == rightCount) {
216 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
217 }
218
219 // If one of them has unknown child items, place them on the end. If we
220 // were comparing two unknown childed items, the previous comparison
221 // sorted them by QCollator between them. This case is when we
222 // have an unknown childed item, and another known.
223 if (leftCount == KDirModel::ChildCountUnknown) {
224 return false;
225 }
226
227 if (rightCount == KDirModel::ChildCountUnknown) {
228 return true;
229 }
230
231 // If they had different number of items, we sort them depending
232 // on how many items had each other.
233 return leftCount < rightCount;
234 }
235
236 // If what we are measuring is two files and they have the same size,
237 // sort them by their file names.
238 if (leftFileItem.size() == rightFileItem.size()) {
239 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
240 }
241
242 // If their sizes are different, sort them by their sizes, as expected.
243 return leftFileItem.size() < rightFileItem.size();
244 }
245
246 case KDirModel::ModifiedTime: {
247 QDateTime leftModifiedTime = leftFileItem.time(which: KFileItem::ModificationTime).toLocalTime();
248 QDateTime rightModifiedTime = rightFileItem.time(which: KFileItem::ModificationTime).toLocalTime();
249
250 if (leftModifiedTime == rightModifiedTime) {
251 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
252 }
253
254 return leftModifiedTime < rightModifiedTime;
255 }
256
257 case KDirModel::Permissions: {
258 const int leftPermissions = leftFileItem.permissions();
259 const int rightPermissions = rightFileItem.permissions();
260
261 if (leftPermissions == rightPermissions) {
262 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
263 }
264
265 return leftPermissions > rightPermissions;
266 }
267
268 case KDirModel::Owner: {
269 if (leftFileItem.user() == rightFileItem.user()) {
270 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
271 }
272
273 return d->compare(a: leftFileItem.user(), b: rightFileItem.user()) < 0;
274 }
275
276 case KDirModel::Group: {
277 if (leftFileItem.group() == rightFileItem.group()) {
278 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
279 }
280
281 return d->compare(a: leftFileItem.group(), b: rightFileItem.group()) < 0;
282 }
283
284 case KDirModel::Type: {
285 if (leftFileItem.mimetype() == rightFileItem.mimetype()) {
286 return d->compare(a: leftFileItem.text(), b: rightFileItem.text(), caseSensitivity: sortCaseSensitivity()) < 0;
287 }
288
289 return d->compare(a: leftFileItem.mimeComment(), b: rightFileItem.mimeComment()) < 0;
290 }
291 }
292
293 // We have set a SortRole and trust the ProxyModel to do
294 // the right thing for now.
295 return KCategorizedSortFilterProxyModel::subSortLessThan(left, right);
296}
297
298#include "moc_kdirsortfilterproxymodel.cpp"
299

source code of kio/src/filewidgets/kdirsortfilterproxymodel.cpp