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> *
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. *
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. *
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 ***************************************************************************/
23 #include "dolphinsortfilterproxymodel.h"
25 #include <kdirmodel.h>
26 #include <kfileitem.h>
27 #include <kdatetime.h>
29 static const int dolphinMapSize
= 7;
30 static int dolphinViewToDirModelColumn
[] =
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
41 static DolphinView::Sorting dirModelColumnToDolphinView
[] =
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
53 DolphinSortFilterProxyModel::DolphinSortFilterProxyModel(QObject
* parent
) :
54 KSortFilterProxyModel(parent
),
56 m_sorting(DolphinView::SortByName
),
57 m_sortOrder(Qt::AscendingOrder
)
59 setDynamicSortFilter(true);
61 // sort by the user visible string for now
62 setSortRole(DolphinView::SortByName
);
63 setSortCaseSensitivity(Qt::CaseInsensitive
);
64 sort(KDirModel::Name
, Qt::AscendingOrder
);
67 DolphinSortFilterProxyModel::~DolphinSortFilterProxyModel()
70 void DolphinSortFilterProxyModel::setSorting(DolphinView::Sorting sorting
)
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
)],
79 void DolphinSortFilterProxyModel::setSortOrder(Qt::SortOrder sortOrder
)
81 // change the sort order by keeping the current column
82 sort(dolphinViewToDirModelColumn
[m_sorting
], sortOrder
);
85 void DolphinSortFilterProxyModel::sort(int column
, Qt::SortOrder sortOrder
)
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
);
96 bool DolphinSortFilterProxyModel::hasChildren(const QModelIndex
& parent
) const
98 const QModelIndex sourceParent
= mapToSource(parent
);
99 return sourceModel()->hasChildren(sourceParent
);
102 bool DolphinSortFilterProxyModel::canFetchMore(const QModelIndex
& parent
) const
104 const QModelIndex sourceParent
= mapToSource(parent
);
105 return sourceModel()->canFetchMore(sourceParent
);
108 DolphinView::Sorting
DolphinSortFilterProxyModel::sortingForColumn(int column
)
110 if ((column
>= 0) && (column
< dolphinMapSize
)) {
111 return dirModelColumnToDolphinView
[column
];
113 return DolphinView::SortByName
;
116 bool DolphinSortFilterProxyModel::lessThanGeneralPurpose(const QModelIndex
&left
,
117 const QModelIndex
&right
) const
119 KDirModel
* dirModel
= static_cast<KDirModel
*>(sourceModel());
121 const KFileItem
*leftFileItem
= dirModel
->itemForIndex(left
);
122 const KFileItem
*rightFileItem
= dirModel
->itemForIndex(right
);
124 if (sortRole() == DolphinView::SortByName
) { // If we are sorting by name
125 QString leftFileName
= leftFileItem
->name();
126 QString rightFileName
= rightFileItem
->name();
128 leftFileName
= leftFileName
.at(0) == '.' ? leftFileName
.mid(1) :
131 rightFileName
= rightFileName
.at(0) == '.' ? rightFileName
.mid(1) :
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;
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
141 if (leftFileItem
->isDir() && !rightFileItem
->isDir())
146 else if (sortRole() == DolphinView::SortByDate
) {
148 leftTime
.setTime_t(leftFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
150 rightTime
.setTime_t(rightFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
152 return leftTime
> rightTime
;
154 else if (sortRole() == DolphinView::SortByPermissions
) {
155 return naturalCompare(leftFileItem
->permissionsString(),
156 rightFileItem
->permissionsString()) < 0;
158 else if (sortRole() == DolphinView::SortByOwner
) {
159 return naturalCompare(leftFileItem
->user().toLower(),
160 rightFileItem
->user().toLower()) < 0;
162 else if (sortRole() == DolphinView::SortByGroup
) {
163 return naturalCompare(leftFileItem
->group().toLower(),
164 rightFileItem
->group().toLower()) < 0;
166 else if (sortRole() == DolphinView::SortByType
) {
167 // If we are sorting by size, show folders first. We will sort them
169 if (leftFileItem
->isDir() && !rightFileItem
->isDir())
171 else if (!leftFileItem
->isDir() && rightFileItem
->isDir())
174 return naturalCompare(leftFileItem
->mimeComment().toLower(),
175 rightFileItem
->mimeComment().toLower()) < 0;
179 bool DolphinSortFilterProxyModel::lessThan(const QModelIndex
& left
,
180 const QModelIndex
& right
) const
182 KDirModel
* dirModel
= static_cast<KDirModel
*>(sourceModel());
184 QVariant leftData
= dirModel
->data(left
, sortRole());
185 QVariant rightData
= dirModel
->data(right
, sortRole());
187 const KFileItem
*leftFileItem
= dirModel
->itemForIndex(left
);
188 const KFileItem
*rightFileItem
= dirModel
->itemForIndex(right
);
190 if (sortRole() == DolphinView::SortByName
) { // If we are sorting by name
191 if ((leftData
.type() == QVariant::String
) && (rightData
.type() ==
193 // On our priority, folders go above regular files
194 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
197 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
201 // Hidden elements go before visible ones, if they both are
203 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
206 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
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();
214 return sortCaseSensitivity() ? (naturalCompare(leftStr
, rightStr
) < 0) :
215 (naturalCompare(leftStr
.toLower(), rightStr
.toLower()) < 0);
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()) {
223 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
227 // Hidden elements go before visible ones, if they both are
229 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
232 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
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
;
242 const QVariant rightValue
= dirModel
->data(right
, KDirModel::ChildCountRole
);
243 const int rightCount
= rightValue
.type() == QVariant::Int
? rightValue
.toInt() : KDirModel::ChildCountUnknown
;
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);
253 // If they had different number of items, we sort them depending
254 // on how many items had each other
255 return leftCount
< rightCount
;
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);
265 // If their sizes are different, sort them by their sizes, as expected
266 return leftFileItem
->size() < rightFileItem
->size();
268 else if (sortRole() == DolphinView::SortByDate
) {
269 // On our priority, folders go above regular files
270 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
273 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
277 // Hidden elements go before visible ones, if they both are
279 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
282 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
287 leftTime
.setTime_t(leftFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
289 rightTime
.setTime_t(rightFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
291 if (leftTime
== rightTime
)
293 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
294 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
297 return leftTime
> rightTime
;
299 else if (sortRole() == DolphinView::SortByPermissions
) {
300 // On our priority, folders go above regular files
301 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
304 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
308 // Hidden elements go before visible ones, if they both are
310 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
313 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
317 const QString leftPermissions
= leftFileItem
->permissionsString();
318 const QString rightPermissions
= rightFileItem
->permissionsString();
320 if (leftPermissions
== rightPermissions
)
322 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
323 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
326 return naturalCompare(leftPermissions
,
327 rightPermissions
) < 0;
329 else if (sortRole() == DolphinView::SortByOwner
) {
330 // On our priority, folders go above regular files
331 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
334 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
338 // Hidden elements go before visible ones, if they both are
340 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
343 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
347 const QString leftOwner
= leftFileItem
->user();
348 const QString rightOwner
= rightFileItem
->user();
350 if (leftOwner
== rightOwner
)
352 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
353 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
356 return naturalCompare(leftOwner
.toLower(),
357 rightOwner
.toLower()) < 0;
359 else if (sortRole() == DolphinView::SortByGroup
) {
360 // On our priority, folders go above regular files
361 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
364 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
368 // Hidden elements go before visible ones, if they both are
370 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
373 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
377 const QString leftGroup
= leftFileItem
->group();
378 const QString rightGroup
= rightFileItem
->group();
380 if (leftGroup
== rightGroup
)
382 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
383 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
386 return naturalCompare(leftGroup
.toLower(),
387 rightGroup
.toLower()) < 0;
389 else if (sortRole() == DolphinView::SortByType
) {
390 // On our priority, folders go above regular files
391 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
394 else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
398 // Hidden elements go before visible ones, if they both are
400 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
403 else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
407 if (leftFileItem
->mimetype() == rightFileItem
->mimetype())
409 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
410 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
413 return naturalCompare(leftFileItem
->mimeComment(),
414 rightFileItem
->mimeComment()) < 0;
417 // We have set a SortRole and trust the ProxyModel to do
418 // the right thing for now.
420 return QSortFilterProxyModel::lessThan(left
, right
);
423 int DolphinSortFilterProxyModel::naturalCompare(const QString
& a
,
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, ...).
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.
435 const QChar
* currA
= a
.unicode(); // iterator over a
436 const QChar
* currB
= b
.unicode(); // iterator over b
438 if (currA
== currB
) {
442 const QChar
* begSeqA
= currA
; // beginning of a new character sequence of a
443 const QChar
* begSeqB
= currB
;
445 while (!currA
->isNull() && !currB
->isNull()) {
446 // find sequence of characters ending at the first non-character
447 while (!currA
->isNull() && !currA
->isDigit()) {
451 while (!currB
->isNull() && !currB
->isDigit()) {
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
);
463 if (currA
->isNull() || currB
->isNull()) {
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)
472 if (!currA
->isDigit() && !currB
->isDigit()) {
474 } else if (!currA
->isDigit()) {
476 } else if (!currB
->isDigit()) {
478 } else if (*currA
< *currB
) {
480 } else if (*currA
> *currB
) {
487 // No digit-sequence starts with 0 -> assume we are looking at some integer
488 // do right aligned comparison.
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.
496 if (!currA
->isDigit() && !currB
->isDigit()) {
501 } else if (!currA
->isDigit()) {
503 } else if (!currB
->isDigit()) {
505 } else if ((*currA
< *currB
) && (weight
== 0)) {
507 } else if ((*currA
> *currB
) && (weight
== 0)) {
519 if (currA
->isNull() && currB
->isNull()) {
523 return currA
->isNull() ? -1 : + 1;
526 #include "dolphinsortfilterproxymodel.moc"