]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.cpp
Improve KFileItemModel::createMimeData()
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kfileitemmodel.h"
21
22 #include <KDirLister>
23 #include <KDirModel>
24 #include <KLocale>
25 #include <KStringHandler>
26 #include <KDebug>
27
28 #include <QMimeData>
29 #include <QTimer>
30
31 #define KFILEITEMMODEL_DEBUG
32
33 KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) :
34 KItemModelBase(QByteArray(), "name", parent),
35 m_dirLister(dirLister),
36 m_naturalSorting(true),
37 m_sortFoldersFirst(true),
38 m_groupRole(NoRole),
39 m_sortRole(NameRole),
40 m_caseSensitivity(Qt::CaseInsensitive),
41 m_sortedItems(),
42 m_items(),
43 m_data(),
44 m_requestRole(),
45 m_minimumUpdateIntervalTimer(0),
46 m_maximumUpdateIntervalTimer(0),
47 m_pendingItemsToInsert(),
48 m_pendingItemsToDelete(),
49 m_rootExpansionLevel(-1)
50 {
51 resetRoles();
52 m_requestRole[NameRole] = true;
53 m_requestRole[IsDirRole] = true;
54
55 Q_ASSERT(dirLister);
56
57 connect(dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
58 connect(dirLister, SIGNAL(completed()), this, SLOT(slotCompleted()));
59 connect(dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
60 connect(dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
61 connect(dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
62 connect(dirLister, SIGNAL(clear(KUrl)), this, SLOT(slotClear(KUrl)));
63
64 // Although the layout engine of KItemListView is fast it is very inefficient to e.g.
65 // emit 50 itemsInserted()-signals each 100 ms. m_minimumUpdateIntervalTimer assures that updates
66 // are done in 1 second intervals for equal operations.
67 m_minimumUpdateIntervalTimer = new QTimer(this);
68 m_minimumUpdateIntervalTimer->setInterval(1000);
69 m_minimumUpdateIntervalTimer->setSingleShot(true);
70 connect(m_minimumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItems()));
71
72 // For slow KIO-slaves like used for searching it makes sense to show results periodically even
73 // before the completed() or canceled() signal has been emitted.
74 m_maximumUpdateIntervalTimer = new QTimer(this);
75 m_maximumUpdateIntervalTimer->setInterval(2000);
76 m_maximumUpdateIntervalTimer->setSingleShot(true);
77 connect(m_maximumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItems()));
78
79 Q_ASSERT(m_minimumUpdateIntervalTimer->interval() <= m_maximumUpdateIntervalTimer->interval());
80 }
81
82 KFileItemModel::~KFileItemModel()
83 {
84 }
85
86 int KFileItemModel::count() const
87 {
88 return m_data.count();
89 }
90
91 QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
92 {
93 if (index >= 0 && index < count()) {
94 return m_data.at(index);
95 }
96 return QHash<QByteArray, QVariant>();
97 }
98
99 bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& values)
100 {
101 if (index >= 0 && index < count()) {
102 QHash<QByteArray, QVariant> currentValue = m_data.at(index);
103
104 QSet<QByteArray> changedRoles;
105 QHashIterator<QByteArray, QVariant> it(values);
106 while (it.hasNext()) {
107 it.next();
108 const QByteArray role = it.key();
109 const QVariant value = it.value();
110
111 if (currentValue[role] != value) {
112 currentValue[role] = value;
113 changedRoles.insert(role);
114 }
115 }
116
117 if (!changedRoles.isEmpty()) {
118 m_data[index] = currentValue;
119 emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
120 }
121
122 return true;
123 }
124 return false;
125 }
126
127 bool KFileItemModel::supportsGrouping() const
128 {
129 return true;
130 }
131
132 bool KFileItemModel::supportsSorting() const
133 {
134 return true;
135 }
136
137 QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
138 {
139 QMimeData* data = new QMimeData();
140
141 // The following code has been taken from KDirModel::mimeData()
142 // (kdelibs/kio/kio/kdirmodel.cpp)
143 // Copyright (C) 2006 David Faure <faure@kde.org>
144 KUrl::List urls;
145 KUrl::List mostLocalUrls;
146 bool canUseMostLocalUrls = true;
147
148 QSetIterator<int> it(indexes);
149 while (it.hasNext()) {
150 const int index = it.next();
151 const KFileItem item = fileItem(index);
152 if (!item.isNull()) {
153 urls << item.url();
154
155 bool isLocal;
156 mostLocalUrls << item.mostLocalUrl(isLocal);
157 if (!isLocal) {
158 canUseMostLocalUrls = false;
159 }
160 }
161 }
162
163 const bool different = canUseMostLocalUrls && mostLocalUrls != urls;
164 urls = KDirModel::simplifiedUrlList(urls);
165 if (different) {
166 mostLocalUrls = KDirModel::simplifiedUrlList(mostLocalUrls);
167 urls.populateMimeData(mostLocalUrls, data);
168 } else {
169 urls.populateMimeData(data);
170 }
171
172 return data;
173 }
174
175 KFileItem KFileItemModel::fileItem(int index) const
176 {
177 if (index >= 0 && index < count()) {
178 return m_sortedItems.at(index);
179 }
180
181 return KFileItem();
182 }
183
184 int KFileItemModel::index(const KFileItem& item) const
185 {
186 if (item.isNull()) {
187 return -1;
188 }
189
190 return m_items.value(item, -1);
191 }
192
193 void KFileItemModel::clear()
194 {
195 slotClear();
196 }
197
198 void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
199 {
200 if (count() > 0) {
201 const bool supportedExpanding = m_requestRole[IsExpandedRole] && m_requestRole[ExpansionLevelRole];
202 const bool willSupportExpanding = roles.contains("isExpanded") && roles.contains("expansionLevel");
203 if (supportedExpanding && !willSupportExpanding) {
204 // No expanding is supported anymore. Take care to delete all items that have an expansion level
205 // that is not 0 (and hence are part of an expanded item).
206 removeExpandedItems();
207 }
208 }
209
210 resetRoles();
211 QSetIterator<QByteArray> it(roles);
212 while (it.hasNext()) {
213 const QByteArray& role = it.next();
214 m_requestRole[roleIndex(role)] = true;
215 }
216
217 if (count() > 0) {
218 // Update m_data with the changed requested roles
219 const int maxIndex = count() - 1;
220 for (int i = 0; i <= maxIndex; ++i) {
221 m_data[i] = retrieveData(m_sortedItems.at(i));
222 }
223
224 kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
225 emit itemsChanged(KItemRangeList() << KItemRange(0, count()), QSet<QByteArray>());
226 }
227 }
228
229 QSet<QByteArray> KFileItemModel::roles() const
230 {
231 QSet<QByteArray> roles;
232 for (int i = 0; i < RolesCount; ++i) {
233 if (m_requestRole[i]) {
234 switch (i) {
235 case NoRole: break;
236 case NameRole: roles.insert("name"); break;
237 case SizeRole: roles.insert("size"); break;
238 case DateRole: roles.insert("date"); break;
239 case PermissionsRole: roles.insert("permissions"); break;
240 case OwnerRole: roles.insert("owner"); break;
241 case GroupRole: roles.insert("group"); break;
242 case TypeRole: roles.insert("type"); break;
243 case DestinationRole: roles.insert("destination"); break;
244 case PathRole: roles.insert("path"); break;
245 case IsDirRole: roles.insert("isDir"); break;
246 case IsExpandedRole: roles.insert("isExpanded"); break;
247 case ExpansionLevelRole: roles.insert("expansionLevel"); break;
248 default: Q_ASSERT(false); break;
249 }
250 }
251 }
252 return roles;
253 }
254
255 bool KFileItemModel::setExpanded(int index, bool expanded)
256 {
257 if (isExpanded(index) == expanded || index < 0 || index >= count()) {
258 return false;
259 }
260
261 QHash<QByteArray, QVariant> values;
262 values.insert("isExpanded", expanded);
263 if (!setData(index, values)) {
264 return false;
265 }
266
267 if (expanded) {
268 const KUrl url = m_sortedItems.at(index).url();
269 KDirLister* dirLister = m_dirLister.data();
270 if (dirLister) {
271 dirLister->openUrl(url, KDirLister::Keep);
272 return true;
273 }
274 } else {
275 KFileItemList itemsToRemove;
276 const int expansionLevel = data(index)["expansionLevel"].toInt();
277 ++index;
278 while (index < count() && data(index)["expansionLevel"].toInt() > expansionLevel) {
279 itemsToRemove.append(m_sortedItems.at(index));
280 ++index;
281 }
282 removeItems(itemsToRemove);
283 return true;
284 }
285
286 return false;
287 }
288
289 bool KFileItemModel::isExpanded(int index) const
290 {
291 if (index >= 0 && index < count()) {
292 return m_data.at(index).value("isExpanded").toBool();
293 }
294 return false;
295 }
296
297 bool KFileItemModel::isExpandable(int index) const
298 {
299 if (index >= 0 && index < count()) {
300 return m_sortedItems.at(index).isDir();
301 }
302 return false;
303 }
304
305 void KFileItemModel::onGroupRoleChanged(const QByteArray& current, const QByteArray& previous)
306 {
307 Q_UNUSED(previous);
308 m_groupRole = roleIndex(current);
309 }
310
311 void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
312 {
313 Q_UNUSED(previous);
314 const int itemCount = count();
315 if (itemCount <= 0) {
316 return;
317 }
318
319 m_sortRole = roleIndex(current);
320
321 KFileItemList sortedItems = m_sortedItems;
322 m_sortedItems.clear();
323 m_items.clear();
324 m_data.clear();
325 emit itemsRemoved(KItemRangeList() << KItemRange(0, itemCount));
326
327 sort(sortedItems.begin(), sortedItems.end());
328 int index = 0;
329 foreach (const KFileItem& item, sortedItems) {
330 m_sortedItems.append(item);
331 m_items.insert(item, index);
332 m_data.append(retrieveData(item));
333
334 ++index;
335 }
336
337 emit itemsInserted(KItemRangeList() << KItemRange(0, itemCount));
338 }
339
340 void KFileItemModel::slotCompleted()
341 {
342 if (m_minimumUpdateIntervalTimer->isActive()) {
343 // dispatchPendingItems() will be called when the timer
344 // has been expired.
345 return;
346 }
347
348 dispatchPendingItems();
349 m_minimumUpdateIntervalTimer->start();
350 }
351
352 void KFileItemModel::slotCanceled()
353 {
354 m_minimumUpdateIntervalTimer->stop();
355 m_maximumUpdateIntervalTimer->stop();
356 dispatchPendingItems();
357 }
358
359 void KFileItemModel::slotNewItems(const KFileItemList& items)
360 {
361 if (!m_pendingItemsToDelete.isEmpty()) {
362 removeItems(m_pendingItemsToDelete);
363 m_pendingItemsToDelete.clear();
364 }
365 m_pendingItemsToInsert.append(items);
366
367 if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
368 // Assure that items get dispatched if no completed() or canceled() signal is
369 // emitted during the maximum update interval.
370 m_maximumUpdateIntervalTimer->start();
371 }
372 }
373
374 void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
375 {
376 if (!m_pendingItemsToInsert.isEmpty()) {
377 insertItems(m_pendingItemsToInsert);
378 m_pendingItemsToInsert.clear();
379 }
380 m_pendingItemsToDelete.append(items);
381 }
382
383 void KFileItemModel::slotClear()
384 {
385 #ifdef KFILEITEMMODEL_DEBUG
386 kDebug() << "Clearing all items";
387 #endif
388
389 m_minimumUpdateIntervalTimer->stop();
390 m_maximumUpdateIntervalTimer->stop();
391 m_pendingItemsToInsert.clear();
392 m_pendingItemsToDelete.clear();
393
394 m_rootExpansionLevel = -1;
395
396 const int removedCount = m_data.count();
397 if (removedCount > 0) {
398 m_sortedItems.clear();
399 m_items.clear();
400 m_data.clear();
401 emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
402 }
403 }
404
405 void KFileItemModel::slotClear(const KUrl& url)
406 {
407 Q_UNUSED(url);
408 }
409
410 void KFileItemModel::dispatchPendingItems()
411 {
412 if (!m_pendingItemsToInsert.isEmpty()) {
413 Q_ASSERT(m_pendingItemsToDelete.isEmpty());
414 insertItems(m_pendingItemsToInsert);
415 m_pendingItemsToInsert.clear();
416 } else if (!m_pendingItemsToDelete.isEmpty()) {
417 Q_ASSERT(m_pendingItemsToInsert.isEmpty());
418 removeItems(m_pendingItemsToDelete);
419 m_pendingItemsToDelete.clear();
420 }
421 }
422
423 void KFileItemModel::insertItems(const KFileItemList& items)
424 {
425 if (items.isEmpty()) {
426 return;
427 }
428
429 #ifdef KFILEITEMMODEL_DEBUG
430 QElapsedTimer timer;
431 timer.start();
432 kDebug() << "===========================================================";
433 kDebug() << "Inserting" << items.count() << "items";
434 #endif
435
436 KFileItemList sortedItems = items;
437 sort(sortedItems.begin(), sortedItems.end());
438
439 #ifdef KFILEITEMMODEL_DEBUG
440 kDebug() << "[TIME] Sorting:" << timer.elapsed();
441 #endif
442
443 KItemRangeList itemRanges;
444 int targetIndex = 0;
445 int sourceIndex = 0;
446 int insertedAtIndex = -1; // Index for the current item-range
447 int insertedCount = 0; // Count for the current item-range
448 int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
449 while (sourceIndex < sortedItems.count()) {
450 // Find target index from m_items to insert the current item
451 // in a sorted order
452 const int previousTargetIndex = targetIndex;
453 while (targetIndex < m_sortedItems.count()) {
454 if (!lessThan(m_sortedItems.at(targetIndex), sortedItems.at(sourceIndex))) {
455 break;
456 }
457 ++targetIndex;
458 }
459
460 if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
461 itemRanges << KItemRange(insertedAtIndex, insertedCount);
462 previouslyInsertedCount += insertedCount;
463 insertedAtIndex = targetIndex - previouslyInsertedCount;
464 insertedCount = 0;
465 }
466
467 // Insert item at the position targetIndex
468 const KFileItem item = sortedItems.at(sourceIndex);
469 m_sortedItems.insert(targetIndex, item);
470 m_data.insert(targetIndex, retrieveData(item));
471 // m_items will be inserted after the loop (see comment below)
472 ++insertedCount;
473
474 if (insertedAtIndex < 0) {
475 insertedAtIndex = targetIndex;
476 Q_ASSERT(previouslyInsertedCount == 0);
477 }
478 ++targetIndex;
479 ++sourceIndex;
480 }
481
482 // The indexes of all m_items must be adjusted, not only the index
483 // of the new items
484 for (int i = 0; i < m_sortedItems.count(); ++i) {
485 m_items.insert(m_sortedItems.at(i), i);
486 }
487
488 itemRanges << KItemRange(insertedAtIndex, insertedCount);
489 emit itemsInserted(itemRanges);
490
491 #ifdef KFILEITEMMODEL_DEBUG
492 kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
493 #endif
494 }
495
496 void KFileItemModel::removeItems(const KFileItemList& items)
497 {
498 if (items.isEmpty()) {
499 return;
500 }
501
502 #ifdef KFILEITEMMODEL_DEBUG
503 kDebug() << "Removing " << items.count() << "items";
504 #endif
505
506 KFileItemList sortedItems = items;
507 sort(sortedItems.begin(), sortedItems.end());
508
509 QList<int> indexesToRemove;
510 indexesToRemove.reserve(items.count());
511
512 // Calculate the item ranges that will get deleted
513 KItemRangeList itemRanges;
514 int removedAtIndex = -1;
515 int removedCount = 0;
516 int targetIndex = 0;
517 foreach (const KFileItem& itemToRemove, sortedItems) {
518 const int previousTargetIndex = targetIndex;
519 while (targetIndex < m_sortedItems.count()) {
520 if (m_sortedItems.at(targetIndex) == itemToRemove) {
521 break;
522 }
523 ++targetIndex;
524 }
525 if (targetIndex >= m_sortedItems.count()) {
526 kWarning() << "Item that should be deleted has not been found!";
527 return;
528 }
529
530 if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
531 itemRanges << KItemRange(removedAtIndex, removedCount);
532 removedAtIndex = targetIndex;
533 removedCount = 0;
534 }
535
536 indexesToRemove.append(targetIndex);
537 if (removedAtIndex < 0) {
538 removedAtIndex = targetIndex;
539 }
540 ++removedCount;
541 ++targetIndex;
542 }
543
544 // Delete the items
545 for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
546 const int indexToRemove = indexesToRemove.at(i);
547 m_items.remove(m_sortedItems.at(indexToRemove));
548 m_sortedItems.removeAt(indexToRemove);
549 m_data.removeAt(indexToRemove);
550 }
551
552 // The indexes of all m_items must be adjusted, not only the index
553 // of the removed items
554 for (int i = 0; i < m_sortedItems.count(); ++i) {
555 m_items.insert(m_sortedItems.at(i), i);
556 }
557
558 if (count() <= 0) {
559 m_rootExpansionLevel = -1;
560 }
561
562 itemRanges << KItemRange(removedAtIndex, removedCount);
563 emit itemsRemoved(itemRanges);
564 }
565
566 void KFileItemModel::removeExpandedItems()
567 {
568
569 KFileItemList expandedItems;
570
571 const int maxIndex = m_data.count() - 1;
572 for (int i = 0; i <= maxIndex; ++i) {
573 if (m_data.at(i).value("expansionLevel").toInt() > 0) {
574 const KFileItem fileItem = m_sortedItems.at(i);
575 expandedItems.append(fileItem);
576 }
577 }
578
579 // The m_rootExpansionLevel may not get reset before all items with
580 // a bigger expansionLevel have been removed.
581 Q_ASSERT(m_rootExpansionLevel >= 0);
582 removeItems(expandedItems);
583
584 m_rootExpansionLevel = -1;
585 }
586
587 void KFileItemModel::resetRoles()
588 {
589 for (int i = 0; i < RolesCount; ++i) {
590 m_requestRole[i] = false;
591 }
592 }
593
594 KFileItemModel::Role KFileItemModel::roleIndex(const QByteArray& role) const
595 {
596 static QHash<QByteArray, Role> rolesHash;
597 if (rolesHash.isEmpty()) {
598 rolesHash.insert("name", NameRole);
599 rolesHash.insert("size", SizeRole);
600 rolesHash.insert("date", DateRole);
601 rolesHash.insert("permissions", PermissionsRole);
602 rolesHash.insert("owner", OwnerRole);
603 rolesHash.insert("group", GroupRole);
604 rolesHash.insert("type", TypeRole);
605 rolesHash.insert("destination", DestinationRole);
606 rolesHash.insert("path", PathRole);
607 rolesHash.insert("isDir", IsDirRole);
608 rolesHash.insert("isExpanded", IsExpandedRole);
609 rolesHash.insert("expansionLevel", ExpansionLevelRole);
610 }
611 return rolesHash.value(role, NoRole);
612 }
613
614 QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) const
615 {
616 // It is important to insert only roles that are fast to retrieve. E.g.
617 // KFileItem::iconName() can be very expensive if the MIME-type is unknown
618 // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
619 QHash<QByteArray, QVariant> data;
620 data.insert("iconPixmap", QPixmap());
621
622 const bool isDir = item.isDir();
623 if (m_requestRole[IsDirRole]) {
624 data.insert("isDir", isDir);
625 }
626
627 if (m_requestRole[NameRole]) {
628 data.insert("name", item.name());
629 }
630
631 if (m_requestRole[SizeRole]) {
632 if (isDir) {
633 data.insert("size", QVariant());
634 } else {
635 data.insert("size", item.size());
636 }
637 }
638
639 if (m_requestRole[DateRole]) {
640 // Don't use KFileItem::timeString() as this is too expensive when
641 // having several thousands of items. Instead the formatting of the
642 // date-time will be done on-demand by the view when the date will be shown.
643 const KDateTime dateTime = item.time(KFileItem::ModificationTime);
644 data.insert("date", dateTime.dateTime());
645 }
646
647 if (m_requestRole[PermissionsRole]) {
648 data.insert("permissions", item.permissionsString());
649 }
650
651 if (m_requestRole[OwnerRole]) {
652 data.insert("owner", item.user());
653 }
654
655 if (m_requestRole[GroupRole]) {
656 data.insert("group", item.group());
657 }
658
659 if (m_requestRole[DestinationRole]) {
660 QString destination = item.linkDest();
661 if (destination.isEmpty()) {
662 destination = i18nc("@item:intable", "No destination");
663 }
664 data.insert("destination", destination);
665 }
666
667 if (m_requestRole[PathRole]) {
668 data.insert("path", item.localPath());
669 }
670
671 if (m_requestRole[IsExpandedRole]) {
672 data.insert("isExpanded", false);
673 }
674
675 if (m_requestRole[ExpansionLevelRole]) {
676 if (m_rootExpansionLevel < 0) {
677 KDirLister* dirLister = m_dirLister.data();
678 if (dirLister) {
679 const QString rootDir = dirLister->url().directory(KUrl::AppendTrailingSlash);
680 m_rootExpansionLevel = rootDir.count('/');
681 }
682 }
683 const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
684 const int level = dir.count('/') - m_rootExpansionLevel - 1;
685 data.insert("expansionLevel", level);
686 }
687
688 if (item.isMimeTypeKnown()) {
689 data.insert("iconName", item.iconName());
690
691 if (m_requestRole[TypeRole]) {
692 data.insert("type", item.mimeComment());
693 }
694 }
695
696 return data;
697 }
698
699 bool KFileItemModel::lessThan(const KFileItem& a, const KFileItem& b) const
700 {
701 int result = 0;
702
703 if (m_rootExpansionLevel >= 0) {
704 result = expansionLevelsCompare(a, b);
705 if (result != 0) {
706 // The items have parents with different expansion levels
707 return result < 0;
708 }
709 }
710
711 if (m_sortFoldersFirst) {
712 const bool isDirA = a.isDir();
713 const bool isDirB = b.isDir();
714 if (isDirA && !isDirB) {
715 return true;
716 } else if (!isDirA && isDirB) {
717 return false;
718 }
719 }
720
721 switch (m_sortRole) {
722 case NameRole: {
723 result = stringCompare(a.text(), b.text());
724 if (result == 0) {
725 // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
726 result = stringCompare(a.name(m_caseSensitivity == Qt::CaseInsensitive),
727 b.name(m_caseSensitivity == Qt::CaseInsensitive));
728 }
729 break;
730 }
731
732 case DateRole: {
733 const KDateTime dateTimeA = a.time(KFileItem::ModificationTime);
734 const KDateTime dateTimeB = b.time(KFileItem::ModificationTime);
735 if (dateTimeA < dateTimeB) {
736 result = -1;
737 } else if (dateTimeA > dateTimeB) {
738 result = +1;
739 }
740 break;
741 }
742
743 default:
744 break;
745 }
746
747 if (result == 0) {
748 // It must be assured that the sort order is always unique even if two values have been
749 // equal. In this case a comparison of the URL is done which is unique in all cases
750 // within KDirLister.
751 result = QString::compare(a.url().url(), b.url().url(), Qt::CaseSensitive);
752 }
753
754 return result < 0;
755 }
756
757 void KFileItemModel::sort(const KFileItemList::iterator& startIterator, const KFileItemList::iterator& endIterator)
758 {
759 KFileItemList::iterator start = startIterator;
760 KFileItemList::iterator end = endIterator;
761
762 // The implementation is based on qSortHelper() from qalgorithms.h
763 // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
764 // In opposite to qSort() it allows to use a member-function for the comparison of elements.
765 while (1) {
766 int span = int(end - start);
767 if (span < 2) {
768 return;
769 }
770
771 --end;
772 KFileItemList::iterator low = start, high = end - 1;
773 KFileItemList::iterator pivot = start + span / 2;
774
775 if (lessThan(*end, *start)) {
776 qSwap(*end, *start);
777 }
778 if (span == 2) {
779 return;
780 }
781
782 if (lessThan(*pivot, *start)) {
783 qSwap(*pivot, *start);
784 }
785 if (lessThan(*end, *pivot)) {
786 qSwap(*end, *pivot);
787 }
788 if (span == 3) {
789 return;
790 }
791
792 qSwap(*pivot, *end);
793
794 while (low < high) {
795 while (low < high && lessThan(*low, *end)) {
796 ++low;
797 }
798
799 while (high > low && lessThan(*end, *high)) {
800 --high;
801 }
802 if (low < high) {
803 qSwap(*low, *high);
804 ++low;
805 --high;
806 } else {
807 break;
808 }
809 }
810
811 if (lessThan(*low, *end)) {
812 ++low;
813 }
814
815 qSwap(*end, *low);
816 sort(start, low);
817
818 start = low + 1;
819 ++end;
820 }
821 }
822
823 int KFileItemModel::stringCompare(const QString& a, const QString& b) const
824 {
825 // Taken from KDirSortFilterProxyModel (kdelibs/kfile/kdirsortfilterproxymodel.*)
826 // Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at>
827 // Copyright (C) 2006 by Dominic Battre <dominic@battre.de>
828 // Copyright (C) 2006 by Martin Pool <mbp@canonical.com>
829
830 if (m_caseSensitivity == Qt::CaseInsensitive) {
831 const int result = m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseInsensitive)
832 : QString::compare(a, b, Qt::CaseInsensitive);
833 if (result != 0) {
834 // Only return the result, if the strings are not equal. If they are equal by a case insensitive
835 // comparison, still a deterministic sort order is required. A case sensitive
836 // comparison is done as fallback.
837 return result;
838 }
839 }
840
841 return m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseSensitive)
842 : QString::compare(a, b, Qt::CaseSensitive);
843 }
844
845 int KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b) const
846 {
847 const KUrl urlA = a.url();
848 const KUrl urlB = b.url();
849 if (urlA.directory() == urlB.directory()) {
850 // Both items have the same directory as parent
851 return 0;
852 }
853
854 // Check whether one item is the parent of the other item
855 if (urlA.isParentOf(urlB)) {
856 return -1;
857 } else if (urlB.isParentOf(urlA)) {
858 return +1;
859 }
860
861 // Determine the maximum common path of both items and
862 // remember the index in 'index'
863 const QString pathA = urlA.path();
864 const QString pathB = urlB.path();
865
866 const int maxIndex = qMin(pathA.length(), pathB.length()) - 1;
867 int index = 0;
868 while (index <= maxIndex && pathA.at(index) == pathB.at(index)) {
869 ++index;
870 }
871 if (index > maxIndex) {
872 index = maxIndex;
873 }
874 while ((pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/')) && index > 0) {
875 --index;
876 }
877
878 // Determine the first sub-path after the common path and
879 // check whether it represents a directory or already a file
880 bool isDirA = true;
881 const QString subPathA = subPath(a, pathA, index, &isDirA);
882 bool isDirB = true;
883 const QString subPathB = subPath(b, pathB, index, &isDirB);
884
885 if (isDirA && !isDirB) {
886 return -1;
887 } else if (!isDirA && isDirB) {
888 return +1;
889 }
890
891 return stringCompare(subPathA, subPathB);
892 }
893
894 QString KFileItemModel::subPath(const KFileItem& item,
895 const QString& itemPath,
896 int start,
897 bool* isDir) const
898 {
899 Q_ASSERT(isDir);
900 const int pathIndex = itemPath.indexOf('/', start + 1);
901 *isDir = (pathIndex > 0) || item.isDir();
902 return itemPath.mid(start, pathIndex - start);
903 }
904
905 bool KFileItemModel::useMaximumUpdateInterval() const
906 {
907 const KDirLister* dirLister = m_dirLister.data();
908 return dirLister && !dirLister->url().isLocalFile();
909 }
910
911 #include "kfileitemmodel.moc"