]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphinsortfilterproxymodel.cpp
648b6133b66b8a5b103c3912334e10e0690e504c
[dolphin.git] / src / dolphinsortfilterproxymodel.cpp
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 * Copyright (C) 2007 by Rafael Fernández López <ereslibre@gmail.com> *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program 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 *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
21 ***************************************************************************/
22
23 #include "dolphinsortfilterproxymodel.h"
24
25 #ifdef HAVE_NEPOMUK
26 #include <config-nepomuk.h>
27 #include <nepomuk/global.h>
28 #include <nepomuk/resource.h>
29 #endif
30
31 #include <kdirmodel.h>
32 #include <kfileitem.h>
33 #include <kdatetime.h>
34
35 static DolphinView::Sorting sortingTypeTable[] =
36 {
37 DolphinView::SortByName, // KDirModel::Name
38 DolphinView::SortBySize, // KDirModel::Size
39 DolphinView::SortByDate, // KDirModel::ModifiedTime
40 DolphinView::SortByPermissions, // KDirModel::Permissions
41 DolphinView::SortByOwner, // KDirModel::Owner
42 DolphinView::SortByGroup, // KDirModel::Group
43 DolphinView::SortByType // KDirModel::Type
44 #ifdef HAVE_NEPOMUK
45 , DolphinView::SortByRating
46 , DolphinView::SortByTags
47 #endif
48 };
49
50 DolphinSortFilterProxyModel::DolphinSortFilterProxyModel(QObject* parent) :
51 KSortFilterProxyModel(parent),
52 m_sorting(DolphinView::SortByName),
53 m_sortOrder(Qt::AscendingOrder)
54 {
55 setDynamicSortFilter(true);
56
57 // sort by the user visible string for now
58 setSortRole(DolphinView::SortByName);
59 setSortCaseSensitivity(Qt::CaseInsensitive);
60 sort(KDirModel::Name, Qt::AscendingOrder);
61 }
62
63 DolphinSortFilterProxyModel::~DolphinSortFilterProxyModel()
64 {
65 }
66
67 void DolphinSortFilterProxyModel::setSorting(DolphinView::Sorting sorting)
68 {
69 // change the sorting column by keeping the current sort order
70 sort(sorting, m_sortOrder);
71 }
72
73 void DolphinSortFilterProxyModel::setSortOrder(Qt::SortOrder sortOrder)
74 {
75 // change the sort order by keeping the current column
76 sort(m_sorting, sortOrder);
77 }
78
79 void DolphinSortFilterProxyModel::sort(int column, Qt::SortOrder sortOrder)
80 {
81 m_sorting = sortingForColumn(column);
82 m_sortOrder = sortOrder;
83 setSortRole(m_sorting);
84 KSortFilterProxyModel::sort(column, sortOrder);
85 }
86
87 bool DolphinSortFilterProxyModel::hasChildren(const QModelIndex& parent) const
88 {
89 const QModelIndex sourceParent = mapToSource(parent);
90 return sourceModel()->hasChildren(sourceParent);
91 }
92
93 bool DolphinSortFilterProxyModel::canFetchMore(const QModelIndex& parent) const
94 {
95 const QModelIndex sourceParent = mapToSource(parent);
96 return sourceModel()->canFetchMore(sourceParent);
97 }
98
99 DolphinView::Sorting DolphinSortFilterProxyModel::sortingForColumn(int column)
100 {
101 Q_ASSERT(column >= 0);
102 Q_ASSERT(column < static_cast<int>(sizeof(sortingTypeTable) / sizeof(DolphinView::Sorting)));
103 return sortingTypeTable[column];
104 }
105
106 bool DolphinSortFilterProxyModel::lessThanGeneralPurpose(const QModelIndex &left,
107 const QModelIndex &right) const
108 {
109 KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
110
111 const KFileItem* leftFileItem = dirModel->itemForIndex(left);
112 const KFileItem* rightFileItem = dirModel->itemForIndex(right);
113
114 switch (sortRole()) {
115 case DolphinView::SortByName: {
116 QString leftFileName(leftFileItem->name());
117 if (leftFileName.at(0) == '.') {
118 leftFileName = leftFileName.mid(1);
119 }
120
121 QString rightFileName(rightFileItem->name());
122 if (rightFileName.at(0) == '.') {
123 rightFileName = rightFileName.mid(1);
124 }
125
126 // We don't care about case for building categories. We also don't
127 // want here to compare by a natural comparison.
128 return naturalCompare(leftFileName, rightFileName) < 0;
129 }
130
131 case DolphinView::SortBySize:
132 // If we are sorting by size, show folders first. We will sort them
133 // correctly later.
134 return leftFileItem->isDir() && !rightFileItem->isDir();
135
136 case DolphinView::SortByDate: {
137 KDateTime leftTime, rightTime;
138 leftTime.setTime_t(leftFileItem->time(KIO::UDS_MODIFICATION_TIME));
139 rightTime.setTime_t(rightFileItem->time(KIO::UDS_MODIFICATION_TIME));
140 return leftTime > rightTime;
141 }
142
143 case DolphinView::SortByPermissions: {
144 return naturalCompare(leftFileItem->permissionsString(),
145 rightFileItem->permissionsString()) < 0;
146 }
147
148 case DolphinView::SortByOwner: {
149 return naturalCompare(leftFileItem->user().toLower(),
150 rightFileItem->user().toLower()) < 0;
151 }
152
153 case DolphinView::SortByGroup: {
154 return naturalCompare(leftFileItem->group().toLower(),
155 rightFileItem->group().toLower()) < 0;
156 }
157
158 case DolphinView::SortByType: {
159 // If we are sorting by size, show folders first. We will sort them
160 // correctly later.
161 if (leftFileItem->isDir() && !rightFileItem->isDir()) {
162 return true;
163 } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
164 return false;
165 }
166
167 return naturalCompare(leftFileItem->mimeComment().toLower(),
168 rightFileItem->mimeComment().toLower()) < 0;
169 }
170 #ifdef HAVE_NEPOMUK
171 case DolphinView::SortByRating: {
172 const quint32 leftRating = ratingForIndex(left);
173 const quint32 rightRating = ratingForIndex(right);
174 return leftRating > rightRating;
175 }
176 #endif
177 default:
178 break;
179 }
180 return false;
181 }
182
183 bool DolphinSortFilterProxyModel::lessThan(const QModelIndex& left,
184 const QModelIndex& right) const
185 {
186 KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
187
188 const KFileItem* leftFileItem = dirModel->itemForIndex(left);
189 const KFileItem* rightFileItem = dirModel->itemForIndex(right);
190
191 // If we are sorting by rating, folders and files are citizens of the same
192 // class
193 if (sortRole() != DolphinView::SortByRating)
194 {
195 // On our priority, folders go above regular files.
196 if (leftFileItem->isDir() && !rightFileItem->isDir()) {
197 return true;
198 } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
199 return false;
200 }
201 }
202
203 // Hidden elements go before visible ones, if they both are
204 // folders or files.
205 if (leftFileItem->isHidden() && !rightFileItem->isHidden()) {
206 return true;
207 } else if (!leftFileItem->isHidden() && rightFileItem->isHidden()) {
208 return false;
209 }
210
211 switch (sortRole()) {
212 case DolphinView::SortByName: {
213 // So we are in the same priority, what counts now is their names.
214 const QVariant leftData = dirModel->data(left, KDirModel::Name);
215 const QVariant rightData = dirModel->data(right, KDirModel::Name);
216 const QString leftValueString(leftData.toString());
217 const QString rightValueString(rightData.toString());
218
219 return sortCaseSensitivity() ?
220 (naturalCompare(leftValueString, rightValueString) < 0) :
221 (naturalCompare(leftValueString.toLower(), rightValueString.toLower()) < 0);
222 }
223
224 case DolphinView::SortBySize: {
225 // If we have two folders, what we have to measure is the number of
226 // items that contains each other
227 if (leftFileItem->isDir() && rightFileItem->isDir()) {
228 QVariant leftValue = dirModel->data(left, KDirModel::ChildCountRole);
229 int leftCount = leftValue.type() == QVariant::Int ? leftValue.toInt() : KDirModel::ChildCountUnknown;
230
231 QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
232 int rightCount = rightValue.type() == QVariant::Int ? rightValue.toInt() : KDirModel::ChildCountUnknown;
233
234 // In the case they two have the same child items, we sort them by
235 // their names. So we have always everything ordered. We also check
236 // if we are taking in count their cases.
237 if (leftCount == rightCount) {
238 return sortCaseSensitivity() ? (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
239 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
240 }
241
242 // If they had different number of items, we sort them depending
243 // on how many items had each other.
244 return leftCount < rightCount;
245 }
246
247 // If what we are measuring is two files and they have the same size,
248 // sort them by their file names.
249 if (leftFileItem->size() == rightFileItem->size()) {
250 return sortCaseSensitivity() ? (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
251 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
252 }
253
254 // If their sizes are different, sort them by their sizes, as expected.
255 return leftFileItem->size() < rightFileItem->size();
256 }
257
258 case DolphinView::SortByDate: {
259 KDateTime leftTime, rightTime;
260 leftTime.setTime_t(leftFileItem->time(KIO::UDS_MODIFICATION_TIME));
261 rightTime.setTime_t(rightFileItem->time(KIO::UDS_MODIFICATION_TIME));
262
263 if (leftTime == rightTime) {
264 return sortCaseSensitivity() ?
265 (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
266 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
267 }
268
269 return leftTime > rightTime;
270 }
271
272 case DolphinView::SortByPermissions: {
273 if (leftFileItem->permissionsString() == rightFileItem->permissionsString()) {
274 return sortCaseSensitivity() ?
275 (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
276 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
277 }
278
279 return naturalCompare(leftFileItem->permissionsString(),
280 rightFileItem->permissionsString()) < 0;
281 }
282
283 case DolphinView::SortByOwner: {
284 if (leftFileItem->user() == rightFileItem->user()) {
285 return sortCaseSensitivity() ?
286 (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
287 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
288 }
289
290 return naturalCompare(leftFileItem->user(), rightFileItem->user()) < 0;
291 }
292
293 case DolphinView::SortByGroup: {
294 if (leftFileItem->group() == rightFileItem->group()) {
295 return sortCaseSensitivity() ? (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
296 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
297 }
298
299 return naturalCompare(leftFileItem->group(),
300 rightFileItem->group()) < 0;
301 }
302
303 case DolphinView::SortByType: {
304 if (leftFileItem->mimetype() == rightFileItem->mimetype()) {
305 return sortCaseSensitivity() ?
306 (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
307 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
308 }
309
310 return naturalCompare(leftFileItem->mimeComment(),
311 rightFileItem->mimeComment()) < 0;
312 }
313
314 #ifdef HAVE_NEPOMUK
315 case DolphinView::SortByRating: {
316 const quint32 leftRating = ratingForIndex(left);
317 const quint32 rightRating = ratingForIndex(right);
318
319 if (leftRating == rightRating) {
320 // On our priority, folders go above regular files.
321 if (leftFileItem->isDir() && !rightFileItem->isDir()) {
322 return true;
323 } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
324 return false;
325 }
326
327 return sortCaseSensitivity() ?
328 (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
329 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
330 }
331
332 return leftRating > rightRating;
333 }
334 #endif
335 }
336
337 // We have set a SortRole and trust the ProxyModel to do
338 // the right thing for now.
339 return QSortFilterProxyModel::lessThan(left, right);
340 }
341
342 quint32 DolphinSortFilterProxyModel::ratingForIndex(const QModelIndex& index) const
343 {
344 #ifdef HAVE_NEPOMUK
345 quint32 rating = 0;
346
347 const KDirModel* dirModel = static_cast<const KDirModel*>(sourceModel());
348 KFileItem* item = dirModel->itemForIndex(index);
349 if (item != 0) {
350 const Nepomuk::Resource resource(item->url().url(), Nepomuk::NFO::File());
351 rating = resource.rating();
352 }
353 return rating;
354 #else
355 Q_UNUSED(index);
356 return 0;
357 #endif
358 }
359
360 int DolphinSortFilterProxyModel::naturalCompare(const QString& a,
361 const QString& b)
362 {
363 // This method chops the input a and b into pieces of
364 // digits and non-digits (a1.05 becomes a | 1 | . | 05)
365 // and compares these pieces of a and b to each other
366 // (first with first, second with second, ...).
367 //
368 // This is based on the natural sort order code code by Martin Pool
369 // http://sourcefrog.net/projects/natsort/
370 // Martin Pool agreed to license this under LGPL or GPL.
371
372 const QChar* currA = a.unicode(); // iterator over a
373 const QChar* currB = b.unicode(); // iterator over b
374
375 if (currA == currB) {
376 return 0;
377 }
378
379 const QChar* begSeqA = currA; // beginning of a new character sequence of a
380 const QChar* begSeqB = currB;
381
382 while (!currA->isNull() && !currB->isNull()) {
383 // find sequence of characters ending at the first non-character
384 while (!currA->isNull() && !currA->isDigit()) {
385 ++currA;
386 }
387
388 while (!currB->isNull() && !currB->isDigit()) {
389 ++currB;
390 }
391
392 // compare these sequences
393 const QString subA(begSeqA, currA - begSeqA);
394 const QString subB(begSeqB, currB - begSeqB);
395 const int cmp = QString::localeAwareCompare(subA, subB);
396 if (cmp != 0) {
397 return cmp;
398 }
399
400 if (currA->isNull() || currB->isNull()) {
401 break;
402 }
403
404 // now some digits follow...
405 if ((*currA == '0') || (*currB == '0')) {
406 // one digit-sequence starts with 0 -> assume we are in a fraction part
407 // do left aligned comparison (numbers are considered left aligned)
408 while (1) {
409 if (!currA->isDigit() && !currB->isDigit()) {
410 break;
411 } else if (!currA->isDigit()) {
412 return -1;
413 } else if (!currB->isDigit()) {
414 return + 1;
415 } else if (*currA < *currB) {
416 return -1;
417 } else if (*currA > *currB) {
418 return + 1;
419 }
420 ++currA;
421 ++currB;
422 }
423 } else {
424 // No digit-sequence starts with 0 -> assume we are looking at some integer
425 // do right aligned comparison.
426 //
427 // The longest run of digits wins. That aside, the greatest
428 // value wins, but we can't know that it will until we've scanned
429 // both numbers to know that they have the same magnitude.
430
431 int weight = 0;
432 while (1) {
433 if (!currA->isDigit() && !currB->isDigit()) {
434 if (weight != 0) {
435 return weight;
436 }
437 break;
438 } else if (!currA->isDigit()) {
439 return -1;
440 } else if (!currB->isDigit()) {
441 return + 1;
442 } else if ((*currA < *currB) && (weight == 0)) {
443 weight = -1;
444 } else if ((*currA > *currB) && (weight == 0)) {
445 weight = + 1;
446 }
447 ++currA;
448 ++currB;
449 }
450 }
451
452 begSeqA = currA;
453 begSeqB = currB;
454 }
455
456 if (currA->isNull() && currB->isNull()) {
457 return 0;
458 }
459
460 return currA->isNull() ? -1 : + 1;
461 }
462
463 #include "dolphinsortfilterproxymodel.moc"