]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodel.cpp
Fix grouping-issue with not visible sorting roles
[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("name", parent),
35 m_dirLister(dirLister),
36 m_naturalSorting(true),
37 m_sortFoldersFirst(true),
38 m_sortRole(NameRole),
39 m_roles(),
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_pendingEmitLoadingCompleted(false),
49 m_groups(),
50 m_rootExpansionLevel(-1),
51 m_expandedUrls(),
52 m_restoredExpandedUrls()
53 {
54 resetRoles();
55 m_requestRole[NameRole] = true;
56 m_requestRole[IsDirRole] = true;
57
58 Q_ASSERT(dirLister);
59
60 connect(dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
61 connect(dirLister, SIGNAL(completed()), this, SLOT(slotCompleted()));
62 connect(dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
63 connect(dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
64 connect(dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
65 connect(dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
66 connect(dirLister, SIGNAL(clear(KUrl)), this, SLOT(slotClear(KUrl)));
67
68 // Although the layout engine of KItemListView is fast it is very inefficient to e.g.
69 // emit 50 itemsInserted()-signals each 100 ms. m_minimumUpdateIntervalTimer assures that updates
70 // are done in 1 second intervals for equal operations.
71 m_minimumUpdateIntervalTimer = new QTimer(this);
72 m_minimumUpdateIntervalTimer->setInterval(1000);
73 m_minimumUpdateIntervalTimer->setSingleShot(true);
74 connect(m_minimumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItemsToInsert()));
75
76 // For slow KIO-slaves like used for searching it makes sense to show results periodically even
77 // before the completed() or canceled() signal has been emitted.
78 m_maximumUpdateIntervalTimer = new QTimer(this);
79 m_maximumUpdateIntervalTimer->setInterval(2000);
80 m_maximumUpdateIntervalTimer->setSingleShot(true);
81 connect(m_maximumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItemsToInsert()));
82
83 Q_ASSERT(m_minimumUpdateIntervalTimer->interval() <= m_maximumUpdateIntervalTimer->interval());
84 }
85
86 KFileItemModel::~KFileItemModel()
87 {
88 }
89
90 int KFileItemModel::count() const
91 {
92 return m_data.count();
93 }
94
95 QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
96 {
97 if (index >= 0 && index < count()) {
98 return m_data.at(index);
99 }
100 return QHash<QByteArray, QVariant>();
101 }
102
103 bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& values)
104 {
105 if (index >= 0 && index < count()) {
106 QHash<QByteArray, QVariant> currentValue = m_data.at(index);
107
108 QSet<QByteArray> changedRoles;
109 QHashIterator<QByteArray, QVariant> it(values);
110 while (it.hasNext()) {
111 it.next();
112 const QByteArray role = it.key();
113 const QVariant value = it.value();
114
115 if (currentValue[role] != value) {
116 currentValue[role] = value;
117 changedRoles.insert(role);
118 }
119 }
120
121 if (!changedRoles.isEmpty()) {
122 m_data[index] = currentValue;
123 emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
124 }
125
126 return true;
127 }
128 return false;
129 }
130
131 void KFileItemModel::setSortFoldersFirst(bool foldersFirst)
132 {
133 if (foldersFirst != m_sortFoldersFirst) {
134 m_sortFoldersFirst = foldersFirst;
135 resortAllItems();
136 }
137 }
138
139 bool KFileItemModel::sortFoldersFirst() const
140 {
141 return m_sortFoldersFirst;
142 }
143
144 QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
145 {
146 QMimeData* data = new QMimeData();
147
148 // The following code has been taken from KDirModel::mimeData()
149 // (kdelibs/kio/kio/kdirmodel.cpp)
150 // Copyright (C) 2006 David Faure <faure@kde.org>
151 KUrl::List urls;
152 KUrl::List mostLocalUrls;
153 bool canUseMostLocalUrls = true;
154
155 QSetIterator<int> it(indexes);
156 while (it.hasNext()) {
157 const int index = it.next();
158 const KFileItem item = fileItem(index);
159 if (!item.isNull()) {
160 urls << item.url();
161
162 bool isLocal;
163 mostLocalUrls << item.mostLocalUrl(isLocal);
164 if (!isLocal) {
165 canUseMostLocalUrls = false;
166 }
167 }
168 }
169
170 const bool different = canUseMostLocalUrls && mostLocalUrls != urls;
171 urls = KDirModel::simplifiedUrlList(urls); // TODO: Check if we still need KDirModel for this in KDE 5.0
172 if (different) {
173 mostLocalUrls = KDirModel::simplifiedUrlList(mostLocalUrls);
174 urls.populateMimeData(mostLocalUrls, data);
175 } else {
176 urls.populateMimeData(data);
177 }
178
179 return data;
180 }
181
182 int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const
183 {
184 startFromIndex = qMax(0, startFromIndex);
185 for (int i = startFromIndex; i < count(); ++i) {
186 if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) {
187 return i;
188 }
189 }
190 for (int i = 0; i < startFromIndex; ++i) {
191 if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) {
192 return i;
193 }
194 }
195 return -1;
196 }
197
198 bool KFileItemModel::supportsDropping(int index) const
199 {
200 const KFileItem item = fileItem(index);
201 return item.isNull() ? false : item.isDir();
202 }
203
204 QString KFileItemModel::roleDescription(const QByteArray& role) const
205 {
206 QString descr;
207
208 switch (roleIndex(role)) {
209 case NameRole: descr = i18nc("@item:intable", "Name"); break;
210 case SizeRole: descr = i18nc("@item:intable", "Size"); break;
211 case DateRole: descr = i18nc("@item:intable", "Date"); break;
212 case PermissionsRole: descr = i18nc("@item:intable", "Permissions"); break;
213 case OwnerRole: descr = i18nc("@item:intable", "Owner"); break;
214 case GroupRole: descr = i18nc("@item:intable", "Group"); break;
215 case TypeRole: descr = i18nc("@item:intable", "Type"); break;
216 case DestinationRole: descr = i18nc("@item:intable", "Destination"); break;
217 case PathRole: descr = i18nc("@item:intable", "Path"); break;
218 case NoRole: break;
219 case IsDirRole: break;
220 case IsExpandedRole: break;
221 case ExpansionLevelRole: break;
222 default: Q_ASSERT(false); break;
223 }
224
225 return descr;
226 }
227
228 QList<QPair<int, QVariant> > KFileItemModel::groups() const
229 {
230 if (!m_data.isEmpty() && m_groups.isEmpty()) {
231 #ifdef KFILEITEMMODEL_DEBUG
232 QElapsedTimer timer;
233 timer.start();
234 #endif
235 switch (roleIndex(sortRole())) {
236 case NameRole: m_groups = nameRoleGroups(); break;
237 case SizeRole: m_groups = sizeRoleGroups(); break;
238 case DateRole: m_groups = dateRoleGroups(); break;
239 case PermissionsRole: m_groups = permissionRoleGroups(); break;
240 case OwnerRole: m_groups = genericStringRoleGroups("owner"); break;
241 case GroupRole: m_groups = genericStringRoleGroups("group"); break;
242 case TypeRole: m_groups = genericStringRoleGroups("type"); break;
243 case DestinationRole: m_groups = genericStringRoleGroups("destination"); break;
244 case PathRole: m_groups = genericStringRoleGroups("path"); break;
245 case NoRole: break;
246 case IsDirRole: break;
247 case IsExpandedRole: break;
248 case ExpansionLevelRole: break;
249 default: Q_ASSERT(false); break;
250 }
251
252 #ifdef KFILEITEMMODEL_DEBUG
253 kDebug() << "[TIME] Calculating groups for" << count() << "items:" << timer.elapsed();
254 #endif
255 }
256
257 return m_groups;
258 }
259
260 KFileItem KFileItemModel::fileItem(int index) const
261 {
262 if (index >= 0 && index < count()) {
263 return m_sortedItems.at(index);
264 }
265
266 return KFileItem();
267 }
268
269 KFileItem KFileItemModel::fileItem(const KUrl& url) const
270 {
271 const int index = m_items.value(url, -1);
272 if (index >= 0) {
273 return m_sortedItems.at(index);
274 }
275 return KFileItem();
276 }
277
278 int KFileItemModel::index(const KFileItem& item) const
279 {
280 if (item.isNull()) {
281 return -1;
282 }
283
284 return m_items.value(item.url(), -1);
285 }
286
287 int KFileItemModel::index(const KUrl& url) const
288 {
289 KUrl urlToFind = url;
290 urlToFind.adjustPath(KUrl::RemoveTrailingSlash);
291 return m_items.value(urlToFind, -1);
292 }
293
294 KFileItem KFileItemModel::rootItem() const
295 {
296 const KDirLister* dirLister = m_dirLister.data();
297 if (dirLister) {
298 return dirLister->rootItem();
299 }
300 return KFileItem();
301 }
302
303 void KFileItemModel::clear()
304 {
305 slotClear();
306 }
307
308 void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
309 {
310 m_roles = roles;
311
312 if (count() > 0) {
313 const bool supportedExpanding = m_requestRole[IsExpandedRole] && m_requestRole[ExpansionLevelRole];
314 const bool willSupportExpanding = roles.contains("isExpanded") && roles.contains("expansionLevel");
315 if (supportedExpanding && !willSupportExpanding) {
316 // No expanding is supported anymore. Take care to delete all items that have an expansion level
317 // that is not 0 (and hence are part of an expanded item).
318 removeExpandedItems();
319 }
320 }
321
322 resetRoles();
323
324 QSetIterator<QByteArray> it(roles);
325 while (it.hasNext()) {
326 const QByteArray& role = it.next();
327 m_requestRole[roleIndex(role)] = true;
328 }
329
330 if (count() > 0) {
331 // Update m_data with the changed requested roles
332 const int maxIndex = count() - 1;
333 for (int i = 0; i <= maxIndex; ++i) {
334 m_data[i] = retrieveData(m_sortedItems.at(i));
335 }
336
337 kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
338 emit itemsChanged(KItemRangeList() << KItemRange(0, count()), QSet<QByteArray>());
339 }
340 }
341
342 QSet<QByteArray> KFileItemModel::roles() const
343 {
344 return m_roles;
345 }
346
347 bool KFileItemModel::setExpanded(int index, bool expanded)
348 {
349 if (isExpanded(index) == expanded || index < 0 || index >= count()) {
350 return false;
351 }
352
353 QHash<QByteArray, QVariant> values;
354 values.insert("isExpanded", expanded);
355 if (!setData(index, values)) {
356 return false;
357 }
358
359 const KUrl url = m_sortedItems.at(index).url();
360 if (expanded) {
361 m_expandedUrls.insert(url);
362
363 KDirLister* dirLister = m_dirLister.data();
364 if (dirLister) {
365 dirLister->openUrl(url, KDirLister::Keep);
366 return true;
367 }
368 } else {
369 m_expandedUrls.remove(url);
370
371 KFileItemList itemsToRemove;
372 const int expansionLevel = data(index)["expansionLevel"].toInt();
373 ++index;
374 while (index < count() && data(index)["expansionLevel"].toInt() > expansionLevel) {
375 itemsToRemove.append(m_sortedItems.at(index));
376 ++index;
377 }
378 removeItems(itemsToRemove);
379 return true;
380 }
381
382 return false;
383 }
384
385 bool KFileItemModel::isExpanded(int index) const
386 {
387 if (index >= 0 && index < count()) {
388 return m_data.at(index).value("isExpanded").toBool();
389 }
390 return false;
391 }
392
393 bool KFileItemModel::isExpandable(int index) const
394 {
395 if (index >= 0 && index < count()) {
396 return m_sortedItems.at(index).isDir();
397 }
398 return false;
399 }
400
401 QSet<KUrl> KFileItemModel::expandedUrls() const
402 {
403 return m_expandedUrls;
404 }
405
406 void KFileItemModel::restoreExpandedUrls(const QSet<KUrl>& urls)
407 {
408 m_restoredExpandedUrls = urls;
409 }
410
411 void KFileItemModel::onGroupedSortingChanged(bool current)
412 {
413 Q_UNUSED(current);
414 m_groups.clear();
415 }
416
417 void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
418 {
419 Q_UNUSED(previous);
420 m_sortRole = roleIndex(current);
421
422 #ifdef KFILEITEMMODEL_DEBUG
423 if (!m_requestRole[m_sortRole]) {
424 kWarning() << "The sort-role has been changed to a role that has not been received yet";
425 }
426 #endif
427
428 resortAllItems();
429 }
430
431 void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
432 {
433 Q_UNUSED(current);
434 Q_UNUSED(previous);
435 resortAllItems();
436 }
437
438 void KFileItemModel::slotCompleted()
439 {
440 if (m_restoredExpandedUrls.isEmpty() && m_minimumUpdateIntervalTimer->isActive()) {
441 // dispatchPendingItems() will be called when the timer
442 // has been expired.
443 m_pendingEmitLoadingCompleted = true;
444 return;
445 }
446
447 m_pendingEmitLoadingCompleted = false;
448 dispatchPendingItemsToInsert();
449
450 if (!m_restoredExpandedUrls.isEmpty()) {
451 // Try to find a URL that can be expanded.
452 // Note that the parent folder must be expanded before any of its subfolders become visible.
453 // Therefore, some URLs in m_restoredExpandedUrls might not be visible yet
454 // -> we expand the first visible URL we find in m_restoredExpandedUrls.
455 foreach(const KUrl& url, m_restoredExpandedUrls) {
456 const int index = m_items.value(url, -1);
457 if (index >= 0) {
458 // We have found an expandable URL. Expand it and return - when
459 // the dir lister has finished, this slot will be called again.
460 m_restoredExpandedUrls.remove(url);
461 setExpanded(index, true);
462 return;
463 }
464 }
465
466 // None of the URLs in m_restoredExpandedUrls could be found in the model. This can happen
467 // if these URLs have been deleted in the meantime.
468 m_restoredExpandedUrls.clear();
469 }
470
471 emit loadingCompleted();
472 m_minimumUpdateIntervalTimer->start();
473 }
474
475 void KFileItemModel::slotCanceled()
476 {
477 m_minimumUpdateIntervalTimer->stop();
478 m_maximumUpdateIntervalTimer->stop();
479 dispatchPendingItemsToInsert();
480 }
481
482 void KFileItemModel::slotNewItems(const KFileItemList& items)
483 {
484 m_pendingItemsToInsert.append(items);
485
486 if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
487 // Assure that items get dispatched if no completed() or canceled() signal is
488 // emitted during the maximum update interval.
489 m_maximumUpdateIntervalTimer->start();
490 }
491 }
492
493 void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
494 {
495 if (!m_pendingItemsToInsert.isEmpty()) {
496 insertItems(m_pendingItemsToInsert);
497 m_pendingItemsToInsert.clear();
498 }
499 removeItems(items);
500 }
501
502 void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
503 {
504 Q_ASSERT(!items.isEmpty());
505 #ifdef KFILEITEMMODEL_DEBUG
506 kDebug() << "Refreshing" << items.count() << "items";
507 #endif
508
509 m_groups.clear();
510
511 // Get the indexes of all items that have been refreshed
512 QList<int> indexes;
513 indexes.reserve(items.count());
514
515 QListIterator<QPair<KFileItem, KFileItem> > it(items);
516 while (it.hasNext()) {
517 const QPair<KFileItem, KFileItem>& itemPair = it.next();
518 const int index = m_items.value(itemPair.second.url(), -1);
519 if (index >= 0) {
520 indexes.append(index);
521 }
522 }
523
524 // If the changed items have been created recently, they might not be in m_items yet.
525 // In that case, the list 'indexes' might be empty.
526 if (indexes.isEmpty()) {
527 return;
528 }
529
530 // Extract the item-ranges out of the changed indexes
531 qSort(indexes);
532
533 KItemRangeList itemRangeList;
534 int rangeIndex = 0;
535 int rangeCount = 1;
536 int previousIndex = indexes.at(0);
537
538 const int maxIndex = indexes.count() - 1;
539 for (int i = 1; i <= maxIndex; ++i) {
540 const int currentIndex = indexes.at(i);
541 if (currentIndex == previousIndex + 1) {
542 ++rangeCount;
543 } else {
544 itemRangeList.append(KItemRange(rangeIndex, rangeCount));
545
546 rangeIndex = currentIndex;
547 rangeCount = 1;
548 }
549 previousIndex = currentIndex;
550 }
551
552 if (rangeCount > 0) {
553 itemRangeList.append(KItemRange(rangeIndex, rangeCount));
554 }
555
556 emit itemsChanged(itemRangeList, QSet<QByteArray>());
557 }
558
559 void KFileItemModel::slotClear()
560 {
561 #ifdef KFILEITEMMODEL_DEBUG
562 kDebug() << "Clearing all items";
563 #endif
564
565 m_groups.clear();
566
567 m_minimumUpdateIntervalTimer->stop();
568 m_maximumUpdateIntervalTimer->stop();
569 m_pendingItemsToInsert.clear();
570
571 m_rootExpansionLevel = -1;
572
573 const int removedCount = m_data.count();
574 if (removedCount > 0) {
575 m_sortedItems.clear();
576 m_items.clear();
577 m_data.clear();
578 emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
579 }
580
581 m_expandedUrls.clear();
582 }
583
584 void KFileItemModel::slotClear(const KUrl& url)
585 {
586 Q_UNUSED(url);
587 }
588
589 void KFileItemModel::dispatchPendingItemsToInsert()
590 {
591 if (!m_pendingItemsToInsert.isEmpty()) {
592 insertItems(m_pendingItemsToInsert);
593 m_pendingItemsToInsert.clear();
594 }
595
596 if (m_pendingEmitLoadingCompleted) {
597 emit loadingCompleted();
598 }
599 }
600
601 void KFileItemModel::insertItems(const KFileItemList& items)
602 {
603 if (items.isEmpty()) {
604 return;
605 }
606
607 #ifdef KFILEITEMMODEL_DEBUG
608 QElapsedTimer timer;
609 timer.start();
610 kDebug() << "===========================================================";
611 kDebug() << "Inserting" << items.count() << "items";
612 #endif
613
614 m_groups.clear();
615
616 KFileItemList sortedItems = items;
617 sort(sortedItems.begin(), sortedItems.end());
618
619 #ifdef KFILEITEMMODEL_DEBUG
620 kDebug() << "[TIME] Sorting:" << timer.elapsed();
621 #endif
622
623 KItemRangeList itemRanges;
624 int targetIndex = 0;
625 int sourceIndex = 0;
626 int insertedAtIndex = -1; // Index for the current item-range
627 int insertedCount = 0; // Count for the current item-range
628 int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
629 while (sourceIndex < sortedItems.count()) {
630 // Find target index from m_items to insert the current item
631 // in a sorted order
632 const int previousTargetIndex = targetIndex;
633 while (targetIndex < m_sortedItems.count()) {
634 if (!lessThan(m_sortedItems.at(targetIndex), sortedItems.at(sourceIndex))) {
635 break;
636 }
637 ++targetIndex;
638 }
639
640 if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
641 itemRanges << KItemRange(insertedAtIndex, insertedCount);
642 previouslyInsertedCount += insertedCount;
643 insertedAtIndex = targetIndex - previouslyInsertedCount;
644 insertedCount = 0;
645 }
646
647 // Insert item at the position targetIndex
648 const KFileItem item = sortedItems.at(sourceIndex);
649 m_sortedItems.insert(targetIndex, item);
650 m_data.insert(targetIndex, retrieveData(item));
651 // m_items will be inserted after the loop (see comment below)
652 ++insertedCount;
653
654 if (insertedAtIndex < 0) {
655 insertedAtIndex = targetIndex;
656 Q_ASSERT(previouslyInsertedCount == 0);
657 }
658 ++targetIndex;
659 ++sourceIndex;
660 }
661
662 // The indexes of all m_items must be adjusted, not only the index
663 // of the new items
664 for (int i = 0; i < m_sortedItems.count(); ++i) {
665 m_items.insert(m_sortedItems.at(i).url(), i);
666 }
667
668 itemRanges << KItemRange(insertedAtIndex, insertedCount);
669 emit itemsInserted(itemRanges);
670
671 #ifdef KFILEITEMMODEL_DEBUG
672 kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
673 #endif
674 }
675
676 void KFileItemModel::removeItems(const KFileItemList& items)
677 {
678 if (items.isEmpty()) {
679 return;
680 }
681
682 #ifdef KFILEITEMMODEL_DEBUG
683 kDebug() << "Removing " << items.count() << "items";
684 #endif
685
686 m_groups.clear();
687
688 KFileItemList sortedItems = items;
689 sort(sortedItems.begin(), sortedItems.end());
690
691 QList<int> indexesToRemove;
692 indexesToRemove.reserve(items.count());
693
694 // Calculate the item ranges that will get deleted
695 KItemRangeList itemRanges;
696 int removedAtIndex = -1;
697 int removedCount = 0;
698 int targetIndex = 0;
699 foreach (const KFileItem& itemToRemove, sortedItems) {
700 const int previousTargetIndex = targetIndex;
701 while (targetIndex < m_sortedItems.count()) {
702 if (m_sortedItems.at(targetIndex).url() == itemToRemove.url()) {
703 break;
704 }
705 ++targetIndex;
706 }
707 if (targetIndex >= m_sortedItems.count()) {
708 kWarning() << "Item that should be deleted has not been found!";
709 return;
710 }
711
712 if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
713 itemRanges << KItemRange(removedAtIndex, removedCount);
714 removedAtIndex = targetIndex;
715 removedCount = 0;
716 }
717
718 indexesToRemove.append(targetIndex);
719 if (removedAtIndex < 0) {
720 removedAtIndex = targetIndex;
721 }
722 ++removedCount;
723 ++targetIndex;
724 }
725
726 // Delete the items
727 for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
728 const int indexToRemove = indexesToRemove.at(i);
729 m_items.remove(m_sortedItems.at(indexToRemove).url());
730 m_sortedItems.removeAt(indexToRemove);
731 m_data.removeAt(indexToRemove);
732 }
733
734 // The indexes of all m_items must be adjusted, not only the index
735 // of the removed items
736 for (int i = 0; i < m_sortedItems.count(); ++i) {
737 m_items.insert(m_sortedItems.at(i).url(), i);
738 }
739
740 if (count() <= 0) {
741 m_rootExpansionLevel = -1;
742 }
743
744 itemRanges << KItemRange(removedAtIndex, removedCount);
745 emit itemsRemoved(itemRanges);
746 }
747
748 void KFileItemModel::resortAllItems()
749 {
750 const int itemCount = count();
751 if (itemCount <= 0) {
752 return;
753 }
754
755 const KFileItemList oldSortedItems = m_sortedItems;
756 const QHash<KUrl, int> oldItems = m_items;
757 const QList<QHash<QByteArray, QVariant> > oldData = m_data;
758
759 m_groups.clear();
760 m_items.clear();
761 m_data.clear();
762
763 sort(m_sortedItems.begin(), m_sortedItems.end());
764 int index = 0;
765 foreach (const KFileItem& item, m_sortedItems) {
766 m_items.insert(item.url(), index);
767
768 const int oldItemIndex = oldItems.value(item.url());
769 m_data.append(oldData.at(oldItemIndex));
770
771 ++index;
772 }
773
774 bool emitItemsMoved = false;
775 QList<int> movedToIndexes;
776 movedToIndexes.reserve(m_sortedItems.count());
777 for (int i = 0; i < itemCount; i++) {
778 const int newIndex = m_items.value(oldSortedItems.at(i).url());
779 movedToIndexes.append(newIndex);
780 if (!emitItemsMoved && newIndex != i) {
781 emitItemsMoved = true;
782 }
783 }
784
785 if (emitItemsMoved) {
786 emit itemsMoved(KItemRange(0, itemCount), movedToIndexes);
787 }
788 }
789
790 void KFileItemModel::removeExpandedItems()
791 {
792 KFileItemList expandedItems;
793
794 const int maxIndex = m_data.count() - 1;
795 for (int i = 0; i <= maxIndex; ++i) {
796 if (m_data.at(i).value("expansionLevel").toInt() > 0) {
797 const KFileItem fileItem = m_sortedItems.at(i);
798 expandedItems.append(fileItem);
799 }
800 }
801
802 // The m_rootExpansionLevel may not get reset before all items with
803 // a bigger expansionLevel have been removed.
804 Q_ASSERT(m_rootExpansionLevel >= 0);
805 removeItems(expandedItems);
806
807 m_rootExpansionLevel = -1;
808 m_expandedUrls.clear();
809 }
810
811 void KFileItemModel::resetRoles()
812 {
813 for (int i = 0; i < RolesCount; ++i) {
814 m_requestRole[i] = false;
815 }
816 }
817
818 KFileItemModel::Role KFileItemModel::roleIndex(const QByteArray& role) const
819 {
820 static QHash<QByteArray, Role> rolesHash;
821 if (rolesHash.isEmpty()) {
822 rolesHash.insert("name", NameRole);
823 rolesHash.insert("size", SizeRole);
824 rolesHash.insert("date", DateRole);
825 rolesHash.insert("permissions", PermissionsRole);
826 rolesHash.insert("owner", OwnerRole);
827 rolesHash.insert("group", GroupRole);
828 rolesHash.insert("type", TypeRole);
829 rolesHash.insert("destination", DestinationRole);
830 rolesHash.insert("path", PathRole);
831 rolesHash.insert("isDir", IsDirRole);
832 rolesHash.insert("isExpanded", IsExpandedRole);
833 rolesHash.insert("expansionLevel", ExpansionLevelRole);
834 }
835 return rolesHash.value(role, NoRole);
836 }
837
838 QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) const
839 {
840 // It is important to insert only roles that are fast to retrieve. E.g.
841 // KFileItem::iconName() can be very expensive if the MIME-type is unknown
842 // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
843 QHash<QByteArray, QVariant> data;
844 data.insert("iconPixmap", QPixmap());
845
846 const bool isDir = item.isDir();
847 if (m_requestRole[IsDirRole]) {
848 data.insert("isDir", isDir);
849 }
850
851 if (m_requestRole[NameRole]) {
852 data.insert("name", item.name());
853 }
854
855 if (m_requestRole[SizeRole]) {
856 if (isDir) {
857 data.insert("size", QVariant());
858 } else {
859 data.insert("size", item.size());
860 }
861 }
862
863 if (m_requestRole[DateRole]) {
864 // Don't use KFileItem::timeString() as this is too expensive when
865 // having several thousands of items. Instead the formatting of the
866 // date-time will be done on-demand by the view when the date will be shown.
867 const KDateTime dateTime = item.time(KFileItem::ModificationTime);
868 data.insert("date", dateTime.dateTime());
869 }
870
871 if (m_requestRole[PermissionsRole]) {
872 data.insert("permissions", item.permissionsString());
873 }
874
875 if (m_requestRole[OwnerRole]) {
876 data.insert("owner", item.user());
877 }
878
879 if (m_requestRole[GroupRole]) {
880 data.insert("group", item.group());
881 }
882
883 if (m_requestRole[DestinationRole]) {
884 QString destination = item.linkDest();
885 if (destination.isEmpty()) {
886 destination = i18nc("@item:intable", "No destination");
887 }
888 data.insert("destination", destination);
889 }
890
891 if (m_requestRole[PathRole]) {
892 data.insert("path", item.localPath());
893 }
894
895 if (m_requestRole[IsExpandedRole]) {
896 data.insert("isExpanded", false);
897 }
898
899 if (m_requestRole[ExpansionLevelRole]) {
900 if (m_rootExpansionLevel < 0) {
901 KDirLister* dirLister = m_dirLister.data();
902 if (dirLister) {
903 const QString rootDir = dirLister->url().directory(KUrl::AppendTrailingSlash);
904 m_rootExpansionLevel = rootDir.count('/');
905 }
906 }
907 const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
908 const int level = dir.count('/') - m_rootExpansionLevel - 1;
909 data.insert("expansionLevel", level);
910 }
911
912 if (item.isMimeTypeKnown()) {
913 data.insert("iconName", item.iconName());
914
915 if (m_requestRole[TypeRole]) {
916 data.insert("type", item.mimeComment());
917 }
918 }
919
920 return data;
921 }
922
923 bool KFileItemModel::lessThan(const KFileItem& a, const KFileItem& b) const
924 {
925 int result = 0;
926
927 if (m_rootExpansionLevel >= 0) {
928 result = expansionLevelsCompare(a, b);
929 if (result != 0) {
930 // The items have parents with different expansion levels
931 return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
932 }
933 }
934
935 if (m_sortFoldersFirst || m_sortRole == SizeRole) {
936 const bool isDirA = a.isDir();
937 const bool isDirB = b.isDir();
938 if (isDirA && !isDirB) {
939 return true;
940 } else if (!isDirA && isDirB) {
941 return false;
942 }
943 }
944
945 switch (m_sortRole) {
946 case NameRole: {
947 result = stringCompare(a.text(), b.text());
948 if (result == 0) {
949 // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
950 result = stringCompare(a.name(m_caseSensitivity == Qt::CaseInsensitive),
951 b.name(m_caseSensitivity == Qt::CaseInsensitive));
952 }
953 break;
954 }
955
956 case DateRole: {
957 const KDateTime dateTimeA = a.time(KFileItem::ModificationTime);
958 const KDateTime dateTimeB = b.time(KFileItem::ModificationTime);
959 if (dateTimeA < dateTimeB) {
960 result = -1;
961 } else if (dateTimeA > dateTimeB) {
962 result = +1;
963 }
964 break;
965 }
966
967 case SizeRole: {
968 // TODO: Implement sorting folders by the number of items inside.
969 // This is more tricky to get right because this number is retrieved
970 // asynchronously by KFileItemModelRolesUpdater.
971 const KIO::filesize_t sizeA = a.size();
972 const KIO::filesize_t sizeB = b.size();
973 if (sizeA < sizeB) {
974 result = -1;
975 } else if (sizeA > sizeB) {
976 result = +1;
977 }
978 break;
979 }
980
981 default:
982 break;
983 }
984
985 if (result == 0) {
986 // It must be assured that the sort order is always unique even if two values have been
987 // equal. In this case a comparison of the URL is done which is unique in all cases
988 // within KDirLister.
989 result = QString::compare(a.url().url(), b.url().url(), Qt::CaseSensitive);
990 }
991
992 return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
993 }
994
995 void KFileItemModel::sort(const KFileItemList::iterator& startIterator, const KFileItemList::iterator& endIterator)
996 {
997 KFileItemList::iterator start = startIterator;
998 KFileItemList::iterator end = endIterator;
999
1000 // The implementation is based on qSortHelper() from qalgorithms.h
1001 // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
1002 // In opposite to qSort() it allows to use a member-function for the comparison of elements.
1003 while (1) {
1004 int span = int(end - start);
1005 if (span < 2) {
1006 return;
1007 }
1008
1009 --end;
1010 KFileItemList::iterator low = start, high = end - 1;
1011 KFileItemList::iterator pivot = start + span / 2;
1012
1013 if (lessThan(*end, *start)) {
1014 qSwap(*end, *start);
1015 }
1016 if (span == 2) {
1017 return;
1018 }
1019
1020 if (lessThan(*pivot, *start)) {
1021 qSwap(*pivot, *start);
1022 }
1023 if (lessThan(*end, *pivot)) {
1024 qSwap(*end, *pivot);
1025 }
1026 if (span == 3) {
1027 return;
1028 }
1029
1030 qSwap(*pivot, *end);
1031
1032 while (low < high) {
1033 while (low < high && lessThan(*low, *end)) {
1034 ++low;
1035 }
1036
1037 while (high > low && lessThan(*end, *high)) {
1038 --high;
1039 }
1040 if (low < high) {
1041 qSwap(*low, *high);
1042 ++low;
1043 --high;
1044 } else {
1045 break;
1046 }
1047 }
1048
1049 if (lessThan(*low, *end)) {
1050 ++low;
1051 }
1052
1053 qSwap(*end, *low);
1054 sort(start, low);
1055
1056 start = low + 1;
1057 ++end;
1058 }
1059 }
1060
1061 int KFileItemModel::stringCompare(const QString& a, const QString& b) const
1062 {
1063 // Taken from KDirSortFilterProxyModel (kdelibs/kfile/kdirsortfilterproxymodel.*)
1064 // Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at>
1065 // Copyright (C) 2006 by Dominic Battre <dominic@battre.de>
1066 // Copyright (C) 2006 by Martin Pool <mbp@canonical.com>
1067
1068 if (m_caseSensitivity == Qt::CaseInsensitive) {
1069 const int result = m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseInsensitive)
1070 : QString::compare(a, b, Qt::CaseInsensitive);
1071 if (result != 0) {
1072 // Only return the result, if the strings are not equal. If they are equal by a case insensitive
1073 // comparison, still a deterministic sort order is required. A case sensitive
1074 // comparison is done as fallback.
1075 return result;
1076 }
1077 }
1078
1079 return m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseSensitive)
1080 : QString::compare(a, b, Qt::CaseSensitive);
1081 }
1082
1083 int KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b) const
1084 {
1085 const KUrl urlA = a.url();
1086 const KUrl urlB = b.url();
1087 if (urlA.directory() == urlB.directory()) {
1088 // Both items have the same directory as parent
1089 return 0;
1090 }
1091
1092 // Check whether one item is the parent of the other item
1093 if (urlA.isParentOf(urlB)) {
1094 return -1;
1095 } else if (urlB.isParentOf(urlA)) {
1096 return +1;
1097 }
1098
1099 // Determine the maximum common path of both items and
1100 // remember the index in 'index'
1101 const QString pathA = urlA.path();
1102 const QString pathB = urlB.path();
1103
1104 const int maxIndex = qMin(pathA.length(), pathB.length()) - 1;
1105 int index = 0;
1106 while (index <= maxIndex && pathA.at(index) == pathB.at(index)) {
1107 ++index;
1108 }
1109 if (index > maxIndex) {
1110 index = maxIndex;
1111 }
1112 while ((pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/')) && index > 0) {
1113 --index;
1114 }
1115
1116 // Determine the first sub-path after the common path and
1117 // check whether it represents a directory or already a file
1118 bool isDirA = true;
1119 const QString subPathA = subPath(a, pathA, index, &isDirA);
1120 bool isDirB = true;
1121 const QString subPathB = subPath(b, pathB, index, &isDirB);
1122
1123 if (isDirA && !isDirB) {
1124 return -1;
1125 } else if (!isDirA && isDirB) {
1126 return +1;
1127 }
1128
1129 return stringCompare(subPathA, subPathB);
1130 }
1131
1132 QString KFileItemModel::subPath(const KFileItem& item,
1133 const QString& itemPath,
1134 int start,
1135 bool* isDir) const
1136 {
1137 Q_ASSERT(isDir);
1138 const int pathIndex = itemPath.indexOf('/', start + 1);
1139 *isDir = (pathIndex > 0) || item.isDir();
1140 return itemPath.mid(start, pathIndex - start);
1141 }
1142
1143 bool KFileItemModel::useMaximumUpdateInterval() const
1144 {
1145 const KDirLister* dirLister = m_dirLister.data();
1146 return dirLister && !dirLister->url().isLocalFile();
1147 }
1148
1149 QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
1150 {
1151 Q_ASSERT(!m_data.isEmpty());
1152
1153 const int maxIndex = count() - 1;
1154 QList<QPair<int, QVariant> > groups;
1155
1156 QString groupValue;
1157 QChar firstChar;
1158 bool isLetter = false;
1159 for (int i = 0; i <= maxIndex; ++i) {
1160 if (isChildItem(i)) {
1161 continue;
1162 }
1163
1164 const QString name = m_data.at(i).value("name").toString();
1165
1166 // Use the first character of the name as group indication
1167 QChar newFirstChar = name.at(0).toUpper();
1168 if (newFirstChar == QLatin1Char('~') && name.length() > 1) {
1169 newFirstChar = name.at(1);
1170 }
1171
1172 if (firstChar != newFirstChar) {
1173 QString newGroupValue;
1174 if (newFirstChar >= QLatin1Char('A') && newFirstChar <= QLatin1Char('Z')) {
1175 // Apply group 'A' - 'Z'
1176 newGroupValue = newFirstChar;
1177 isLetter = true;
1178 } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
1179 // Apply group '0 - 9' for any name that starts with a digit
1180 newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
1181 isLetter = false;
1182 } else {
1183 if (isLetter) {
1184 // If the current group is 'A' - 'Z' check whether a locale character
1185 // fits into the existing group.
1186 // TODO: This does not work in the case if e.g. the group 'O' starts with
1187 // an umlaut 'O' -> provide unit-test to document this known issue
1188 const QChar prevChar(firstChar.unicode() - ushort(1));
1189 const QChar nextChar(firstChar.unicode() + ushort(1));
1190 const QString currChar(newFirstChar);
1191 const bool partOfCurrentGroup = currChar.localeAwareCompare(prevChar) > 0 &&
1192 currChar.localeAwareCompare(nextChar) < 0;
1193 if (partOfCurrentGroup) {
1194 continue;
1195 }
1196 }
1197 newGroupValue = i18nc("@title:group", "Others");
1198 isLetter = false;
1199 }
1200
1201 if (newGroupValue != groupValue) {
1202 groupValue = newGroupValue;
1203 groups.append(QPair<int, QVariant>(i, newGroupValue));
1204 }
1205
1206 firstChar = newFirstChar;
1207 }
1208 }
1209 return groups;
1210 }
1211
1212 QList<QPair<int, QVariant> > KFileItemModel::sizeRoleGroups() const
1213 {
1214 Q_ASSERT(!m_data.isEmpty());
1215
1216 const int maxIndex = count() - 1;
1217 QList<QPair<int, QVariant> > groups;
1218
1219 QString groupValue;
1220 for (int i = 0; i <= maxIndex; ++i) {
1221 if (isChildItem(i)) {
1222 continue;
1223 }
1224
1225 const KFileItem& item = m_sortedItems.at(i);
1226 const KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
1227 QString newGroupValue;
1228 if (!item.isNull() && item.isDir()) {
1229 newGroupValue = i18nc("@title:group Size", "Folders");
1230 } else if (fileSize < 5 * 1024 * 1024) {
1231 newGroupValue = i18nc("@title:group Size", "Small");
1232 } else if (fileSize < 10 * 1024 * 1024) {
1233 newGroupValue = i18nc("@title:group Size", "Medium");
1234 } else {
1235 newGroupValue = i18nc("@title:group Size", "Big");
1236 }
1237
1238 if (newGroupValue != groupValue) {
1239 groupValue = newGroupValue;
1240 groups.append(QPair<int, QVariant>(i, newGroupValue));
1241 }
1242 }
1243
1244 return groups;
1245 }
1246
1247 QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
1248 {
1249 Q_ASSERT(!m_data.isEmpty());
1250
1251 const int maxIndex = count() - 1;
1252 QList<QPair<int, QVariant> > groups;
1253
1254 const QDate currentDate = KDateTime::currentLocalDateTime().date();
1255
1256 int yearForCurrentWeek = 0;
1257 int currentWeek = currentDate.weekNumber(&yearForCurrentWeek);
1258 if (yearForCurrentWeek == currentDate.year() + 1) {
1259 currentWeek = 53;
1260 }
1261
1262 QDate previousModifiedDate;
1263 QString groupValue;
1264 for (int i = 0; i <= maxIndex; ++i) {
1265 if (isChildItem(i)) {
1266 continue;
1267 }
1268
1269 const KDateTime modifiedTime = m_sortedItems.at(i).time(KFileItem::ModificationTime);
1270 const QDate modifiedDate = modifiedTime.date();
1271 if (modifiedDate == previousModifiedDate) {
1272 // The current item is in the same group as the previous item
1273 continue;
1274 }
1275 previousModifiedDate = modifiedDate;
1276
1277 const int daysDistance = modifiedDate.daysTo(currentDate);
1278
1279 int yearForModifiedWeek = 0;
1280 int modifiedWeek = modifiedDate.weekNumber(&yearForModifiedWeek);
1281 if (yearForModifiedWeek == modifiedDate.year() + 1) {
1282 modifiedWeek = 53;
1283 }
1284
1285 QString newGroupValue;
1286 if (currentDate.year() == modifiedDate.year() && currentDate.month() == modifiedDate.month()) {
1287 if (modifiedWeek > currentWeek) {
1288 // Usecase: modified date = 2010-01-01, current date = 2010-01-22
1289 // modified week = 53, current week = 3
1290 modifiedWeek = 0;
1291 }
1292 switch (currentWeek - modifiedWeek) {
1293 case 0:
1294 switch (daysDistance) {
1295 case 0: newGroupValue = i18nc("@title:group Date", "Today"); break;
1296 case 1: newGroupValue = i18nc("@title:group Date", "Yesterday"); break;
1297 default: newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: %A", "%A"));
1298 }
1299 break;
1300 case 1:
1301 newGroupValue = i18nc("@title:group Date", "Last Week");
1302 break;
1303 case 2:
1304 newGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
1305 break;
1306 case 3:
1307 newGroupValue = i18nc("@title:group Date", "Three Weeks Ago");
1308 break;
1309 case 4:
1310 case 5:
1311 newGroupValue = i18nc("@title:group Date", "Earlier this Month");
1312 break;
1313 default:
1314 Q_ASSERT(false);
1315 }
1316 } else {
1317 const QDate lastMonthDate = currentDate.addMonths(-1);
1318 if (lastMonthDate.year() == modifiedDate.year() && lastMonthDate.month() == modifiedDate.month()) {
1319 if (daysDistance == 1) {
1320 newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Yesterday (%B, %Y)"));
1321 } else if (daysDistance <= 7) {
1322 newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: %A, %B is full month name in current locale, and %Y is full year number", "%A (%B, %Y)"));
1323 } else if (daysDistance <= 7 * 2) {
1324 newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Last Week (%B, %Y)"));
1325 } else if (daysDistance <= 7 * 3) {
1326 newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Two Weeks Ago (%B, %Y)"));
1327 } else if (daysDistance <= 7 * 4) {
1328 newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Three Weeks Ago (%B, %Y)"));
1329 } else {
1330 newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Earlier on %B, %Y"));
1331 }
1332 } else {
1333 newGroupValue = modifiedTime.toString(i18nc("@title:group The month and year: %B is full month name in current locale, and %Y is full year number", "%B, %Y"));
1334 }
1335 }
1336
1337 if (newGroupValue != groupValue) {
1338 groupValue = newGroupValue;
1339 groups.append(QPair<int, QVariant>(i, newGroupValue));
1340 }
1341 }
1342
1343 return groups;
1344 }
1345
1346 QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
1347 {
1348 Q_ASSERT(!m_data.isEmpty());
1349
1350 const int maxIndex = count() - 1;
1351 QList<QPair<int, QVariant> > groups;
1352
1353 QString permissionsString;
1354 QString groupValue;
1355 for (int i = 0; i <= maxIndex; ++i) {
1356 if (isChildItem(i)) {
1357 continue;
1358 }
1359
1360 const QString newPermissionsString = m_data.at(i).value("permissions").toString();
1361 if (newPermissionsString == permissionsString) {
1362 continue;
1363 }
1364 permissionsString = newPermissionsString;
1365
1366 const QFileInfo info(m_sortedItems.at(i).url().pathOrUrl());
1367
1368 // Set user string
1369 QString user;
1370 if (info.permission(QFile::ReadUser)) {
1371 user = i18nc("@item:intext Access permission, concatenated", "Read, ");
1372 }
1373 if (info.permission(QFile::WriteUser)) {
1374 user += i18nc("@item:intext Access permission, concatenated", "Write, ");
1375 }
1376 if (info.permission(QFile::ExeUser)) {
1377 user += i18nc("@item:intext Access permission, concatenated", "Execute, ");
1378 }
1379 user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.count() - 2);
1380
1381 // Set group string
1382 QString group;
1383 if (info.permission(QFile::ReadGroup)) {
1384 group = i18nc("@item:intext Access permission, concatenated", "Read, ");
1385 }
1386 if (info.permission(QFile::WriteGroup)) {
1387 group += i18nc("@item:intext Access permission, concatenated", "Write, ");
1388 }
1389 if (info.permission(QFile::ExeGroup)) {
1390 group += i18nc("@item:intext Access permission, concatenated", "Execute, ");
1391 }
1392 group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.count() - 2);
1393
1394 // Set others string
1395 QString others;
1396 if (info.permission(QFile::ReadOther)) {
1397 others = i18nc("@item:intext Access permission, concatenated", "Read, ");
1398 }
1399 if (info.permission(QFile::WriteOther)) {
1400 others += i18nc("@item:intext Access permission, concatenated", "Write, ");
1401 }
1402 if (info.permission(QFile::ExeOther)) {
1403 others += i18nc("@item:intext Access permission, concatenated", "Execute, ");
1404 }
1405 others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.count() - 2);
1406
1407 const QString newGroupValue = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others);
1408 if (newGroupValue != groupValue) {
1409 groupValue = newGroupValue;
1410 groups.append(QPair<int, QVariant>(i, newGroupValue));
1411 }
1412 }
1413
1414 return groups;
1415 }
1416
1417 QList<QPair<int, QVariant> > KFileItemModel::genericStringRoleGroups(const QByteArray& role) const
1418 {
1419 Q_ASSERT(!m_data.isEmpty());
1420
1421 const int maxIndex = count() - 1;
1422 QList<QPair<int, QVariant> > groups;
1423
1424 QString groupValue;
1425 for (int i = 0; i <= maxIndex; ++i) {
1426 if (isChildItem(i)) {
1427 continue;
1428 }
1429 const QString newGroupValue = m_data.at(i).value(role).toString();
1430 if (newGroupValue != groupValue) {
1431 groupValue = newGroupValue;
1432 groups.append(QPair<int, QVariant>(i, newGroupValue));
1433 }
1434 }
1435
1436 return groups;
1437 }
1438
1439 #include "kfileitemmodel.moc"