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