X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/90dd8977520bfce23ff6669809db1e9ecb5ec060..fda88516e9c32ed93cb4fe47a3a9a9bf2aecc456:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 5d1a1968a..4b7e5eb19 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -35,6 +35,9 @@ #include #include +#include +#include + // #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(QObject* parent) : @@ -120,6 +123,11 @@ void KFileItemModel::loadDirectory(const KUrl& url) void KFileItemModel::refreshDirectory(const KUrl& url) { + // Refresh all expanded directories first (Bug 295300) + foreach (const KUrl& expandedUrl, m_expandedDirs) { + m_dirLister->openUrl(expandedUrl, KDirLister::Reload); + } + m_dirLister->openUrl(url, KDirLister::Reload); } @@ -159,7 +167,7 @@ bool KFileItemModel::setData(int index, const QHash& value QHashIterator it(values); while (it.hasNext()) { it.next(); - const QByteArray role = it.key(); + const QByteArray role = sharedValue(it.key()); const QVariant value = it.value(); if (currentValues[role] != value) { @@ -417,7 +425,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded) } QHash values; - values.insert("isExpanded", expanded); + values.insert(sharedValue("isExpanded"), expanded); if (!setData(index, values)) { return false; } @@ -636,11 +644,11 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr Q_UNUSED(previous); m_sortRole = typeForRole(current); -#ifdef KFILEITEMMODEL_DEBUG if (!m_requestRole[m_sortRole]) { - kWarning() << "The sort-role has been changed to a role that has not been received yet"; + QSet newRoles = m_roles; + newRoles << current; + setRoles(newRoles); } -#endif resortAllItems(); } @@ -948,9 +956,9 @@ void KFileItemModel::dispatchPendingItemsToInsert() } } -void KFileItemModel::insertItems(QList& items) +void KFileItemModel::insertItems(QList& newItems) { - if (items.isEmpty()) { + if (newItems.isEmpty()) { return; } @@ -958,68 +966,81 @@ void KFileItemModel::insertItems(QList& items) QElapsedTimer timer; timer.start(); kDebug() << "==========================================================="; - kDebug() << "Inserting" << items.count() << "items"; + kDebug() << "Inserting" << newItems.count() << "items"; #endif m_groups.clear(); - sort(items.begin(), items.end()); + sort(newItems.begin(), newItems.end()); #ifdef KFILEITEMMODEL_DEBUG kDebug() << "[TIME] Sorting:" << timer.elapsed(); #endif KItemRangeList itemRanges; - int targetIndex = 0; - int sourceIndex = 0; - int insertedAtIndex = -1; // Index for the current item-range - int insertedCount = 0; // Count for the current item-range - int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges - while (sourceIndex < items.count()) { - // Find target index from m_items to insert the current item - // in a sorted order - const int previousTargetIndex = targetIndex; - while (targetIndex < m_itemData.count()) { - if (!lessThan(m_itemData.at(targetIndex), items.at(sourceIndex))) { - break; + const int existingItemCount = m_itemData.count(); + const int newItemCount = newItems.count(); + const int totalItemCount = existingItemCount + newItemCount; + + if (existingItemCount == 0) { + // Optimization for the common special case that there are no + // items in the model yet. Happens, e.g., when entering a folder. + m_itemData = newItems; + itemRanges << KItemRange(0, newItemCount); + } else { + m_itemData.reserve(totalItemCount); + for (int i = existingItemCount; i < totalItemCount; ++i) { + m_itemData.append(0); + } + + // We build the new list m_items in reverse order to minimize + // the number of moves and guarantee O(N) complexity. + int targetIndex = totalItemCount - 1; + int sourceIndexExistingItems = existingItemCount - 1; + int sourceIndexNewItems = newItemCount - 1; + + int rangeCount = 0; + + while (sourceIndexNewItems >= 0) { + ItemData* newItem = newItems.at(sourceIndexNewItems); + if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems))) { + // Move an existing item to its new position. If any new items + // are behind it, push the item range to itemRanges. + if (rangeCount > 0) { + itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount); + rangeCount = 0; + } + + m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems); + --sourceIndexExistingItems; + } else { + // Insert a new item into the list. + ++rangeCount; + m_itemData[targetIndex] = newItem; + --sourceIndexNewItems; } - ++targetIndex; + --targetIndex; } - if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) { - itemRanges << KItemRange(insertedAtIndex, insertedCount); - previouslyInsertedCount += insertedCount; - insertedAtIndex = targetIndex - previouslyInsertedCount; - insertedCount = 0; + // Push the final item range to itemRanges. + if (rangeCount > 0) { + itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount); } - // Insert item at the position targetIndex by transferring - // the ownership of the item-data from 'items' to m_itemData. - // m_items will be inserted after the loop (see comment below) - m_itemData.insert(targetIndex, items.at(sourceIndex)); - ++insertedCount; - - if (insertedAtIndex < 0) { - insertedAtIndex = targetIndex; - Q_ASSERT(previouslyInsertedCount == 0); - } - ++targetIndex; - ++sourceIndex; + // Note that itemRanges is still sorted in reverse order. + std::reverse(itemRanges.begin(), itemRanges.end()); } - // 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) { + // The indexes starting from the first inserted item must be adjusted. + m_items.reserve(totalItemCount); + for (int i = itemRanges.front().index; i < totalItemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } - itemRanges << KItemRange(insertedAtIndex, insertedCount); emit itemsInserted(itemRanges); #ifdef KFILEITEMMODEL_DEBUG - kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed(); + kDebug() << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed(); #endif } @@ -1111,10 +1132,10 @@ void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end()); - // Step 3: Adjust indexes in the hash m_items. Note that all indexes - // might have been changed by the removal of the items. + // Step 3: Adjust indexes in the hash m_items, starting from the + // index of the first removed item. const int newItemDataCount = m_itemData.count(); - for (int i = 0; i < newItemDataCount; ++i) { + for (int i = itemRanges.front().index; i < newItemDataCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -1231,27 +1252,27 @@ QHash KFileItemModel::retrieveData(const KFileItem& item, // KFileItem::iconName() can be very expensive if the MIME-type is unknown // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater. QHash data; - data.insert("url", item.url()); + data.insert(sharedValue("url"), item.url()); const bool isDir = item.isDir(); if (m_requestRole[IsDirRole]) { - data.insert("isDir", isDir); + data.insert(sharedValue("isDir"), isDir); } if (m_requestRole[IsLinkRole]) { const bool isLink = item.isLink(); - data.insert("isLink", isLink); + data.insert(sharedValue("isLink"), isLink); } if (m_requestRole[NameRole]) { - data.insert("text", item.text()); + data.insert(sharedValue("text"), item.text()); } if (m_requestRole[SizeRole]) { if (isDir) { - data.insert("size", QVariant()); + data.insert(sharedValue("size"), QVariant()); } else { - data.insert("size", item.size()); + data.insert(sharedValue("size"), item.size()); } } @@ -1260,19 +1281,19 @@ QHash KFileItemModel::retrieveData(const KFileItem& item, // having several thousands of items. Instead the formatting of the // date-time will be done on-demand by the view when the date will be shown. const KDateTime dateTime = item.time(KFileItem::ModificationTime); - data.insert("date", dateTime.dateTime()); + data.insert(sharedValue("date"), dateTime.dateTime()); } if (m_requestRole[PermissionsRole]) { - data.insert("permissions", item.permissionsString()); + data.insert(sharedValue("permissions"), item.permissionsString()); } if (m_requestRole[OwnerRole]) { - data.insert("owner", item.user()); + data.insert(sharedValue("owner"), item.user()); } if (m_requestRole[GroupRole]) { - data.insert("group", item.group()); + data.insert(sharedValue("group"), item.group()); } if (m_requestRole[DestinationRole]) { @@ -1280,7 +1301,7 @@ QHash KFileItemModel::retrieveData(const KFileItem& item, if (destination.isEmpty()) { destination = QLatin1String("-"); } - data.insert("destination", destination); + data.insert(sharedValue("destination"), destination); } if (m_requestRole[PathRole]) { @@ -1303,15 +1324,11 @@ QHash KFileItemModel::retrieveData(const KFileItem& item, 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); + data.insert(sharedValue("path"), path); } if (m_requestRole[IsExpandableRole]) { - data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl()); + data.insert(sharedValue("isExpandable"), item.isDir() && item.url() == item.targetUrl()); } if (m_requestRole[ExpandedParentsCountRole]) { @@ -1320,14 +1337,14 @@ QHash KFileItemModel::retrieveData(const KFileItem& item, level = parent->values["expandedParentsCount"].toInt() + 1; } - data.insert("expandedParentsCount", level); + data.insert(sharedValue("expandedParentsCount"), level); } if (item.isMimeTypeKnown()) { - data.insert("iconName", item.iconName()); + data.insert(sharedValue("iconName"), item.iconName()); if (m_requestRole[TypeRole]) { - data.insert("type", item.mimeComment()); + data.insert(sharedValue("type"), item.mimeComment()); } } @@ -1551,6 +1568,11 @@ bool KFileItemModel::useMaximumUpdateInterval() const return !m_dirLister->url().isLocalFile(); } +static bool localeAwareLessThan(const QChar& c1, const QChar& c2) +{ + return QString::localeAwareCompare(c1, c2) < 0; +} + QList > KFileItemModel::nameRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); @@ -1560,7 +1582,6 @@ QList > KFileItemModel::nameRoleGroups() const QString groupValue; QChar firstChar; - bool isLetter = false; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; @@ -1576,31 +1597,31 @@ QList > KFileItemModel::nameRoleGroups() const if (firstChar != newFirstChar) { QString newGroupValue; - if (newFirstChar >= QLatin1Char('A') && newFirstChar <= QLatin1Char('Z')) { - // Apply group 'A' - 'Z' - newGroupValue = newFirstChar; - isLetter = true; + if (newFirstChar.isLetter()) { + // Try to find a matching group in the range 'A' to 'Z'. + static std::vector lettersAtoZ; + if (lettersAtoZ.empty()) { + for (char c = 'A'; c <= 'Z'; ++c) { + lettersAtoZ.push_back(QLatin1Char(c)); + } + } + + std::vector::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan); + if (it != lettersAtoZ.end()) { + if (localeAwareLessThan(newFirstChar, *it) && it != lettersAtoZ.begin()) { + // newFirstChar belongs to the group preceding *it. + // Example: for an umlaut 'A' in the German locale, *it would be 'B' now. + --it; + } + newGroupValue = *it; + } else { + newGroupValue = newFirstChar; + } } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) { // Apply group '0 - 9' for any name that starts with a digit newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9"); - isLetter = false; } else { - if (isLetter) { - // If the current group is 'A' - 'Z' check whether a locale character - // fits into the existing group. - // TODO: This does not work in the case if e.g. the group 'O' starts with - // an umlaut 'O' -> provide unit-test to document this known issue - const QChar prevChar(firstChar.unicode() - ushort(1)); - const QChar nextChar(firstChar.unicode() + ushort(1)); - const QString currChar(newFirstChar); - const bool partOfCurrentGroup = currChar.localeAwareCompare(prevChar) > 0 && - currChar.localeAwareCompare(nextChar) < 0; - if (partOfCurrentGroup) { - continue; - } - } newGroupValue = i18nc("@title:group", "Others"); - isLetter = false; } if (newGroupValue != groupValue) { @@ -1940,6 +1961,19 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout) } } +QByteArray KFileItemModel::sharedValue(const QByteArray& value) +{ + static QSet pool; + const QSet::const_iterator it = pool.constFind(value); + + if (it != pool.constEnd()) { + return *it; + } else { + pool.insert(value); + return value; + } +} + bool KFileItemModel::isConsistent() const { if (m_items.count() != m_itemData.count()) {