X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/93cc4808031cb45da9237dfc0c7759f668259331..d80e9db9fb801bb5230c5b80190e5630bd738823:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 945aa82c8..d64954698 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -28,18 +29,20 @@ #include #include -#define KFILEITEMMODEL_DEBUG +// #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) : KItemModelBase("name", parent), m_dirLister(dirLister), - m_naturalSorting(true), + m_naturalSorting(KGlobalSettings::naturalSorting()), m_sortFoldersFirst(true), m_sortRole(NameRole), m_roles(), m_caseSensitivity(Qt::CaseInsensitive), m_itemData(), m_items(), + m_filter(), + m_filteredItems(), m_requestRole(), m_minimumUpdateIntervalTimer(0), m_maximumUpdateIntervalTimer(0), @@ -47,7 +50,7 @@ KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) : m_pendingItemsToInsert(), m_pendingEmitLoadingCompleted(false), m_groups(), - m_rootExpansionLevel(-1), + m_rootExpansionLevel(UninitializedRootExpansionLevel), m_expandedUrls(), m_urlsToExpand() { @@ -61,7 +64,7 @@ KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) : Q_ASSERT(dirLister); connect(dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled())); - connect(dirLister, SIGNAL(completed()), this, SLOT(slotCompleted())); + connect(dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted())); connect(dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); connect(dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList))); connect(dirLister, SIGNAL(refreshItems(QList >)), this, SLOT(slotRefreshItems(QList >))); @@ -93,6 +96,8 @@ KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) : connect(m_resortAllItemsTimer, SIGNAL(timeout()), this, SLOT(resortAllItems())); Q_ASSERT(m_minimumUpdateIntervalTimer->interval() <= m_maximumUpdateIntervalTimer->interval()); + + connect(KGlobalSettings::self(), SIGNAL(naturalSortingChanged()), this, SLOT(slotNaturalSortingChanged())); } KFileItemModel::~KFileItemModel() @@ -163,6 +168,38 @@ bool KFileItemModel::sortFoldersFirst() const return m_sortFoldersFirst; } +void KFileItemModel::setShowHiddenFiles(bool show) +{ + KDirLister* dirLister = m_dirLister.data(); + if (dirLister) { + dirLister->setShowingDotFiles(show); + dirLister->emitChanges(); + if (show) { + slotCompleted(); + } + } +} + +bool KFileItemModel::showHiddenFiles() const +{ + const KDirLister* dirLister = m_dirLister.data(); + return dirLister ? dirLister->showingDotFiles() : false; +} + +void KFileItemModel::setShowFoldersOnly(bool enabled) +{ + KDirLister* dirLister = m_dirLister.data(); + if (dirLister) { + dirLister->setDirOnlyMode(enabled); + } +} + +bool KFileItemModel::showFoldersOnly() const +{ + KDirLister* dirLister = m_dirLister.data(); + return dirLister ? dirLister->dirOnlyMode() : false; +} + QMimeData* KFileItemModel::createMimeData(const QSet& indexes) const { QMimeData* data = new QMimeData(); @@ -338,8 +375,8 @@ void KFileItemModel::setRoles(const QSet& roles) m_roles = roles; if (count() > 0) { - const bool supportedExpanding = m_requestRole[IsExpandedRole] && m_requestRole[ExpansionLevelRole]; - const bool willSupportExpanding = roles.contains("isExpanded") && roles.contains("expansionLevel"); + const bool supportedExpanding = m_requestRole[ExpansionLevelRole]; + const bool willSupportExpanding = roles.contains("expansionLevel"); if (supportedExpanding && !willSupportExpanding) { // No expanding is supported anymore. Take care to delete all items that have an expansion level // that is not 0 (and hence are part of an expanded item). @@ -347,6 +384,7 @@ void KFileItemModel::setRoles(const QSet& roles) } } + m_groups.clear(); resetRoles(); QSetIterator it(roles); @@ -374,7 +412,7 @@ QSet KFileItemModel::roles() const bool KFileItemModel::setExpanded(int index, bool expanded) { - if (isExpanded(index) == expanded || index < 0 || index >= count()) { + if (!isExpandable(index) || isExpanded(index) == expanded) { return false; } @@ -384,11 +422,11 @@ bool KFileItemModel::setExpanded(int index, bool expanded) return false; } + KDirLister* dirLister = m_dirLister.data(); const KUrl url = m_itemData.at(index)->item.url(); if (expanded) { m_expandedUrls.insert(url); - KDirLister* dirLister = m_dirLister.data(); if (dirLister) { dirLister->openUrl(url, KDirLister::Keep); return true; @@ -396,6 +434,10 @@ bool KFileItemModel::setExpanded(int index, bool expanded) } else { m_expandedUrls.remove(url); + if (dirLister) { + dirLister->stop(url); + } + KFileItemList itemsToRemove; const int expansionLevel = data(index)["expansionLevel"].toInt(); ++index; @@ -421,7 +463,7 @@ bool KFileItemModel::isExpanded(int index) const bool KFileItemModel::isExpandable(int index) const { if (index >= 0 && index < count()) { - return m_itemData.at(index)->item.isDir(); + return m_itemData.at(index)->values.value("isExpandable").toBool(); } return false; } @@ -438,13 +480,12 @@ void KFileItemModel::restoreExpandedUrls(const QSet& urls) void KFileItemModel::setExpanded(const QSet& urls) { - const KDirLister* dirLister = m_dirLister.data(); if (!dirLister) { return; } - const int pos = dirLister->url().url().length(); + const int pos = dirLister->url().path().length(); // Assure that each sub-path of the URLs that should be // expanded is added to m_urlsToExpand too. KDirLister @@ -455,7 +496,7 @@ void KFileItemModel::setExpanded(const QSet& urls) const KUrl& url = it1.next(); KUrl urlToExpand = dirLister->url(); - const QStringList subDirs = url.url().mid(pos).split(QDir::separator()); + const QStringList subDirs = url.path().mid(pos).split(QDir::separator()); for (int i = 0; i < subDirs.count(); ++i) { urlToExpand.addPath(subDirs.at(i)); m_urlsToExpand.insert(urlToExpand); @@ -475,6 +516,52 @@ void KFileItemModel::setExpanded(const QSet& urls) } } +void KFileItemModel::setNameFilter(const QString& nameFilter) +{ + if (m_filter.pattern() != nameFilter) { + dispatchPendingItemsToInsert(); + + m_filter.setPattern(nameFilter); + + // Check which shown items from m_itemData must get + // hidden and hence moved to m_filteredItems. + KFileItemList newFilteredItems; + + foreach (ItemData* itemData, m_itemData) { + if (!m_filter.matches(itemData->item)) { + // Only filter non-expanded items as child items may never + // exist without a parent item + if (!itemData->values.value("isExpanded").toBool()) { + newFilteredItems.append(itemData->item); + m_filteredItems.insert(itemData->item); + } + } + } + + removeItems(newFilteredItems); + + // Check which hidden items from m_filteredItems should + // get visible again and hence removed from m_filteredItems. + KFileItemList newVisibleItems; + + QMutableSetIterator it(m_filteredItems); + while (it.hasNext()) { + const KFileItem item = it.next(); + if (m_filter.matches(item)) { + newVisibleItems.append(item); + m_filteredItems.remove(item); + } + } + + insertItems(newVisibleItems); + } +} + +QString KFileItemModel::nameFilter() const +{ + return m_filter.pattern(); +} + void KFileItemModel::onGroupedSortingChanged(bool current) { Q_UNUSED(current); @@ -537,20 +624,19 @@ void KFileItemModel::resortAllItems() } // Determine the indexes that have been moved - bool emitItemsMoved = false; QList movedToIndexes; movedToIndexes.reserve(itemCount); for (int i = 0; i < itemCount; i++) { const int newIndex = m_items.value(oldUrls.at(i).url()); movedToIndexes.append(newIndex); - if (!emitItemsMoved && newIndex != i) { - emitItemsMoved = true; - } } - if (emitItemsMoved) { - emit itemsMoved(KItemRange(0, itemCount), movedToIndexes); - } + // Don't check whether items have really been moved and always emit a + // itemsMoved() signal after resorting: In case of grouped items + // the groups might change even if the items themselves don't change their + // position. Let the receiver of the signal decide whether a check for moved + // items makes sense. + emit itemsMoved(KItemRange(0, itemCount), movedToIndexes); #ifdef KFILEITEMMODEL_DEBUG kDebug() << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed(); @@ -604,7 +690,55 @@ void KFileItemModel::slotCanceled() void KFileItemModel::slotNewItems(const KFileItemList& items) { - m_pendingItemsToInsert.append(items); + Q_ASSERT(!items.isEmpty()); + + if (m_requestRole[ExpansionLevelRole] && m_rootExpansionLevel >= 0) { + // To be able to compare whether the new items may be inserted as children + // of a parent item the pending items must be added to the model first. + dispatchPendingItemsToInsert(); + + KFileItem item = items.first(); + + // If the expanding of items is enabled, the call + // dirLister->openUrl(url, KDirLister::Keep) in KFileItemModel::setExpanded() + // might result in emitting the same items twice due to the Keep-parameter. + // This case happens if an item gets expanded, collapsed and expanded again + // before the items could be loaded for the first expansion. + const int index = m_items.value(item.url(), -1); + if (index >= 0) { + // The items are already part of the model. + return; + } + + // KDirLister keeps the children of items that got expanded once even if + // they got collapsed again with KFileItemModel::setExpanded(false). So it must be + // checked whether the parent for new items is still expanded. + KUrl parentUrl = item.url().upUrl(); + parentUrl.adjustPath(KUrl::RemoveTrailingSlash); + const int parentIndex = m_items.value(parentUrl, -1); + if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) { + // The parent is not expanded. + return; + } + } + + if (m_filter.pattern().isEmpty()) { + m_pendingItemsToInsert.append(items); + } else { + // The name-filter is active. Hide filtered items + // before inserting them into the model and remember + // the filtered items in m_filteredItems. + KFileItemList filteredItems; + foreach (const KFileItem& item, items) { + if (m_filter.matches(item)) { + filteredItems.append(item); + } else { + m_filteredItems.insert(item); + } + } + + m_pendingItemsToInsert.append(filteredItems); + } if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) { // Assure that items get dispatched if no completed() or canceled() signal is @@ -615,11 +749,23 @@ void KFileItemModel::slotNewItems(const KFileItemList& items) void KFileItemModel::slotItemsDeleted(const KFileItemList& items) { - if (!m_pendingItemsToInsert.isEmpty()) { - insertItems(m_pendingItemsToInsert); - m_pendingItemsToInsert.clear(); + dispatchPendingItemsToInsert(); + + KFileItemList itemsToRemove = items; + if (m_requestRole[ExpansionLevelRole] && m_rootExpansionLevel >= 0) { + // Assure that removing a parent item also results in removing all children + foreach (const KFileItem& item, items) { + itemsToRemove.append(childItems(item)); + } } - removeItems(items); + + if (!m_filteredItems.isEmpty()) { + foreach (const KFileItem& item, itemsToRemove) { + m_filteredItems.remove(item); + } + } + + removeItems(itemsToRemove); } void KFileItemModel::slotRefreshItems(const QList >& items) @@ -638,8 +784,14 @@ void KFileItemModel::slotRefreshItems(const QList >& QListIterator > it(items); while (it.hasNext()) { const QPair& itemPair = it.next(); - const int index = m_items.value(itemPair.second.url(), -1); + const KFileItem& oldItem = itemPair.first; + const KFileItem& newItem = itemPair.second; + const int index = m_items.value(oldItem.url(), -1); if (index >= 0) { + m_itemData[index]->item = newItem; + m_itemData[index]->values = retrieveData(newItem); + m_items.remove(oldItem.url()); + m_items.insert(newItem.url(), index); indexes.append(index); } } @@ -654,9 +806,9 @@ void KFileItemModel::slotRefreshItems(const QList >& qSort(indexes); KItemRangeList itemRangeList; - int rangeIndex = 0; - int rangeCount = 1; int previousIndex = indexes.at(0); + int rangeIndex = previousIndex; + int rangeCount = 1; const int maxIndex = indexes.count() - 1; for (int i = 1; i <= maxIndex; ++i) { @@ -676,7 +828,9 @@ void KFileItemModel::slotRefreshItems(const QList >& itemRangeList.append(KItemRange(rangeIndex, rangeCount)); } - emit itemsChanged(itemRangeList, QSet()); + emit itemsChanged(itemRangeList, m_roles); + + resortAllItems(); } void KFileItemModel::slotClear() @@ -685,6 +839,7 @@ void KFileItemModel::slotClear() kDebug() << "Clearing all items"; #endif + m_filteredItems.clear(); m_groups.clear(); m_minimumUpdateIntervalTimer->stop(); @@ -692,7 +847,7 @@ void KFileItemModel::slotClear() m_resortAllItemsTimer->stop(); m_pendingItemsToInsert.clear(); - m_rootExpansionLevel = -1; + m_rootExpansionLevel = UninitializedRootExpansionLevel; const int removedCount = m_itemData.count(); if (removedCount > 0) { @@ -710,6 +865,12 @@ void KFileItemModel::slotClear(const KUrl& url) Q_UNUSED(url); } +void KFileItemModel::slotNaturalSortingChanged() +{ + m_naturalSorting = KGlobalSettings::naturalSorting(); + resortAllItems(); +} + void KFileItemModel::dispatchPendingItemsToInsert() { if (!m_pendingItemsToInsert.isEmpty()) { @@ -809,7 +970,14 @@ void KFileItemModel::removeItems(const KFileItemList& items) m_groups.clear(); - QList sortedItems = createItemDataList(items); + QList sortedItems; + sortedItems.reserve(items.count()); + foreach (const KFileItem& item, items) { + const int index = m_items.value(item.url(), -1); + if (index >= 0) { + sortedItems.append(m_itemData.at(index)); + } + } sort(sortedItems.begin(), sortedItems.end()); QList indexesToRemove; @@ -848,13 +1016,15 @@ void KFileItemModel::removeItems(const KFileItemList& items) ++removedCount; ++targetIndex; } - qDeleteAll(sortedItems); - sortedItems.clear(); // Delete the items for (int i = indexesToRemove.count() - 1; i >= 0; --i) { const int indexToRemove = indexesToRemove.at(i); - delete m_itemData.at(indexToRemove); + ItemData* data = m_itemData.at(indexToRemove); + + m_items.remove(data->item.url()); + + delete data; m_itemData.removeAt(indexToRemove); } @@ -866,7 +1036,7 @@ void KFileItemModel::removeItems(const KFileItemList& items) } if (count() <= 0) { - m_rootExpansionLevel = -1; + m_rootExpansionLevel = UninitializedRootExpansionLevel; } itemRanges << KItemRange(removedAtIndex, removedCount); @@ -882,6 +1052,21 @@ QList KFileItemModel::createItemDataList(const KFileI ItemData* itemData = new ItemData(); itemData->item = item; itemData->values = retrieveData(item); + itemData->parent = 0; + + const bool determineParent = m_requestRole[ExpansionLevelRole] + && itemData->values["expansionLevel"].toInt() > 0; + if (determineParent) { + KUrl parentUrl = item.url().upUrl(); + parentUrl.adjustPath(KUrl::RemoveTrailingSlash); + const int parentIndex = m_items.value(parentUrl, -1); + if (parentIndex >= 0) { + itemData->parent = m_itemData.at(parentIndex); + } else { + kWarning() << "Parent item not found for" << item.url(); + } + } + itemDataList.append(itemData); } @@ -905,7 +1090,7 @@ void KFileItemModel::removeExpandedItems() Q_ASSERT(m_rootExpansionLevel >= 0); removeItems(expandedItems); - m_rootExpansionLevel = -1; + m_rootExpansionLevel = UninitializedRootExpansionLevel; m_expandedUrls.clear(); } @@ -934,11 +1119,36 @@ KFileItemModel::Role KFileItemModel::roleIndex(const QByteArray& role) const rolesHash.insert("rating", RatingRole); rolesHash.insert("isDir", IsDirRole); rolesHash.insert("isExpanded", IsExpandedRole); + rolesHash.insert("isExpandable", IsExpandableRole); rolesHash.insert("expansionLevel", ExpansionLevelRole); } return rolesHash.value(role, NoRole); } +QByteArray KFileItemModel::roleByteArray(Role role) const +{ + static const char* const roles[RolesCount] = { + 0, // NoRole + "name", + "size", + "date", + "permissions", + "owner", + "group", + "type", + "destination", + "path", + "comment", + "tags", + "rating", + "isDir", + "isExpanded", + "isExpandable", + "expansionLevel" + }; + return roles[role]; +} + QHash KFileItemModel::retrieveData(const KFileItem& item) const { // It is important to insert only roles that are fast to retrieve. E.g. @@ -946,6 +1156,7 @@ QHash KFileItemModel::retrieveData(const KFileItem& item) // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater. QHash data; data.insert("iconPixmap", QPixmap()); + data.insert("url", item.url()); const bool isDir = item.isDir(); if (m_requestRole[IsDirRole]) { @@ -993,25 +1204,49 @@ QHash KFileItemModel::retrieveData(const KFileItem& item) } if (m_requestRole[PathRole]) { - data.insert("path", item.localPath()); + QString path; + if (item.url().protocol() == QLatin1String("trash")) { + path = item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA); + } else { + path = item.localPath(); + } + + const int index = path.lastIndexOf(item.text()); + path = path.mid(0, index - 1); + data.insert("path", path); } if (m_requestRole[IsExpandedRole]) { data.insert("isExpanded", false); } + if (m_requestRole[IsExpandableRole]) { + data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl()); + } + if (m_requestRole[ExpansionLevelRole]) { - if (m_rootExpansionLevel < 0 && m_dirLister.data()) { - const QString rootDir = m_dirLister.data()->url().directory(KUrl::AppendTrailingSlash); - m_rootExpansionLevel = rootDir.count('/'); - if (m_rootExpansionLevel == 1) { - // Special case: The root is already reached and no parent is available - --m_rootExpansionLevel; + if (m_rootExpansionLevel == UninitializedRootExpansionLevel && m_dirLister.data()) { + const KUrl rootUrl = m_dirLister.data()->url(); + const QString protocol = rootUrl.protocol(); + const bool forceRootExpansionLevel = (protocol == QLatin1String("trash") || + protocol == QLatin1String("nepomuk") || + protocol == QLatin1String("remote") || + protocol.contains(QLatin1String("search"))); + if (forceRootExpansionLevel) { + m_rootExpansionLevel = ForceRootExpansionLevel; + } else { + const QString rootDir = rootUrl.path(KUrl::AddTrailingSlash); + m_rootExpansionLevel = rootDir.count('/'); } } - const QString dir = item.url().directory(KUrl::AppendTrailingSlash); - const int level = dir.count('/') - m_rootExpansionLevel - 1; - data.insert("expansionLevel", level); + + if (m_rootExpansionLevel == ForceRootExpansionLevel) { + data.insert("expansionLevel", -1); + } else { + const QString dir = item.url().directory(KUrl::AppendTrailingSlash); + const int level = dir.count('/') - m_rootExpansionLevel; + data.insert("expansionLevel", level); + } } if (item.isMimeTypeKnown()) { @@ -1027,13 +1262,10 @@ QHash KFileItemModel::retrieveData(const KFileItem& item) bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const { - const KFileItem& itemA = a->item; - const KFileItem& itemB = b->item; - int result = 0; if (m_rootExpansionLevel >= 0) { - result = expansionLevelsCompare(itemA, itemB); + result = expansionLevelsCompare(a, b); if (result != 0) { // The items have parents with different expansion levels return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; @@ -1041,8 +1273,8 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const } if (m_sortFoldersFirst || m_sortRole == SizeRole) { - const bool isDirA = itemA.isDir(); - const bool isDirB = itemB.isDir(); + const bool isDirA = a->item.isDir(); + const bool isDirB = b->item.isDir(); if (isDirA && !isDirB) { return true; } else if (!isDirA && isDirB) { @@ -1050,28 +1282,23 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const } } - switch (m_sortRole) { - case NameRole: { - result = stringCompare(itemA.text(), itemB.text()); - if (result == 0) { - // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used - result = stringCompare(itemA.name(m_caseSensitivity == Qt::CaseInsensitive), - itemB.name(m_caseSensitivity == Qt::CaseInsensitive)); - } - break; - } + result = sortRoleCompare(a, b); - case DateRole: { - const KDateTime dateTimeA = itemA.time(KFileItem::ModificationTime); - const KDateTime dateTimeB = itemB.time(KFileItem::ModificationTime); - if (dateTimeA < dateTimeB) { - result = -1; - } else if (dateTimeA > dateTimeB) { - result = +1; - } - break; - } + return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; +} + +int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const +{ + const KFileItem& itemA = a->item; + const KFileItem& itemB = b->item; + int result = 0; + + switch (m_sortRole) { + case NameRole: + // The name role is handled as default fallback after the switch + break; + case SizeRole: { if (itemA.isDir()) { Q_ASSERT(itemB.isDir()); // see "if (m_sortFoldersFirst || m_sortRole == SizeRole)" above @@ -1093,41 +1320,62 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const break; } - case TypeRole: { - result = QString::compare(a->values.value("type").toString(), - b->values.value("type").toString()); + case DateRole: { + const KDateTime dateTimeA = itemA.time(KFileItem::ModificationTime); + const KDateTime dateTimeB = itemB.time(KFileItem::ModificationTime); + if (dateTimeA < dateTimeB) { + result = -1; + } else if (dateTimeA > dateTimeB) { + result = +1; + } break; } - - case CommentRole: { - result = QString::compare(a->values.value("comment").toString(), - b->values.value("comment").toString()); - break; - } - - case TagsRole: { - result = QString::compare(a->values.value("tags").toString(), - b->values.value("tags").toString()); - break; - } case RatingRole: { result = a->values.value("rating").toInt() - b->values.value("rating").toInt(); break; } - + + case PermissionsRole: + case OwnerRole: + case GroupRole: + case TypeRole: + case DestinationRole: + case PathRole: + case CommentRole: + case TagsRole: { + const QByteArray role = roleByteArray(m_sortRole); + result = QString::compare(a->values.value(role).toString(), + b->values.value(role).toString()); + break; + } + default: break; } - if (result == 0) { - // It must be assured that the sort order is always unique even if two values have been - // equal. In this case a comparison of the URL is done which is unique in all cases - // within KDirLister. - result = QString::compare(itemA.url().url(), itemB.url().url(), Qt::CaseSensitive); + if (result != 0) { + // The current sort role was sufficient to define an order + return result; } - return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; + // Fallback #1: Compare the text of the items + result = stringCompare(itemA.text(), itemB.text()); + if (result != 0) { + return result; + } + + // Fallback #2: KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used + result = stringCompare(itemA.name(m_caseSensitivity == Qt::CaseInsensitive), + itemB.name(m_caseSensitivity == Qt::CaseInsensitive)); + if (result != 0) { + return result; + } + + // Fallback #3: It must be assured that the sort order is always unique even if two values have been + // equal. In this case a comparison of the URL is done which is unique in all cases + // within KDirLister. + return QString::compare(itemA.url().url(), itemB.url().url(), Qt::CaseSensitive); } void KFileItemModel::sort(QList::iterator begin, @@ -1274,10 +1522,10 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b) const : QString::compare(a, b, Qt::CaseSensitive); } -int KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b) const +int KFileItemModel::expansionLevelsCompare(const ItemData* a, const ItemData* b) const { - const KUrl urlA = a.url(); - const KUrl urlB = b.url(); + const KUrl urlA = a->item.url(); + const KUrl urlB = b->item.url(); if (urlA.directory() == urlB.directory()) { // Both items have the same directory as parent return 0; @@ -1285,9 +1533,9 @@ int KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& // Check whether one item is the parent of the other item if (urlA.isParentOf(urlB)) { - return -1; + return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; } else if (urlB.isParentOf(urlA)) { - return +1; + return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; } // Determine the maximum common path of both items and @@ -1310,17 +1558,37 @@ int KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& // Determine the first sub-path after the common path and // check whether it represents a directory or already a file bool isDirA = true; - const QString subPathA = subPath(a, pathA, index, &isDirA); + const QString subPathA = subPath(a->item, pathA, index, &isDirA); bool isDirB = true; - const QString subPathB = subPath(b, pathB, index, &isDirB); + const QString subPathB = subPath(b->item, pathB, index, &isDirB); if (isDirA && !isDirB) { - return -1; + return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; } else if (!isDirA && isDirB) { - return +1; + return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; + } + + // Compare the items of the parents that represent the first + // different path after the common path. + const QString parentPathA = pathA.left(index) + subPathA; + const QString parentPathB = pathB.left(index) + subPathB; + + const ItemData* parentA = a; + while (parentA && parentA->item.url().path() != parentPathA) { + parentA = parentA->parent; } - return stringCompare(subPathA, subPathB); + const ItemData* parentB = b; + while (parentB && parentB->item.url().path() != parentPathB) { + parentB = parentB->parent; + } + + if (parentA && parentB) { + return sortRoleCompare(parentA, parentB); + } + + kWarning() << "Child items without parent detected:" << a->item.url() << b->item.url(); + return QString::compare(urlA.url(), urlB.url(), Qt::CaseSensitive); } QString KFileItemModel::subPath(const KFileItem& item, @@ -1360,7 +1628,7 @@ QList > KFileItemModel::nameRoleGroups() const // Use the first character of the name as group indication QChar newFirstChar = name.at(0).toUpper(); if (newFirstChar == QLatin1Char('~') && name.length() > 1) { - newFirstChar = name.at(1); + newFirstChar = name.at(1).toUpper(); } if (firstChar != newFirstChar) { @@ -1616,12 +1884,12 @@ QList > KFileItemModel::ratingRoleGroups() const const int maxIndex = count() - 1; QList > groups; - int groupValue; + int groupValue = -1; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - const int newGroupValue = m_itemData.at(i)->values.value("rating").toInt(); + const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt(); if (newGroupValue != groupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); @@ -1638,19 +1906,38 @@ QList > KFileItemModel::genericStringRoleGroups(const QByte const int maxIndex = count() - 1; QList > groups; + bool isFirstGroupValue = true; QString groupValue; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const QString newGroupValue = m_itemData.at(i)->values.value(role).toString(); - if (newGroupValue != groupValue) { + if (newGroupValue != groupValue || isFirstGroupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); + isFirstGroupValue = false; } } return groups; } +KFileItemList KFileItemModel::childItems(const KFileItem& item) const +{ + KFileItemList items; + + int index = m_items.value(item.url(), -1); + if (index >= 0) { + const int parentLevel = m_itemData.at(index)->values.value("expansionLevel").toInt(); + ++index; + while (index < m_itemData.count() && m_itemData.at(index)->values.value("expansionLevel").toInt() > parentLevel) { + items.append(m_itemData.at(index)->item); + ++index; + } + } + + return items; +} + #include "kfileitemmodel.moc"