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