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()
71 void DolphinSortFilterProxyModel::setSorting(DolphinView::Sorting sorting
)
73 // Update the sort column by mapping DolpginView::Sorting to
74 // KDirModel::ModelColumns. We will keep the sortOrder.
75 Q_ASSERT(static_cast<int>(sorting
) >= 0 && static_cast<int>(sorting
) < dolphinMapSize
);
76 sort(dolphinViewToDirModelColumn
[static_cast<int>(sorting
)],
80 void DolphinSortFilterProxyModel::setSortOrder(Qt::SortOrder sortOrder
)
82 // change the sort order by keeping the current column
83 sort(dolphinViewToDirModelColumn
[m_sorting
], sortOrder
);
86 void DolphinSortFilterProxyModel::sort(int column
, Qt::SortOrder sortOrder
)
88 m_sortColumn
= column
;
89 m_sortOrder
= sortOrder
;
90 m_sorting
= (column
>= 0) && (column
< dolphinMapSize
) ?
91 dirModelColumnToDolphinView
[column
] :
92 DolphinView::SortByName
;
93 setSortRole(dirModelColumnToDolphinView
[column
]);
94 KSortFilterProxyModel::sort(column
, sortOrder
);
97 bool DolphinSortFilterProxyModel::hasChildren(const QModelIndex
& parent
) const
99 const QModelIndex sourceParent
= mapToSource(parent
);
100 return sourceModel()->hasChildren(sourceParent
);
103 bool DolphinSortFilterProxyModel::canFetchMore(const QModelIndex
& parent
) const
105 const QModelIndex sourceParent
= mapToSource(parent
);
106 return sourceModel()->canFetchMore(sourceParent
);
109 DolphinView::Sorting
DolphinSortFilterProxyModel::sortingForColumn(int column
)
111 if ((column
>= 0) && (column
< dolphinMapSize
)) {
112 return dirModelColumnToDolphinView
[column
];
114 return DolphinView::SortByName
;
117 bool DolphinSortFilterProxyModel::lessThanGeneralPurpose(const QModelIndex
&left
,
118 const QModelIndex
&right
) const
120 KDirModel
* dirModel
= static_cast<KDirModel
*>(sourceModel());
122 const KFileItem
* leftFileItem
= dirModel
->itemForIndex(left
);
123 const KFileItem
* rightFileItem
= dirModel
->itemForIndex(right
);
125 switch (sortRole()) {
126 case DolphinView::SortByName
: {
127 QString
leftFileName(leftFileItem
->name());
128 if (leftFileName
.at(0) == '.') {
129 leftFileName
= leftFileName
.mid(1);
132 QString
rightFileName(rightFileItem
->name());
133 if (rightFileName
.at(0) == '.') {
134 rightFileName
= rightFileName
.mid(1);
137 // We don't care about case for building categories. We also don't
138 // want here to compare by a natural comparison.
139 return naturalCompare(leftFileName
, rightFileName
) < 0;
142 case DolphinView::SortBySize
:
143 // If we are sorting by size, show folders first. We will sort them
145 return leftFileItem
->isDir() && !rightFileItem
->isDir();
147 case DolphinView::SortByDate
: {
148 KDateTime leftTime
, rightTime
;
149 leftTime
.setTime_t(leftFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
150 rightTime
.setTime_t(rightFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
151 return leftTime
> rightTime
;
154 case DolphinView::SortByPermissions
: {
155 return naturalCompare(leftFileItem
->permissionsString(),
156 rightFileItem
->permissionsString()) < 0;
159 case DolphinView::SortByOwner
: {
160 return naturalCompare(leftFileItem
->user().toLower(),
161 rightFileItem
->user().toLower()) < 0;
164 case DolphinView::SortByGroup
: {
165 return naturalCompare(leftFileItem
->group().toLower(),
166 rightFileItem
->group().toLower()) < 0;
169 case DolphinView::SortByType
: {
170 // If we are sorting by size, show folders first. We will sort them
172 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
174 } else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
178 return naturalCompare(leftFileItem
->mimeComment().toLower(),
179 rightFileItem
->mimeComment().toLower()) < 0;
184 bool DolphinSortFilterProxyModel::lessThan(const QModelIndex
& left
,
185 const QModelIndex
& right
) const
187 KDirModel
* dirModel
= static_cast<KDirModel
*>(sourceModel());
189 QVariant leftData
= dirModel
->data(left
, dolphinViewToDirModelColumn
[sortRole()]);
190 QVariant rightData
= dirModel
->data(right
, dolphinViewToDirModelColumn
[sortRole()]);
192 const KFileItem
*leftFileItem
= dirModel
->itemForIndex(left
);
193 const KFileItem
*rightFileItem
= dirModel
->itemForIndex(right
);
195 // On our priority, folders go above regular files
196 if (leftFileItem
->isDir() && !rightFileItem
->isDir()) {
198 } else if (!leftFileItem
->isDir() && rightFileItem
->isDir()) {
202 // Hidden elements go before visible ones, if they both are
204 if (leftFileItem
->isHidden() && !rightFileItem
->isHidden()) {
206 } else if (!leftFileItem
->isHidden() && rightFileItem
->isHidden()) {
210 switch (sortRole()) {
211 case DolphinView::SortByName
: {
212 // So we are in the same priority, what counts now is their names
213 QString leftValueString
= leftData
.toString();
214 QString rightValueString
= rightData
.toString();
216 return sortCaseSensitivity() ? (naturalCompare(leftValueString
, rightValueString
) < 0) :
217 (naturalCompare(leftValueString
.toLower(), rightValueString
.toLower()) < 0);
220 case DolphinView::SortBySize
: {
221 // If we have two folders, what we have to measure is the number of
222 // items that contains each other
223 if (leftFileItem
->isDir() && rightFileItem
->isDir()) {
224 QVariant leftValue
= dirModel
->data(left
, KDirModel::ChildCountRole
);
225 int leftCount
= leftValue
.type() == QVariant::Int
? leftValue
.toInt() : KDirModel::ChildCountUnknown
;
227 QVariant rightValue
= dirModel
->data(right
, KDirModel::ChildCountRole
);
228 int rightCount
= rightValue
.type() == QVariant::Int
? rightValue
.toInt() : KDirModel::ChildCountUnknown
;
230 // In the case they two have the same child items, we sort them by
231 // their names. So we have always everything ordered. We also check
232 // if we are taking in count their cases
233 if (leftCount
== rightCount
) {
234 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
235 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
238 // If they had different number of items, we sort them depending
239 // on how many items had each other
240 return leftCount
< rightCount
;
243 // If what we are measuring is two files and they have the same size,
244 // sort them by their file names
245 if (leftFileItem
->size() == rightFileItem
->size()) {
246 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
247 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
250 // If their sizes are different, sort them by their sizes, as expected
251 return leftFileItem
->size() < rightFileItem
->size();
254 case DolphinView::SortByDate
: {
255 KDateTime leftTime
, rightTime
;
256 leftTime
.setTime_t(leftFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
257 rightTime
.setTime_t(rightFileItem
->time(KIO::UDS_MODIFICATION_TIME
));
259 if (leftTime
== rightTime
) {
260 return sortCaseSensitivity() ?
261 (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
262 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
265 return leftTime
> rightTime
;
268 case DolphinView::SortByPermissions
: {
269 if (leftFileItem
->permissionsString() == rightFileItem
->permissionsString()) {
270 return sortCaseSensitivity() ?
271 (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
272 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
275 return naturalCompare(leftFileItem
->permissionsString(),
276 rightFileItem
->permissionsString()) < 0;
279 case DolphinView::SortByOwner
: {
280 if (leftFileItem
->user() == rightFileItem
->user()) {
281 return sortCaseSensitivity() ?
282 (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
283 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
286 return naturalCompare(leftFileItem
->user(), rightFileItem
->user()) < 0;
289 case DolphinView::SortByGroup
: {
290 if (leftFileItem
->group() == rightFileItem
->group()) {
291 return sortCaseSensitivity() ? (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
292 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
295 return naturalCompare(leftFileItem
->group(),
296 rightFileItem
->group()) < 0;
299 case DolphinView::SortByType
: {
300 if (leftFileItem
->mimetype() == rightFileItem
->mimetype()) {
301 return sortCaseSensitivity() ?
302 (naturalCompare(leftFileItem
->name(), rightFileItem
->name()) < 0) :
303 (naturalCompare(leftFileItem
->name().toLower(), rightFileItem
->name().toLower()) < 0);
306 return naturalCompare(leftFileItem
->mimeComment(),
307 rightFileItem
->mimeComment()) < 0;
311 // We have set a SortRole and trust the ProxyModel to do
312 // the right thing for now.
313 return QSortFilterProxyModel::lessThan(left
, right
);
316 int DolphinSortFilterProxyModel::naturalCompare(const QString
& a
,
319 // This method chops the input a and b into pieces of
320 // digits and non-digits (a1.05 becomes a | 1 | . | 05)
321 // and compares these pieces of a and b to each other
322 // (first with first, second with second, ...).
324 // This is based on the natural sort order code code by Martin Pool
325 // http://sourcefrog.net/projects/natsort/
326 // Martin Pool agreed to license this under LGPL or GPL.
328 const QChar
* currA
= a
.unicode(); // iterator over a
329 const QChar
* currB
= b
.unicode(); // iterator over b
331 if (currA
== currB
) {
335 const QChar
* begSeqA
= currA
; // beginning of a new character sequence of a
336 const QChar
* begSeqB
= currB
;
338 while (!currA
->isNull() && !currB
->isNull()) {
339 // find sequence of characters ending at the first non-character
340 while (!currA
->isNull() && !currA
->isDigit()) {
344 while (!currB
->isNull() && !currB
->isDigit()) {
348 // compare these sequences
349 const QString
subA(begSeqA
, currA
- begSeqA
);
350 const QString
subB(begSeqB
, currB
- begSeqB
);
351 const int cmp
= QString::localeAwareCompare(subA
, subB
);
356 if (currA
->isNull() || currB
->isNull()) {
360 // now some digits follow...
361 if ((*currA
== '0') || (*currB
== '0')) {
362 // one digit-sequence starts with 0 -> assume we are in a fraction part
363 // do left aligned comparison (numbers are considered left aligned)
365 if (!currA
->isDigit() && !currB
->isDigit()) {
367 } else if (!currA
->isDigit()) {
369 } else if (!currB
->isDigit()) {
371 } else if (*currA
< *currB
) {
373 } else if (*currA
> *currB
) {
380 // No digit-sequence starts with 0 -> assume we are looking at some integer
381 // do right aligned comparison.
383 // The longest run of digits wins. That aside, the greatest
384 // value wins, but we can't know that it will until we've scanned
385 // both numbers to know that they have the same magnitude.
389 if (!currA
->isDigit() && !currB
->isDigit()) {
394 } else if (!currA
->isDigit()) {
396 } else if (!currB
->isDigit()) {
398 } else if ((*currA
< *currB
) && (weight
== 0)) {
400 } else if ((*currA
> *currB
) && (weight
== 0)) {
412 if (currA
->isNull() && currB
->isNull()) {
416 return currA
->isNull() ? -1 : + 1;
419 #include "dolphinsortfilterproxymodel.moc"