X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/40c71d5ce0f6978ce48eb67fd73ed211bab4e887..6b91e4e6b80f23f2123eb7b85b4a2bf1e348fe84:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 8b6522c07..d16a6c1d3 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1,5 +1,6 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * + * Copyright (C) 2013 by Frank Reininghaus * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -28,13 +29,15 @@ #include "private/kfileitemmodelsortalgorithm.h" #include "private/kfileitemmodeldirlister.h" +#include #include #include +#include // #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(QObject* parent) : - KItemModelBase("name", parent), + KItemModelBase("text", parent), m_dirLister(0), m_naturalSorting(KGlobalSettings::naturalSorting()), m_sortDirsFirst(true), @@ -56,9 +59,13 @@ KFileItemModel::KFileItemModel(QObject* parent) : m_urlsToExpand() { m_dirLister = new KFileItemModelDirLister(this); - m_dirLister->setAutoUpdate(true); m_dirLister->setDelayedMimeTypes(true); + const QWidget* parentWidget = qobject_cast(parent); + if (parentWidget) { + m_dirLister->setMainWindow(parentWidget->window()); + } + connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted())); connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled())); connect(m_dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted())); @@ -70,13 +77,16 @@ KFileItemModel::KFileItemModel(QObject* parent) : connect(m_dirLister, SIGNAL(infoMessage(QString)), this, SIGNAL(infoMessage(QString))); connect(m_dirLister, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString))); connect(m_dirLister, SIGNAL(redirection(KUrl,KUrl)), this, SIGNAL(directoryRedirection(KUrl,KUrl))); + connect(m_dirLister, SIGNAL(urlIsFileError(KUrl)), this, SIGNAL(urlIsFileError(KUrl))); // Apply default roles that should be determined resetRoles(); m_requestRole[NameRole] = true; m_requestRole[IsDirRole] = true; - m_roles.insert("name"); + m_requestRole[IsLinkRole] = true; + m_roles.insert("text"); m_roles.insert("isDir"); + m_roles.insert("isLink"); // For slow KIO-slaves like used for searching it makes sense to show results periodically even // before the completed() or canceled() signal has been emitted. @@ -163,6 +173,12 @@ bool KFileItemModel::setData(int index, const QHash& value } m_itemData[index]->values = currentValues; + if (changedRoles.contains("text")) { + KUrl url = m_itemData[index]->item.url(); + url.setFileName(currentValues["text"].toString()); + m_itemData[index]->item.setUrl(url); + } + emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles); if (changedRoles.contains(sortRole())) { @@ -251,12 +267,12 @@ int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromInd { startFromIndex = qMax(0, startFromIndex); for (int i = startFromIndex; i < count(); ++i) { - if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { return i; } } for (int i = 0; i < startFromIndex; ++i) { - if (data(i)["name"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { return i; } } @@ -276,7 +292,7 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const int count = 0; const RoleInfoMap* map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { - description.insert(map[i].role, map[i].roleTranslation); + description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation)); } } @@ -497,46 +513,64 @@ void KFileItemModel::setNameFilter(const QString& nameFilter) { if (m_filter.pattern() != nameFilter) { dispatchPendingItemsToInsert(); - m_filter.setPattern(nameFilter); + applyFilters(); + } +} - // Check which shown items from m_itemData must get - // hidden and hence moved to m_filteredItems. - KFileItemList newFilteredItems; +QString KFileItemModel::nameFilter() const +{ + return m_filter.pattern(); +} - foreach (ItemData* itemData, m_itemData) { +void KFileItemModel::setMimeTypeFilters(const QStringList& filters) +{ + if (m_filter.mimeTypes() != filters) { + dispatchPendingItemsToInsert(); + m_filter.setMimeTypes(filters); + applyFilters(); + } +} + +QStringList KFileItemModel::mimeTypeFilters() const +{ + return m_filter.mimeTypes(); +} + + +void KFileItemModel::applyFilters() +{ + // Check which shown items from m_itemData must get + // hidden and hence moved to m_filteredItems. + KFileItemList newFilteredItems; + + foreach (ItemData* itemData, m_itemData) { + // Only filter non-expanded items as child items may never + // exist without a parent item + if (!itemData->values.value("isExpanded").toBool()) { 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); - } + newFilteredItems.append(itemData->item); + m_filteredItems.insert(itemData->item); } } + } - removeItems(newFilteredItems); + removeItems(newFilteredItems); - // Check which hidden items from m_filteredItems should - // get visible again and hence removed from m_filteredItems. - KFileItemList newVisibleItems; + // 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); - } + QMutableSetIterator it(m_filteredItems); + while (it.hasNext()) { + const KFileItem item = it.next(); + if (m_filter.matches(item)) { + newVisibleItems.append(item); + it.remove(); } - - insertItems(newVisibleItems); } -} -QString KFileItemModel::nameFilter() const -{ - return m_filter.pattern(); + insertItems(newVisibleItems); } QList KFileItemModel::rolesInformation() @@ -549,8 +583,15 @@ QList KFileItemModel::rolesInformation() if (map[i].roleType != NoRole) { RoleInfo info; info.role = map[i].role; - info.translation = map[i].roleTranslation; - info.group = map[i].groupTranslation; + info.translation = i18nc(map[i].roleTranslationContext, map[i].roleTranslation); + if (map[i].groupTranslation) { + info.group = i18nc(map[i].groupTranslationContext, map[i].groupTranslation); + } else { + // For top level roles, groupTranslation is 0. We must make sure that + // info.group is an empty string then because the code that generates + // menus tries to put the actions into sub menus otherwise. + info.group = QString(); + } info.requiresNepomuk = map[i].requiresNepomuk; info.requiresIndexer = map[i].requiresIndexer; rolesInfo.append(info); @@ -617,7 +658,7 @@ void KFileItemModel::resortAllItems() m_items.clear(); // Resort the items - KFileItemModelSortAlgorithm::sort(this, m_itemData.begin(), m_itemData.end()); + sort(m_itemData.begin(), m_itemData.end()); for (int i = 0; i < itemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -651,7 +692,7 @@ void KFileItemModel::slotCompleted() // Note that the parent folder must be expanded before any of its subfolders become visible. // Therefore, some URLs in m_restoredExpandedUrls might not be visible yet // -> we expand the first visible URL we find in m_restoredExpandedUrls. - foreach(const KUrl& url, m_urlsToExpand) { + foreach (const KUrl& url, m_urlsToExpand) { const int index = m_items.value(url, -1); if (index >= 0) { m_urlsToExpand.remove(url); @@ -675,6 +716,8 @@ void KFileItemModel::slotCanceled() { m_maximumUpdateIntervalTimer->stop(); dispatchPendingItemsToInsert(); + + emit directoryLoadingCanceled(); } void KFileItemModel::slotNewItems(const KFileItemList& items) @@ -711,10 +754,10 @@ void KFileItemModel::slotNewItems(const KFileItemList& items) } } - if (m_filter.pattern().isEmpty()) { + if (!m_filter.hasSetFilters()) { m_pendingItemsToInsert.append(items); } else { - // The name-filter is active. Hide filtered items + // The name or type filter is active. Hide filtered items // before inserting them into the model and remember // the filtered items in m_filteredItems. KFileItemList filteredItems; @@ -752,6 +795,33 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items) foreach (const KFileItem& item, itemsToRemove) { m_filteredItems.remove(item); } + + if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) { + // Remove all filtered children of deleted items. First, we put the + // deleted URLs into a set to provide fast lookup while iterating + // over m_filteredItems and prevent quadratic complexity if there + // are N removed items and N filtered items. + QSet urlsToRemove; + urlsToRemove.reserve(itemsToRemove.count()); + foreach (const KFileItem& item, itemsToRemove) { + KUrl url = item.url(); + url.adjustPath(KUrl::RemoveTrailingSlash); + urlsToRemove.insert(url); + } + + QSet::iterator it = m_filteredItems.begin(); + while (it != m_filteredItems.end()) { + const KUrl url = it->url(); + KUrl parentUrl = url.upUrl(); + parentUrl.adjustPath(KUrl::RemoveTrailingSlash); + + if (urlsToRemove.contains(parentUrl)) { + it = m_filteredItems.erase(it); + } else { + ++it; + } + } + } } removeItems(itemsToRemove); @@ -898,7 +968,7 @@ void KFileItemModel::insertItems(const KFileItemList& items) m_groups.clear(); QList sortedItems = createItemDataList(items); - KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end()); + sort(sortedItems.begin(), sortedItems.end()); #ifdef KFILEITEMMODEL_DEBUG kDebug() << "[TIME] Sorting:" << timer.elapsed(); @@ -928,7 +998,7 @@ void KFileItemModel::insertItems(const KFileItemList& items) insertedCount = 0; } - // Insert item at the position targetIndex by transfering + // Insert item at the position targetIndex by transferring // the ownership of the item-data from sortedItems to m_itemData. // m_items will be inserted after the loop (see comment below) m_itemData.insert(targetIndex, sortedItems.at(sourceIndex)); @@ -945,6 +1015,7 @@ void KFileItemModel::insertItems(const KFileItemList& items) // The indexes of all m_items must be adjusted, not only the index // of the new items const int itemDataCount = m_itemData.count(); + m_items.reserve(itemDataCount); for (int i = 0; i < itemDataCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -957,80 +1028,96 @@ void KFileItemModel::insertItems(const KFileItemList& items) #endif } -void KFileItemModel::removeItems(const KFileItemList& items) +static KItemRangeList sortedIndexesToKItemRangeList(const QList& sortedNumbers) { - if (items.isEmpty()) { - return; + if (sortedNumbers.empty()) { + return KItemRangeList(); } + KItemRangeList result; + + QList::const_iterator it = sortedNumbers.begin(); + int index = *it; + int count = 1; + + ++it; + + QList::const_iterator end = sortedNumbers.end(); + while (it != end) { + if (*it == index + count) { + ++count; + } else { + result << KItemRange(index, count); + index = *it; + count = 1; + } + ++it; + } + + result << KItemRange(index, count); + return result; +} + +void KFileItemModel::removeItems(const KFileItemList& items) +{ #ifdef KFILEITEMMODEL_DEBUG kDebug() << "Removing " << items.count() << "items"; #endif m_groups.clear(); - QList sortedItems; - sortedItems.reserve(items.count()); + // Step 1: Determine the indexes of the removed items, remove them from + // the hash m_items, and free the ItemData. + QList indexesToRemove; + indexesToRemove.reserve(items.count()); foreach (const KFileItem& item, items) { - const int index = m_items.value(item.url(), -1); + const KUrl url = item.url(); + const int index = m_items.value(url, -1); if (index >= 0) { - sortedItems.append(m_itemData.at(index)); + indexesToRemove.append(index); + + // Prevent repeated expensive rehashing by using QHash::erase(), + // rather than QHash::remove(). + QHash::iterator it = m_items.find(url); + m_items.erase(it); + + ItemData* data = m_itemData.at(index); + delete data; + m_itemData[index] = 0; } } - KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end()); - QList indexesToRemove; - indexesToRemove.reserve(items.count()); + if (indexesToRemove.isEmpty()) { + return; + } - // Calculate the item ranges that will get deleted - KItemRangeList itemRanges; - int removedAtIndex = -1; - int removedCount = 0; - int targetIndex = 0; - foreach (const ItemData* itemData, sortedItems) { - const KFileItem& itemToRemove = itemData->item; + std::sort(indexesToRemove.begin(), indexesToRemove.end()); - const int previousTargetIndex = targetIndex; - while (targetIndex < m_itemData.count()) { - if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) { - break; - } - ++targetIndex; - } - if (targetIndex >= m_itemData.count()) { - kWarning() << "Item that should be deleted has not been found!"; - return; - } + // Step 2: Remove the ItemData pointers from the list m_itemData. + const KItemRangeList itemRanges = sortedIndexesToKItemRangeList(indexesToRemove); + int target = itemRanges.at(0).index; + int source = itemRanges.at(0).index + itemRanges.at(0).count; + int nextRange = 1; - if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) { - itemRanges << KItemRange(removedAtIndex, removedCount); - removedAtIndex = targetIndex; - removedCount = 0; - } + const int oldItemDataCount = m_itemData.count(); + while (source < oldItemDataCount) { + m_itemData[target] = m_itemData[source]; + ++target; + ++source; - indexesToRemove.append(targetIndex); - if (removedAtIndex < 0) { - removedAtIndex = targetIndex; + if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) { + // Skip the items in the next removed range. + source += itemRanges.at(nextRange).count; + ++nextRange; } - ++removedCount; - ++targetIndex; } - // Delete the items - for (int i = indexesToRemove.count() - 1; i >= 0; --i) { - const int indexToRemove = indexesToRemove.at(i); - ItemData* data = m_itemData.at(indexToRemove); - - m_items.remove(data->item.url()); + m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end()); - delete data; - m_itemData.removeAt(indexToRemove); - } - - // The indexes of all m_items must be adjusted, not only the index - // of the removed items - const int itemDataCount = m_itemData.count(); - for (int i = 0; i < itemDataCount; ++i) { + // Step 3: Adjust indexes in the hash m_items. Note that all indexes + // might have been changed by the removal of the items. + const int newItemDataCount = m_itemData.count(); + for (int i = 0; i < newItemDataCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -1038,7 +1125,6 @@ void KFileItemModel::removeItems(const KFileItemList& items) m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot; } - itemRanges << KItemRange(removedAtIndex, removedCount); emit itemsRemoved(itemRanges); } @@ -1114,6 +1200,7 @@ KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArray& role) con // Insert internal roles (take care to synchronize the implementation // with KFileItemModel::roleForType() in case if a change is done). roles.insert("isDir", IsDirRole); + roles.insert("isLink", IsLinkRole); roles.insert("isExpanded", IsExpandedRole); roles.insert("isExpandable", IsExpandableRole); roles.insert("expandedParentsCount", ExpandedParentsCountRole); @@ -1139,6 +1226,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const // Insert internal roles (take care to synchronize the implementation // with KFileItemModel::typeForRole() in case if a change is done). roles.insert(IsDirRole, "isDir"); + roles.insert(IsLinkRole, "isLink"); roles.insert(IsExpandedRole, "isExpanded"); roles.insert(IsExpandableRole, "isExpandable"); roles.insert(ExpandedParentsCountRole, "expandedParentsCount"); @@ -1162,8 +1250,13 @@ QHash KFileItemModel::retrieveData(const KFileItem& item) data.insert("isDir", isDir); } + if (m_requestRole[IsLinkRole]) { + const bool isLink = item.isLink(); + data.insert("isLink", isLink); + } + if (m_requestRole[NameRole]) { - data.insert("name", item.text()); + data.insert("text", item.text()); } if (m_requestRole[SizeRole]) { @@ -1207,7 +1300,17 @@ QHash KFileItemModel::retrieveData(const KFileItem& item) if (item.url().protocol() == QLatin1String("trash")) { path = item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA); } else { + // For performance reasons cache the home-path in a static QString + // (see QDir::homePath() for more details) + static QString homePath; + if (homePath.isEmpty()) { + homePath = QDir::homePath(); + } + path = item.localPath(); + if (path.startsWith(homePath)) { + path.replace(0, homePath.length(), QLatin1Char('~')); + } } const int index = path.lastIndexOf(item.text()); @@ -1263,11 +1366,34 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const { int result = 0; - if (m_expandedParentsCountRoot >= 0) { - result = expandedParentsCountCompare(a, b); - if (result != 0) { - // The items have parents with different expansion levels - return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; + if (m_expandedParentsCountRoot >= 0 && a->parent != b->parent) { + const int expansionLevelA = a->values.value("expandedParentsCount").toInt(); + const int expansionLevelB = b->values.value("expandedParentsCount").toInt(); + + // If b has a higher expansion level than a, check if a is a parent + // of b, and make sure that both expansion levels are equal otherwise. + for (int i = expansionLevelB; i > expansionLevelA; --i) { + if (b->parent == a) { + return true; + } + b = b->parent; + } + + // If a has a higher expansion level than a, check if b is a parent + // of a, and make sure that both expansion levels are equal otherwise. + for (int i = expansionLevelA; i > expansionLevelB; --i) { + if (a->parent == b) { + return false; + } + a = a->parent; + } + + Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt()); + + // Compare the last parents of a and b which are different. + while (a->parent != b->parent) { + a = a->parent; + b = b->parent; } } @@ -1286,6 +1412,44 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; } +/** + * Helper class for KFileItemModel::sort(). + */ +class KFileItemModelLessThan +{ +public: + KFileItemModelLessThan(const KFileItemModel* model) : + m_model(model) + { + } + + bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const + { + return m_model->lessThan(a, b); + } + +private: + const KFileItemModel* m_model; +}; + +void KFileItemModel::sort(QList::iterator begin, + QList::iterator end) const +{ + KFileItemModelLessThan lessThan(this); + + if (m_sortRole == NameRole) { + // Sorting by name can be expensive, in particular if natural sorting is + // enabled. Use all CPU cores to speed up the sorting process. + static const int numberOfThreads = QThread::idealThreadCount(); + parallelMergeSort(begin, end, lessThan, numberOfThreads); + } else { + // Sorting by other roles is quite fast. Use only one thread to prevent + // problems caused by non-reentrant comparison functions, see + // https://bugs.kde.org/show_bug.cgi?id=312679 + mergeSort(begin, end, lessThan); + } +} + int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const { const KFileItem& itemA = a->item; @@ -1355,22 +1519,13 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const break; } - case PermissionsRole: - case OwnerRole: - case GroupRole: - case TypeRole: - case DestinationRole: - case PathRole: - case CommentRole: - case TagsRole: { + default: { const QByteArray role = roleForType(m_sortRole); result = QString::compare(a->values.value(role).toString(), b->values.value(role).toString()); break; } - default: - break; } if (result != 0) { @@ -1419,88 +1574,6 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b) const : QString::compare(a, b, Qt::CaseSensitive); } -int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemData* b) const -{ - 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; - } - - // Check whether one item is the parent of the other item - if (urlA.isParentOf(urlB)) { - return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; - } else if (urlB.isParentOf(urlA)) { - return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; - } - - // Determine the maximum common path of both items and - // remember the index in 'index' - const QString pathA = urlA.path(); - const QString pathB = urlB.path(); - - const int maxIndex = qMin(pathA.length(), pathB.length()) - 1; - int index = 0; - while (index <= maxIndex && pathA.at(index) == pathB.at(index)) { - ++index; - } - if (index > maxIndex) { - index = maxIndex; - } - while ((pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/')) && index > 0) { - --index; - } - - // 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->item, pathA, index, &isDirA); - bool isDirB = true; - const QString subPathB = subPath(b->item, pathB, index, &isDirB); - - if (m_sortDirsFirst || m_sortRole == SizeRole) { - if (isDirA && !isDirB) { - return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; - } else if (!isDirA && isDirB) { - 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; - } - - 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, - const QString& itemPath, - int start, - bool* isDir) const -{ - Q_ASSERT(isDir); - const int pathIndex = itemPath.indexOf('/', start + 1); - *isDir = (pathIndex > 0) || item.isDir(); - return itemPath.mid(start, pathIndex - start); -} - bool KFileItemModel::useMaximumUpdateInterval() const { return !m_dirLister->url().isLocalFile(); @@ -1521,7 +1594,7 @@ QList > KFileItemModel::nameRoleGroups() const continue; } - const QString name = m_itemData.at(i)->values.value("name").toString(); + const QString name = m_itemData.at(i)->values.value("text").toString(); // Use the first character of the name as group indication QChar newFirstChar = name.at(0).toUpper(); @@ -1871,7 +1944,7 @@ const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count) static const RoleInfoMap rolesInfoMap[] = { // | role | roleType | role translation | group translation | requires Nepomuk | requires indexer { 0, NoRole, 0, 0, 0, 0, false, false }, - { "name", NameRole, I18N_NOOP2_NOSTRIP("@label", "Name"), 0, 0, false, false }, + { "text", NameRole, I18N_NOOP2_NOSTRIP("@label", "Name"), 0, 0, false, false }, { "size", SizeRole, I18N_NOOP2_NOSTRIP("@label", "Size"), 0, 0, false, false }, { "date", DateRole, I18N_NOOP2_NOSTRIP("@label", "Date"), 0, 0, false, false }, { "type", TypeRole, I18N_NOOP2_NOSTRIP("@label", "Type"), 0, 0, false, false }, @@ -1882,10 +1955,10 @@ const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count) { "lineCount", LineCountRole, I18N_NOOP2_NOSTRIP("@label", "Line Count"), I18N_NOOP2_NOSTRIP("@label", "Document"), true, true }, { "imageSize", ImageSizeRole, I18N_NOOP2_NOSTRIP("@label", "Image Size"), I18N_NOOP2_NOSTRIP("@label", "Image"), true, true }, { "orientation", OrientationRole, I18N_NOOP2_NOSTRIP("@label", "Orientation"), I18N_NOOP2_NOSTRIP("@label", "Image"), true, true }, - { "artist", ArtistRole, I18N_NOOP2_NOSTRIP("@label", "Artist"), I18N_NOOP2_NOSTRIP("@label", "Music"), true, true }, - { "album", AlbumRole, I18N_NOOP2_NOSTRIP("@label", "Album"), I18N_NOOP2_NOSTRIP("@label", "Music"), true, true }, - { "duration", DurationRole, I18N_NOOP2_NOSTRIP("@label", "Duration"), I18N_NOOP2_NOSTRIP("@label", "Music"), true, true }, - { "track", TrackRole, I18N_NOOP2_NOSTRIP("@label", "Track"), I18N_NOOP2_NOSTRIP("@label", "Music"), true, true }, + { "artist", ArtistRole, I18N_NOOP2_NOSTRIP("@label", "Artist"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, + { "album", AlbumRole, I18N_NOOP2_NOSTRIP("@label", "Album"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, + { "duration", DurationRole, I18N_NOOP2_NOSTRIP("@label", "Duration"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, + { "track", TrackRole, I18N_NOOP2_NOSTRIP("@label", "Track"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "path", PathRole, I18N_NOOP2_NOSTRIP("@label", "Path"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "destination", DestinationRole, I18N_NOOP2_NOSTRIP("@label", "Link Destination"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "copiedFrom", CopiedFromRole, I18N_NOOP2_NOSTRIP("@label", "Copied From"), I18N_NOOP2_NOSTRIP("@label", "Other"), true, false }, @@ -1902,7 +1975,7 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout) { QElapsedTimer timer; timer.start(); - foreach (KFileItem item, items) { + foreach (KFileItem item, items) { // krazy:exclude=foreach item.determineMimeType(); if (timer.elapsed() > timeout) { // Don't block the user interface, let the remaining items @@ -1912,4 +1985,51 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout) } } +bool KFileItemModel::isConsistent() const +{ + if (m_items.count() != m_itemData.count()) { + return false; + } + + for (int i = 0; i < count(); ++i) { + // Check if m_items and m_itemData are consistent. + const KFileItem item = fileItem(i); + if (item.isNull()) { + qWarning() << "Item" << i << "is null"; + return false; + } + + const int itemIndex = index(item); + if (itemIndex != i) { + qWarning() << "Item" << i << "has a wrong index:" << itemIndex; + return false; + } + + // Check if the items are sorted correctly. + if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i))) { + qWarning() << "The order of items" << i - 1 << "and" << i << "is wrong:" + << fileItem(i - 1) << fileItem(i); + return false; + } + + // Check if all parent-child relationships are consistent. + const ItemData* data = m_itemData.at(i); + const ItemData* parent = data->parent; + if (parent) { + if (data->values.value("expandedParentsCount").toInt() != parent->values.value("expandedParentsCount").toInt() + 1) { + qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item; + return false; + } + + const int parentIndex = index(parent->item); + if (parentIndex >= i) { + qWarning() << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item; + return false; + } + } + } + + return true; +} + #include "kfileitemmodel.moc"