1/*
2 Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at>
3 Copyright (C) 2006 by Dominic Battre <dominic@battre.de>
4 Copyright (C) 2006 by Martin Pool <mbp@canonical.com>
5
6 Separated from Dolphin by Nick Shaforostoff <shafff@ukr.net>
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License version 2 as published by the Free Software Foundation.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23#include "kdirsortfilterproxymodel.h"
24
25#include <kdatetime.h>
26#include <kdirmodel.h>
27#include <kfileitem.h>
28#include <kglobalsettings.h>
29#include <klocale.h>
30#include <kstringhandler.h>
31
32
33class KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate
34{
35public:
36 KDirSortFilterProxyModelPrivate(KDirSortFilterProxyModel* q);
37
38 int compare(const QString&, const QString&, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive) const;
39 void slotNaturalSortingChanged();
40
41 bool m_sortFoldersFirst;
42 bool m_naturalSorting;
43};
44
45KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::KDirSortFilterProxyModelPrivate(KDirSortFilterProxyModel* q) :
46 m_sortFoldersFirst(true),
47 m_naturalSorting(KGlobalSettings::naturalSorting())
48{
49 connect(KGlobalSettings::self(), SIGNAL(naturalSortingChanged()),
50 q, SLOT(slotNaturalSortingChanged()));
51}
52
53int KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::compare(const QString& a,
54 const QString& b,
55 Qt::CaseSensitivity caseSensitivity) const
56{
57 if (caseSensitivity == Qt::CaseInsensitive) {
58 const int result = m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseInsensitive)
59 : QString::compare(a, b, Qt::CaseInsensitive);
60 if (result != 0) {
61 // Only return the result, if the strings are not equal. If they are equal by a case insensitive
62 // comparison, still a deterministic sort order is required. A case sensitive
63 // comparison is done as fallback.
64 return result;
65 }
66 }
67
68 return m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseSensitive)
69 : QString::compare(a, b, Qt::CaseSensitive);
70}
71
72
73void KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::slotNaturalSortingChanged()
74{
75 m_naturalSorting = KGlobalSettings::naturalSorting();
76}
77
78KDirSortFilterProxyModel::KDirSortFilterProxyModel(QObject* parent)
79 : KCategorizedSortFilterProxyModel(parent), d(new KDirSortFilterProxyModelPrivate(this))
80{
81 setDynamicSortFilter(true);
82
83 // sort by the user visible string for now
84 setSortCaseSensitivity(Qt::CaseInsensitive);
85 sort(KDirModel::Name, Qt::AscendingOrder);
86
87 setSupportedDragActions(Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction);
88}
89
90KDirSortFilterProxyModel::~KDirSortFilterProxyModel()
91{
92 delete d;
93}
94
95bool KDirSortFilterProxyModel::hasChildren(const QModelIndex& parent) const
96{
97 const QModelIndex sourceParent = mapToSource(parent);
98 return sourceModel()->hasChildren(sourceParent);
99}
100
101bool KDirSortFilterProxyModel::canFetchMore(const QModelIndex& parent) const
102{
103 const QModelIndex sourceParent = mapToSource(parent);
104 return sourceModel()->canFetchMore(sourceParent);
105}
106
107int KDirSortFilterProxyModel::pointsForPermissions(const QFileInfo &info)
108{
109 int points = 0;
110
111 QFile::Permission permissionsCheck[] = { QFile::ReadUser,
112 QFile::WriteUser,
113 QFile::ExeUser,
114 QFile::ReadGroup,
115 QFile::WriteGroup,
116 QFile::ExeGroup,
117 QFile::ReadOther,
118 QFile::WriteOther,
119 QFile::ExeOther };
120
121 for (int i = 0; i < 9; i++) {
122 points += info.permission(permissionsCheck[i]) ? 1 : 0;
123 }
124
125 return points;
126}
127
128void KDirSortFilterProxyModel::setSortFoldersFirst(bool foldersFirst)
129{
130 d->m_sortFoldersFirst = foldersFirst;
131}
132
133bool KDirSortFilterProxyModel::sortFoldersFirst() const
134{
135 return d->m_sortFoldersFirst;
136}
137
138bool KDirSortFilterProxyModel::subSortLessThan(const QModelIndex& left,
139 const QModelIndex& right) const
140{
141 KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
142
143 const KFileItem leftFileItem = dirModel->itemForIndex(left);
144 const KFileItem rightFileItem = dirModel->itemForIndex(right);
145
146 const bool isLessThan = (sortOrder() == Qt::AscendingOrder);
147
148 // Folders go before files if the corresponding setting is set.
149 if (d->m_sortFoldersFirst) {
150 const bool leftItemIsDir = leftFileItem.isDir();
151 const bool rightItemIsDir = rightFileItem.isDir();
152 if (leftItemIsDir && !rightItemIsDir) {
153 return isLessThan;
154 } else if (!leftItemIsDir && rightItemIsDir) {
155 return !isLessThan;
156 }
157 }
158
159
160 // Hidden elements go before visible ones.
161 const bool leftItemIsHidden = leftFileItem.isHidden();
162 const bool rightItemIsHidden = rightFileItem.isHidden();
163 if (leftItemIsHidden && !rightItemIsHidden) {
164 return isLessThan;
165 } else if (!leftItemIsHidden && rightItemIsHidden) {
166 return !isLessThan;
167 }
168
169 switch (left.column()) {
170 case KDirModel::Name: {
171 int result = d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity());
172 if (result == 0) {
173 // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
174 result = d->compare(leftFileItem.name(sortCaseSensitivity() == Qt::CaseInsensitive),
175 rightFileItem.name(sortCaseSensitivity() == Qt::CaseInsensitive),
176 sortCaseSensitivity());
177 if (result == 0) {
178 // If KFileItem::text() is also not unique most probably a search protocol is used
179 // that allows showing the same file names from different directories
180 result = d->compare(leftFileItem.url().url(), rightFileItem.url().url(), sortCaseSensitivity());
181 }
182 }
183
184 return result < 0;
185 }
186
187 case KDirModel::Size: {
188 // If we have two folders, what we have to measure is the number of
189 // items that contains each other
190 if (leftFileItem.isDir() && rightFileItem.isDir()) {
191 QVariant leftValue = dirModel->data(left, KDirModel::ChildCountRole);
192 int leftCount = (leftValue.type() == QVariant::Int) ? leftValue.toInt() : KDirModel::ChildCountUnknown;
193
194 QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
195 int rightCount = (rightValue.type() == QVariant::Int) ? rightValue.toInt() : KDirModel::ChildCountUnknown;
196
197 // In the case they two have the same child items, we sort them by
198 // their names. So we have always everything ordered. We also check
199 // if we are taking in count their cases.
200 if (leftCount == rightCount) {
201 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
202 }
203
204 // If one of them has unknown child items, place them on the end. If we
205 // were comparing two unknown childed items, the previous comparation
206 // sorted them by naturalCompare between them. This case is when we
207 // have an unknown childed item, and another known.
208 if (leftCount == KDirModel::ChildCountUnknown) {
209 return false;
210 }
211
212 if (rightCount == KDirModel::ChildCountUnknown) {
213 return true;
214 }
215
216 // If they had different number of items, we sort them depending
217 // on how many items had each other.
218 return leftCount < rightCount;
219 }
220
221 // If what we are measuring is two files and they have the same size,
222 // sort them by their file names.
223 if (leftFileItem.size() == rightFileItem.size()) {
224 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
225 }
226
227 // If their sizes are different, sort them by their sizes, as expected.
228 return leftFileItem.size() < rightFileItem.size();
229 }
230
231 case KDirModel::ModifiedTime: {
232 KDateTime leftModifiedTime = leftFileItem.time(KFileItem::ModificationTime).toLocalZone();
233 KDateTime rightModifiedTime = rightFileItem.time(KFileItem::ModificationTime).toLocalZone();
234
235 if (leftModifiedTime == rightModifiedTime) {
236 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
237 }
238
239 return leftModifiedTime < rightModifiedTime;
240 }
241
242 case KDirModel::Permissions: {
243 // ### You can't use QFileInfo on urls!! Use the KFileItem instead.
244 QFileInfo leftFileInfo(leftFileItem.url().pathOrUrl());
245 QFileInfo rightFileInfo(rightFileItem.url().pathOrUrl());
246
247 int leftPermissionsPoints = pointsForPermissions(leftFileInfo);
248 int rightPermissionsPoints = pointsForPermissions(rightFileInfo);
249
250 if (leftPermissionsPoints == rightPermissionsPoints) {
251 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
252 }
253
254 return leftPermissionsPoints > rightPermissionsPoints;
255 }
256
257 case KDirModel::Owner: {
258 if (leftFileItem.user() == rightFileItem.user()) {
259 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
260 }
261
262 return d->compare(leftFileItem.user(), rightFileItem.user()) < 0;
263 }
264
265 case KDirModel::Group: {
266 if (leftFileItem.group() == rightFileItem.group()) {
267 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
268 }
269
270 return d->compare(leftFileItem.group(), rightFileItem.group()) < 0;
271 }
272
273 case KDirModel::Type: {
274 if (leftFileItem.mimetype() == rightFileItem.mimetype()) {
275 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
276 }
277
278 return d->compare(leftFileItem.mimeComment(), rightFileItem.mimeComment()) < 0;
279 }
280
281 }
282
283 // We have set a SortRole and trust the ProxyModel to do
284 // the right thing for now.
285 return KCategorizedSortFilterProxyModel::subSortLessThan(left, right);
286}
287
288#include "kdirsortfilterproxymodel.moc"
289