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