]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphinsortfilterproxymodel.cpp
5d3e45b0fc166ce59b48be763bd9737cb4d3eaff
[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
71 void DolphinSortFilterProxyModel::setSorting(DolphinView::Sorting sorting)
72 {
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)],
77 m_sortOrder);
78 }
79
80 void DolphinSortFilterProxyModel::setSortOrder(Qt::SortOrder sortOrder)
81 {
82 // change the sort order by keeping the current column
83 sort(dolphinViewToDirModelColumn[m_sorting], sortOrder);
84 }
85
86 void DolphinSortFilterProxyModel::sort(int column, Qt::SortOrder sortOrder)
87 {
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);
95 }
96
97 bool DolphinSortFilterProxyModel::hasChildren(const QModelIndex& parent) const
98 {
99 const QModelIndex sourceParent = mapToSource(parent);
100 return sourceModel()->hasChildren(sourceParent);
101 }
102
103 bool DolphinSortFilterProxyModel::canFetchMore(const QModelIndex& parent) const
104 {
105 const QModelIndex sourceParent = mapToSource(parent);
106 return sourceModel()->canFetchMore(sourceParent);
107 }
108
109 DolphinView::Sorting DolphinSortFilterProxyModel::sortingForColumn(int column)
110 {
111 if ((column >= 0) && (column < dolphinMapSize)) {
112 return dirModelColumnToDolphinView[column];
113 }
114 return DolphinView::SortByName;
115 }
116
117 bool DolphinSortFilterProxyModel::lessThanGeneralPurpose(const QModelIndex &left,
118 const QModelIndex &right) const
119 {
120 KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
121
122 const KFileItem* leftFileItem = dirModel->itemForIndex(left);
123 const KFileItem* rightFileItem = dirModel->itemForIndex(right);
124
125 switch (sortRole()) {
126 case DolphinView::SortByName: {
127 QString leftFileName(leftFileItem->name());
128 if (leftFileName.at(0) == '.') {
129 leftFileName = leftFileName.mid(1);
130 }
131
132 QString rightFileName(rightFileItem->name());
133 if (rightFileName.at(0) == '.') {
134 rightFileName = rightFileName.mid(1);
135 }
136
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;
140 }
141
142 case DolphinView::SortBySize:
143 // If we are sorting by size, show folders first. We will sort them
144 // correctly later.
145 return leftFileItem->isDir() && !rightFileItem->isDir();
146
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;
152 }
153
154 case DolphinView::SortByPermissions: {
155 return naturalCompare(leftFileItem->permissionsString(),
156 rightFileItem->permissionsString()) < 0;
157 }
158
159 case DolphinView::SortByOwner: {
160 return naturalCompare(leftFileItem->user().toLower(),
161 rightFileItem->user().toLower()) < 0;
162 }
163
164 case DolphinView::SortByGroup: {
165 return naturalCompare(leftFileItem->group().toLower(),
166 rightFileItem->group().toLower()) < 0;
167 }
168
169 case DolphinView::SortByType: {
170 // If we are sorting by size, show folders first. We will sort them
171 // correctly later
172 if (leftFileItem->isDir() && !rightFileItem->isDir()) {
173 return true;
174 } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
175 return false;
176 }
177
178 return naturalCompare(leftFileItem->mimeComment().toLower(),
179 rightFileItem->mimeComment().toLower()) < 0;
180 }
181 }
182 }
183
184 bool DolphinSortFilterProxyModel::lessThan(const QModelIndex& left,
185 const QModelIndex& right) const
186 {
187 KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
188
189 QVariant leftData = dirModel->data(left, dolphinViewToDirModelColumn[sortRole()]);
190 QVariant rightData = dirModel->data(right, dolphinViewToDirModelColumn[sortRole()]);
191
192 const KFileItem *leftFileItem = dirModel->itemForIndex(left);
193 const KFileItem *rightFileItem = dirModel->itemForIndex(right);
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 // Hidden elements go before visible ones, if they both are
203 // folders or files
204 if (leftFileItem->isHidden() && !rightFileItem->isHidden()) {
205 return true;
206 } else if (!leftFileItem->isHidden() && rightFileItem->isHidden()) {
207 return false;
208 }
209
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();
215
216 return sortCaseSensitivity() ? (naturalCompare(leftValueString, rightValueString) < 0) :
217 (naturalCompare(leftValueString.toLower(), rightValueString.toLower()) < 0);
218 }
219
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;
226
227 QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
228 int rightCount = rightValue.type() == QVariant::Int ? rightValue.toInt() : KDirModel::ChildCountUnknown;
229
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);
236 }
237
238 // If they had different number of items, we sort them depending
239 // on how many items had each other
240 return leftCount < rightCount;
241 }
242
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);
248 }
249
250 // If their sizes are different, sort them by their sizes, as expected
251 return leftFileItem->size() < rightFileItem->size();
252 }
253
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));
258
259 if (leftTime == rightTime) {
260 return sortCaseSensitivity() ?
261 (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
262 (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
263 }
264
265 return leftTime > rightTime;
266 }
267
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);
273 }
274
275 return naturalCompare(leftFileItem->permissionsString(),
276 rightFileItem->permissionsString()) < 0;
277 }
278
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);
284 }
285
286 return naturalCompare(leftFileItem->user(), rightFileItem->user()) < 0;
287 }
288
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);
293 }
294
295 return naturalCompare(leftFileItem->group(),
296 rightFileItem->group()) < 0;
297 }
298
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);
304 }
305
306 return naturalCompare(leftFileItem->mimeComment(),
307 rightFileItem->mimeComment()) < 0;
308 }
309 }
310
311 // We have set a SortRole and trust the ProxyModel to do
312 // the right thing for now.
313 return QSortFilterProxyModel::lessThan(left, right);
314 }
315
316 int DolphinSortFilterProxyModel::naturalCompare(const QString& a,
317 const QString& b)
318 {
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, ...).
323 //
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.
327
328 const QChar* currA = a.unicode(); // iterator over a
329 const QChar* currB = b.unicode(); // iterator over b
330
331 if (currA == currB) {
332 return 0;
333 }
334
335 const QChar* begSeqA = currA; // beginning of a new character sequence of a
336 const QChar* begSeqB = currB;
337
338 while (!currA->isNull() && !currB->isNull()) {
339 // find sequence of characters ending at the first non-character
340 while (!currA->isNull() && !currA->isDigit()) {
341 ++currA;
342 }
343
344 while (!currB->isNull() && !currB->isDigit()) {
345 ++currB;
346 }
347
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);
352 if (cmp != 0) {
353 return cmp;
354 }
355
356 if (currA->isNull() || currB->isNull()) {
357 break;
358 }
359
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)
364 while (1) {
365 if (!currA->isDigit() && !currB->isDigit()) {
366 break;
367 } else if (!currA->isDigit()) {
368 return -1;
369 } else if (!currB->isDigit()) {
370 return + 1;
371 } else if (*currA < *currB) {
372 return -1;
373 } else if (*currA > *currB) {
374 return + 1;
375 }
376 ++currA;
377 ++currB;
378 }
379 } else {
380 // No digit-sequence starts with 0 -> assume we are looking at some integer
381 // do right aligned comparison.
382 //
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.
386
387 int weight = 0;
388 while (1) {
389 if (!currA->isDigit() && !currB->isDigit()) {
390 if (weight != 0) {
391 return weight;
392 }
393 break;
394 } else if (!currA->isDigit()) {
395 return -1;
396 } else if (!currB->isDigit()) {
397 return + 1;
398 } else if ((*currA < *currB) && (weight == 0)) {
399 weight = -1;
400 } else if ((*currA > *currB) && (weight == 0)) {
401 weight = + 1;
402 }
403 ++currA;
404 ++currB;
405 }
406 }
407
408 begSeqA = currA;
409 begSeqB = currB;
410 }
411
412 if (currA->isNull() && currB->isNull()) {
413 return 0;
414 }
415
416 return currA->isNull() ? -1 : + 1;
417 }
418
419 #include "dolphinsortfilterproxymodel.moc"