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