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