diff options
Diffstat (limited to 'src/kitemviews')
22 files changed, 802 insertions, 364 deletions
diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index 1ffd5019b..8950c9a1e 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -221,12 +221,6 @@ void KFileItemListView::onPreviewsShownChanged(bool shown) void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) { - if (previous == DetailsLayout || current == DetailsLayout) { - // The details-layout requires some invisible roles that - // must be added to the model if the new layout is "details". - // If the old layout was "details" the roles will get removed. - applyRolesToModel(); - } KStandardItemListView::onItemLayoutChanged(current, previous); triggerVisibleIndexRangeUpdate(); } diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index 3a7724134..688a4da08 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -18,6 +18,8 @@ ***************************************************************************/ #include "kfileitemlistwidget.h" +#include "kfileitemmodel.h" +#include "kitemlistview.h" #include <kmimetype.h> #include <KDebug> @@ -35,6 +37,15 @@ KFileItemListWidgetInformant::~KFileItemListWidgetInformant() { } +QString KFileItemListWidgetInformant::itemText(int index, const KItemListView* view) const +{ + Q_ASSERT(qobject_cast<KFileItemModel*>(view->model())); + KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(view->model()); + + const KFileItem item = fileItemModel->fileItem(index); + return item.text(); +} + QString KFileItemListWidgetInformant::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values) const { diff --git a/src/kitemviews/kfileitemlistwidget.h b/src/kitemviews/kfileitemlistwidget.h index 24c677828..1d7bc7f01 100644 --- a/src/kitemviews/kfileitemlistwidget.h +++ b/src/kitemviews/kfileitemlistwidget.h @@ -31,6 +31,7 @@ public: virtual ~KFileItemListWidgetInformant(); protected: + virtual QString itemText(int index, const KItemListView* view) const; virtual QString roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values) const; }; diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 7b7c39ad7..ea7ac2fc1 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -152,7 +152,12 @@ int KFileItemModel::count() const QHash<QByteArray, QVariant> KFileItemModel::data(int index) const { if (index >= 0 && index < count()) { - return m_itemData.at(index)->values; + ItemData* data = m_itemData.at(index); + if (data->values.isEmpty()) { + data->values = retrieveData(data->item, data->parent); + } + + return data->values; } return QHash<QByteArray, QVariant>(); } @@ -163,7 +168,7 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value return false; } - QHash<QByteArray, QVariant> currentValues = m_itemData.at(index)->values; + QHash<QByteArray, QVariant> currentValues = data(index); // Determine which roles have been changed QSet<QByteArray> changedRoles; @@ -274,12 +279,12 @@ int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromInd { startFromIndex = qMax(0, startFromIndex); for (int i = startFromIndex; i < count(); ++i) { - if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { return i; } } for (int i = 0; i < startFromIndex; ++i) { - if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { return i; } } @@ -439,11 +444,18 @@ bool KFileItemModel::setExpanded(int index, bool expanded) m_expandedDirs.remove(targetUrl); m_dirLister->stop(url); - removeFilteredChildren(KFileItemList() << item); + const int parentLevel = expandedParentsCount(index); + const int itemCount = m_itemData.count(); + const int firstChildIndex = index + 1; + + int childIndex = firstChildIndex; + while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { + ++childIndex; + } + const int childrenCount = childIndex - firstChildIndex; - const KFileItemList itemsToRemove = childItems(item); - removeFilteredChildren(itemsToRemove); - removeItems(itemsToRemove, DeleteItemData); + removeFilteredChildren(KItemRangeList() << KItemRange(index, 1 + childrenCount)); + removeItems(KItemRangeList() << KItemRange(firstChildIndex, childrenCount), DeleteItemData); } return true; @@ -460,7 +472,9 @@ bool KFileItemModel::isExpanded(int index) const bool KFileItemModel::isExpandable(int index) const { if (index >= 0 && index < count()) { - return m_itemData.at(index)->values.value("isExpandable").toBool(); + // Call data (instead of accessing m_itemData directly) + // to ensure that the value is initialized. + return data(index).value("isExpandable").toBool(); } return false; } @@ -468,10 +482,7 @@ bool KFileItemModel::isExpandable(int index) const int KFileItemModel::expandedParentsCount(int index) const { if (index >= 0 && index < count()) { - const int parentsCount = m_itemData.at(index)->values.value("expandedParentsCount").toInt(); - if (parentsCount > 0) { - return parentsCount; - } + return expandedParentsCount(m_itemData.at(index)); } return 0; } @@ -547,21 +558,25 @@ void KFileItemModel::applyFilters() { // Check which shown items from m_itemData must get // hidden and hence moved to m_filteredItems. - KFileItemList newFilteredItems; + QVector<int> newFilteredIndexes; + + const int itemCount = m_itemData.count(); + for (int index = 0; index < itemCount; ++index) { + ItemData* itemData = m_itemData.at(index); - 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()) { const KFileItem item = itemData->item; if (!m_filter.matches(item)) { - newFilteredItems.append(item); + newFilteredIndexes.append(index); m_filteredItems.insert(item, itemData); } } } - removeItems(newFilteredItems, KeepItemData); + const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); + removeItems(removedRanges, KeepItemData); // Check which hidden items from m_filteredItems should // get visible again and hence removed from m_filteredItems. @@ -580,22 +595,24 @@ void KFileItemModel::applyFilters() insertItems(newVisibleItems); } -void KFileItemModel::removeFilteredChildren(const KFileItemList& parentsList) +void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges) { - if (m_filteredItems.isEmpty()) { + if (m_filteredItems.isEmpty() || !m_requestRole[ExpandedParentsCountRole]) { + // There are either no filtered items, or it is not possible to expand + // folders -> there cannot be any filtered children. return; } - // First, we put the parent items into a set to provide fast lookup - // while iterating over m_filteredItems and prevent quadratic - // complexity if there are N parents and N filtered items. - const QSet<KFileItem> parents = parentsList.toSet(); + QSet<ItemData*> parents; + foreach (const KItemRange& range, itemRanges) { + for (int index = range.index; index < range.index + range.count; ++index) { + parents.insert(m_itemData.at(index)); + } + } QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin(); while (it != m_filteredItems.end()) { - const ItemData* parent = it.value()->parent; - - if (parent && parents.contains(parent->item)) { + if (parents.contains(it.value()->parent)) { delete it.value(); it = m_filteredItems.erase(it); } else { @@ -685,7 +702,6 @@ void KFileItemModel::resortAllItems() oldUrls.append(itemData->item.url()); } - m_groups.clear(); m_items.clear(); // Resort the items @@ -694,20 +710,45 @@ void KFileItemModel::resortAllItems() m_items.insert(m_itemData.at(i)->item.url(), i); } - // Determine the indexes that have been moved - QList<int> movedToIndexes; - movedToIndexes.reserve(itemCount); - for (int i = 0; i < itemCount; i++) { - const int newIndex = m_items.value(oldUrls.at(i)); - movedToIndexes.append(newIndex); + // Determine the first index that has been moved. + int firstMovedIndex = 0; + while (firstMovedIndex < itemCount + && firstMovedIndex == m_items.value(oldUrls.at(firstMovedIndex))) { + ++firstMovedIndex; } - // 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); + const bool itemsHaveMoved = firstMovedIndex < itemCount; + if (itemsHaveMoved) { + m_groups.clear(); + + int lastMovedIndex = itemCount - 1; + while (lastMovedIndex > firstMovedIndex + && lastMovedIndex == m_items.value(oldUrls.at(lastMovedIndex))) { + --lastMovedIndex; + } + + Q_ASSERT(firstMovedIndex <= lastMovedIndex); + + // Create a list movedToIndexes, which has the property that + // movedToIndexes[i] is the new index of the item with the old index + // firstMovedIndex + i. + const int movedItemsCount = lastMovedIndex - firstMovedIndex + 1; + QList<int> movedToIndexes; + movedToIndexes.reserve(movedItemsCount); + for (int i = firstMovedIndex; i <= lastMovedIndex; ++i) { + const int newIndex = m_items.value(oldUrls.at(i)); + movedToIndexes.append(newIndex); + } + + emit itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes); + } else if (groupedSorting()) { + // The groups might have changed even if the order of the items has not. + const QList<QPair<int, QVariant> > oldGroups = m_groups; + m_groups.clear(); + if (groups() != oldGroups) { + emit groupsChanged(); + } + } #ifdef KFILEITEMMODEL_DEBUG kDebug() << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed(); @@ -821,29 +862,49 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items) { dispatchPendingItemsToInsert(); - KFileItemList itemsToRemove = items; - if (m_requestRole[ExpandedParentsCountRole]) { - // Assure that removing a parent item also results in removing all children - foreach (const KFileItem& item, items) { - itemsToRemove.append(childItems(item)); - } - } + QVector<int> indexesToRemove; + indexesToRemove.reserve(items.count()); - if (!m_filteredItems.isEmpty()) { - foreach (const KFileItem& item, itemsToRemove) { + foreach (const KFileItem& item, items) { + const KUrl url = item.url(); + const int index = m_items.value(url, -1); + if (index >= 0) { + indexesToRemove.append(index); + } else { + // Probably the item has been filtered. QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item); if (it != m_filteredItems.end()) { delete it.value(); m_filteredItems.erase(it); } } + } - if (m_requestRole[ExpandedParentsCountRole]) { - removeFilteredChildren(itemsToRemove); + std::sort(indexesToRemove.begin(), indexesToRemove.end()); + + if (m_requestRole[ExpandedParentsCountRole] && !m_expandedDirs.isEmpty()) { + // Assure that removing a parent item also results in removing all children + QVector<int> indexesToRemoveWithChildren; + indexesToRemoveWithChildren.reserve(m_items.count()); + + const int itemCount = m_itemData.count(); + foreach (int index, indexesToRemove) { + indexesToRemoveWithChildren.append(index); + + const int parentLevel = expandedParentsCount(index); + int childIndex = index + 1; + while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { + indexesToRemoveWithChildren.append(childIndex); + ++childIndex; + } } + + indexesToRemove = indexesToRemoveWithChildren; } - removeItems(itemsToRemove, DeleteItemData); + const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove); + removeFilteredChildren(itemRanges); + removeItems(itemRanges, DeleteItemData); } void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items) @@ -895,30 +956,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& // Extract the item-ranges out of the changed indexes qSort(indexes); - - KItemRangeList itemRangeList; - int previousIndex = indexes.at(0); - int rangeIndex = previousIndex; - int rangeCount = 1; - - const int maxIndex = indexes.count() - 1; - for (int i = 1; i <= maxIndex; ++i) { - const int currentIndex = indexes.at(i); - if (currentIndex == previousIndex + 1) { - ++rangeCount; - } else { - itemRangeList.append(KItemRange(rangeIndex, rangeCount)); - - rangeIndex = currentIndex; - rangeCount = 1; - } - previousIndex = currentIndex; - } - - if (rangeCount > 0) { - itemRangeList.append(KItemRange(rangeIndex, rangeCount)); - } - + const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes); emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles); } @@ -1056,53 +1094,21 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems) #endif } -static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers) +void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior) { - if (sortedNumbers.empty()) { - return KItemRangeList(); - } - - KItemRangeList result; - - QList<int>::const_iterator it = sortedNumbers.begin(); - int index = *it; - int count = 1; - - ++it; - - QList<int>::const_iterator end = sortedNumbers.end(); - while (it != end) { - if (*it == index + count) { - ++count; - } else { - result << KItemRange(index, count); - index = *it; - count = 1; - } - ++it; + if (itemRanges.isEmpty()) { + return; } - result << KItemRange(index, count); - return result; -} - -void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior behavior) -{ -#ifdef KFILEITEMMODEL_DEBUG - kDebug() << "Removing " << items.count() << "items"; -#endif - m_groups.clear(); - // Step 1: Determine the indexes of the removed items, remove them from - // the hash m_items, and free the ItemData. - QList<int> indexesToRemove; - indexesToRemove.reserve(items.count()); - foreach (const KFileItem& item, items) { - const KUrl url = item.url(); - const int index = m_items.value(url, -1); - if (index >= 0) { - indexesToRemove.append(index); + // Step 1: Remove the items from the hash m_items, and free the ItemData. + int removedItemsCount = 0; + foreach (const KItemRange& range, itemRanges) { + removedItemsCount += range.count; + + for (int index = range.index; index < range.index + range.count; ++index) { + const KUrl url = m_itemData.at(index)->item.url(); // Prevent repeated expensive rehashing by using QHash::erase(), // rather than QHash::remove(). @@ -1117,14 +1123,7 @@ void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior } } - if (indexesToRemove.isEmpty()) { - return; - } - - std::sort(indexesToRemove.begin(), indexesToRemove.end()); - // 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; @@ -1142,7 +1141,7 @@ void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior } } - m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end()); + m_itemData.erase(m_itemData.end() - removedItemsCount, m_itemData.end()); // Step 3: Adjust indexes in the hash m_items, starting from the // index of the first removed item. @@ -1172,30 +1171,75 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl& foreach (const KFileItem& item, items) { ItemData* itemData = new ItemData(); itemData->item = item; - itemData->values = retrieveData(item, parentItem); itemData->parent = parentItem; itemDataList.append(itemData); } + switch (m_sortRole) { + case PermissionsRole: + case OwnerRole: + case GroupRole: + case DestinationRole: + case PathRole: + // These roles can be determined with retrieveData, and they have to be stored + // in the QHash "values" for the sorting. + foreach (ItemData* itemData, itemDataList) { + itemData->values = retrieveData(itemData->item, parentItem); + } + break; + + case TypeRole: + // At least store the data including the file type for items with known MIME type. + foreach (ItemData* itemData, itemDataList) { + const KFileItem item = itemData->item; + if (item.isDir() || item.isMimeTypeKnown()) { + itemData->values = retrieveData(itemData->item, parentItem); + } + } + break; + + default: + // The other roles are either resolved by KFileItemModelRolesUpdater + // (this includes the SizeRole for directories), or they do not need + // to be stored in the QHash "values" for sorting because the data can + // be retrieved directly from the KFileItem (NameRole, SiezRole for files, + // DateRole). + break; + } + return itemDataList; } +int KFileItemModel::expandedParentsCount(const ItemData* data) +{ + // The hash 'values' is only guaranteed to contain the key "expandedParentsCount" + // if the corresponding item is expanded, and it is not a top-level item. + const ItemData* parent = data->parent; + if (parent) { + if (parent->parent) { + Q_ASSERT(parent->values.contains("expandedParentsCount")); + return parent->values.value("expandedParentsCount").toInt() + 1; + } else { + return 1; + } + } else { + return 0; + } +} + void KFileItemModel::removeExpandedItems() { - KFileItemList expandedItems; + QVector<int> indexesToRemove; const int maxIndex = m_itemData.count() - 1; for (int i = 0; i <= maxIndex; ++i) { const ItemData* itemData = m_itemData.at(i); - if (itemData->values.value("expandedParentsCount").toInt() > 0) { - expandedItems.append(itemData->item); + if (itemData->parent) { + indexesToRemove.append(i); } } - // The m_expandedParentsCountRoot may not get reset before all items with - // a bigger count have been removed. - removeItems(expandedItems, DeleteItemData); - + removeItems(KItemRangeList::fromSortedContainer(indexesToRemove), DeleteItemData); m_expandedDirs.clear(); // Also remove all filtered items which have a parent. @@ -1405,7 +1449,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item, if (m_requestRole[ExpandedParentsCountRole]) { if (parent) { - const int level = parent->values["expandedParentsCount"].toInt() + 1; + const int level = expandedParentsCount(parent) + 1; data.insert(sharedValue("expandedParentsCount"), level); } } @@ -1429,8 +1473,8 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const int result = 0; if (a->parent != b->parent) { - const int expansionLevelA = a->values.value("expandedParentsCount").toInt(); - const int expansionLevelB = b->values.value("expandedParentsCount").toInt(); + const int expansionLevelA = expandedParentsCount(a); + const int expansionLevelB = expandedParentsCount(b); // 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. @@ -1450,7 +1494,7 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const a = a->parent; } - Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt()); + Q_ASSERT(expandedParentsCount(a) == expandedParentsCount(b)); // Compare the last parents of a and b which are different. while (a->parent != b->parent) { @@ -1660,7 +1704,7 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const continue; } - const QString name = m_itemData.at(i)->values.value("text").toString(); + const QString name = m_itemData.at(i)->item.text(); // Use the first character of the name as group indication QChar newFirstChar = name.at(0).toUpper(); @@ -1943,23 +1987,6 @@ QList<QPair<int, QVariant> > KFileItemModel::genericStringRoleGroups(const QByte 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("expandedParentsCount").toInt(); - ++index; - while (index < m_itemData.count() && m_itemData.at(index)->values.value("expandedParentsCount").toInt() > parentLevel) { - items.append(m_itemData.at(index)->item); - ++index; - } - } - - return items; -} - void KFileItemModel::emitSortProgress(int resolvedCount) { // Be tolerant against a resolvedCount with a wrong range. @@ -2086,7 +2113,7 @@ bool KFileItemModel::isConsistent() const 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) { + if (expandedParentsCount(data) != expandedParentsCount(parent) + 1) { qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item; return false; } diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index c87ee9736..d00570549 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -309,7 +309,7 @@ private: }; void insertItems(QList<ItemData*>& items); - void removeItems(const KFileItemList& items, RemoveItemsBehavior behavior); + void removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior); /** * Helper method for insertItems() and removeItems(): Creates @@ -319,6 +319,8 @@ private: */ QList<ItemData*> createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const; + static int expandedParentsCount(const ItemData* data); + void removeExpandedItems(); /** @@ -388,11 +390,6 @@ private: bool isChildItem(int index) const; /** - * @return Recursive list of child items that have \a item as upper most parent. - */ - KFileItemList childItems(const KFileItem& item) const; - - /** * Is invoked by KFileItemModelRolesUpdater and results in emitting the * sortProgress signal with a percent-value of the progress. */ @@ -407,7 +404,7 @@ private: * Removes filtered items whose expanded parents have been deleted * or collapsed via setExpanded(parentIndex, false). */ - void removeFilteredChildren(const KFileItemList& parentsList); + void removeFilteredChildren(const KItemRangeList& parents); /** * Maps the QByteArray-roles to RoleTypes and provides translation- and @@ -490,7 +487,11 @@ private: inline bool KFileItemModel::isChildItem(int index) const { - return m_requestRole[ExpandedParentsCountRole] && m_itemData.at(index)->values.value("expandedParentsCount").toInt() > 0; + if (m_itemData.at(index)->parent) { + return true; + } else { + return false; + } } #endif diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index e9b69566d..d6445c981 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -24,13 +24,13 @@ #include <KConfig> #include <KConfigGroup> #include <KDebug> -#include <KDirWatch> #include <KFileItem> #include <KGlobal> #include <KIO/JobUiDelegate> #include <KIO/PreviewJob> #include "private/kpixmapmodifier.h" +#include "private/kdirectorycontentscounter.h" #include <QApplication> #include <QPainter> @@ -46,14 +46,6 @@ #include <Nepomuk2/ResourceManager> #endif -// Required includes for subItemsCount(): -#ifdef Q_WS_WIN - #include <QDir> -#else - #include <dirent.h> - #include <QFile> -#endif - // #define KFILEITEMMODELROLESUPDATER_DEBUG namespace { @@ -95,8 +87,7 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO m_recentlyChangedItemsTimer(0), m_recentlyChangedItems(), m_changedItems(), - m_dirWatcher(0), - m_watchedDirs() + m_directoryContentsCounter(0) #ifdef HAVE_NEPOMUK , m_nepomukResourceWatcher(0), m_nepomukUriItems() @@ -135,10 +126,9 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO m_resolvableRoles += KNepomukRolesProvider::instance().roles(); #endif - // When folders are expandable or the item-count is shown for folders, it is necessary - // to watch the number of items of the sub-folder to be able to react on changes. - m_dirWatcher = new KDirWatch(this); - connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString))); + m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this); + connect(m_directoryContentsCounter, SIGNAL(result(QString,int)), + this, SLOT(slotDirectoryContentsCountReceived(QString,int))); } KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater() @@ -367,25 +357,6 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang const bool allItemsRemoved = (m_model->count() == 0); - if (!m_watchedDirs.isEmpty()) { - // Don't let KDirWatch watch for removed items - if (allItemsRemoved) { - foreach (const QString& path, m_watchedDirs) { - m_dirWatcher->removeDir(path); - } - m_watchedDirs.clear(); - } else { - QMutableSetIterator<QString> it(m_watchedDirs); - while (it.hasNext()) { - const QString& path = it.next(); - if (m_model->index(KUrl(path)) < 0) { - m_dirWatcher->removeDir(path); - it.remove(); - } - } - } - } - #ifdef HAVE_NEPOMUK if (m_nepomukResourceWatcher) { // Don't let the ResourceWatcher watch for removed items @@ -783,7 +754,7 @@ void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resour #endif } -void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path) +void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count) { const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); @@ -791,16 +762,9 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path) if (getSizeRole || getIsExpandableRole) { const int index = m_model->index(KUrl(path)); if (index >= 0) { - if (!m_model->fileItem(index).isDir()) { - // If INotify is used, KDirWatch issues the dirty() signal - // also for changed files inside the directory, even if we - // don't enable this behavior explicitly (see bug 309740). - return; - } QHash<QByteArray, QVariant> data; - const int count = subItemsCount(path); if (getSizeRole) { data.insert("size", count); } @@ -808,9 +772,11 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path) data.insert("isExpandable", count > 0); } - // Note that we do not block the itemsChanged signal here. - // This ensures that a new preview will be generated. + disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>))); m_model->setData(index, data); + connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>))); } } } @@ -1037,7 +1003,7 @@ void KFileItemModelRolesUpdater::applySortRole(int index) data.insert("type", item.mimeComment()); } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { const QString path = item.localPath(); - data.insert("size", subItemsCount(path)); + data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path)); } else { // Probably the sort role is a Nepomuk role - just determine all roles. data = rolesData(item); @@ -1105,7 +1071,7 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol return false; } -QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const +QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) { QHash<QByteArray, QVariant> data; @@ -1114,19 +1080,10 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte if ((getSizeRole || getIsExpandableRole) && item.isDir()) { if (item.isLocalFile()) { + // Tell m_directoryContentsCounter that we want to count the items + // inside the directory. The result will be received in slotDirectoryContentsCountReceived. const QString path = item.localPath(); - const int count = subItemsCount(path); - if (getSizeRole) { - data.insert("size", count); - } - if (getIsExpandableRole) { - data.insert("isExpandable", count > 0); - } - - if (!m_dirWatcher->contains(path)) { - m_dirWatcher->addDir(path); - m_watchedDirs.insert(path); - } + m_directoryContentsCounter->addDirectory(path); } else if (getSizeRole) { data.insert("size", -1); // -1 indicates an unknown number of items } @@ -1170,61 +1127,6 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte return data; } -int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const -{ - const bool countHiddenFiles = m_model->showHiddenFiles(); - const bool showFoldersOnly = m_model->showDirectoriesOnly(); - -#ifdef Q_WS_WIN - QDir dir(path); - QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System; - if (countHiddenFiles) { - filters |= QDir::Hidden; - } - if (showFoldersOnly) { - filters |= QDir::Dirs; - } else { - filters |= QDir::AllEntries; - } - return dir.entryList(filters).count(); -#else - // Taken from kdelibs/kio/kio/kdirmodel.cpp - // Copyright (C) 2006 David Faure <[email protected]> - - int count = -1; - DIR* dir = ::opendir(QFile::encodeName(path)); - if (dir) { // krazy:exclude=syscalls - count = 0; - struct dirent *dirEntry = 0; - while ((dirEntry = ::readdir(dir))) { - if (dirEntry->d_name[0] == '.') { - if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) { - // Skip "." or hidden files - continue; - } - if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { - // Skip ".." - continue; - } - } - - // If only directories are counted, consider an unknown file type and links also - // as directory instead of trying to do an expensive stat() - // (see bugs 292642 and 299997). - const bool countEntry = !showFoldersOnly || - dirEntry->d_type == DT_DIR || - dirEntry->d_type == DT_LNK || - dirEntry->d_type == DT_UNKNOWN; - if (countEntry) { - ++count; - } - } - ::closedir(dir); - } - return count; -#endif -} - void KFileItemModelRolesUpdater::updateAllPreviews() { if (m_state == Paused) { diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h index 409f098e8..fced44a85 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.h +++ b/src/kitemviews/kfileitemmodelrolesupdater.h @@ -32,7 +32,7 @@ #include <QSize> #include <QStringList> -class KDirWatch; +class KDirectoryContentsCounter; class KFileItemModel; class KJob; class QPixmap; @@ -218,12 +218,7 @@ private slots: void applyChangedNepomukRoles(const Nepomuk2::Resource& resource, const Nepomuk2::Types::Property& property); - /** - * Is invoked if a directory watched by KDirWatch got dirty. Updates - * the "isExpandable"- and "size"-roles of the item that matches to - * the given path. - */ - void slotDirWatchDirty(const QString& path); + void slotDirectoryContentsCountReceived(const QString& path, int count); private: /** @@ -267,7 +262,7 @@ private: ResolveAll }; bool applyResolvedRoles(const KFileItem& item, ResolveHint hint); - QHash<QByteArray, QVariant> rolesData(const KFileItem& item) const; + QHash<QByteArray, QVariant> rolesData(const KFileItem& item); /** * @return The number of items of the path \a path. @@ -349,9 +344,8 @@ private: // Items which have not been changed repeatedly recently. QSet<KFileItem> m_changedItems; - KDirWatch* m_dirWatcher; - mutable QSet<QString> m_watchedDirs; // Required as sadly KDirWatch does not offer a getter method - // to get all watched directories. + KDirectoryContentsCounter* m_directoryContentsCounter; + #ifdef HAVE_NEPOMUK Nepomuk2::ResourceWatcher* m_nepomukResourceWatcher; mutable QHash<QUrl, KUrl> m_nepomukUriItems; diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index a66715090..b3d805a91 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1233,6 +1233,13 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, QAccessible::updateAccessibility(this, 0, QAccessible::TableModelChanged); } +void KItemListView::slotGroupsChanged() +{ + updateVisibleGroupHeaders(); + doLayout(NoAnimation); + updateSiblingsInformation(); +} + void KItemListView::slotGroupedSortingChanged(bool current) { m_grouped = current; @@ -1527,6 +1534,8 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsRemoved(KItemRangeList))); disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)), this, SLOT(slotItemsMoved(KItemRange,QList<int>))); + disconnect(m_model, SIGNAL(groupsChanged()), + this, SLOT(slotGroupsChanged())); disconnect(m_model, SIGNAL(groupedSortingChanged(bool)), this, SLOT(slotGroupedSortingChanged(bool))); disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), @@ -1550,6 +1559,8 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsRemoved(KItemRangeList))); connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)), this, SLOT(slotItemsMoved(KItemRange,QList<int>))); + connect(m_model, SIGNAL(groupsChanged()), + this, SLOT(slotGroupsChanged())); connect(m_model, SIGNAL(groupedSortingChanged(bool)), this, SLOT(slotGroupedSortingChanged(bool))); connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 6467b8c91..14360b02b 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -394,6 +394,7 @@ protected slots: virtual void slotItemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes); virtual void slotItemsChanged(const KItemRangeList& itemRanges, const QSet<QByteArray>& roles); + virtual void slotGroupsChanged(); virtual void slotGroupedSortingChanged(bool current); virtual void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index c13c9f88c..edce95ece 100644 --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -22,17 +22,6 @@ #include "kitemmodelbase.h" -KItemRange::KItemRange(int index, int count) : - index(index), - count(count) -{ -} - -bool KItemRange::operator == (const KItemRange& other) const -{ - return index == other.index && count == other.count; -} - KItemModelBase::KItemModelBase(QObject* parent) : QObject(parent), m_groupedSorting(false), diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h index 70f688390..c5b9a0ca5 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -25,6 +25,8 @@ #include <libdolphin_export.h> +#include <kitemviews/kitemrange.h> + #include <QHash> #include <QObject> #include <QSet> @@ -32,16 +34,6 @@ class QMimeData; -struct KItemRange -{ - KItemRange(int index = 0, int count = 0); - int index; - int count; - - bool operator == (const KItemRange& other) const; -}; -typedef QList<KItemRange> KItemRangeList; - /** * @brief Base class for model implementations used by KItemListView and KItemListController. * @@ -218,11 +210,20 @@ signals: * with the items 5 and 6 then the parameters look like this: * - itemRange: has the index 0 and a count of 7. * - movedToIndexes: Contains the seven values 5, 6, 2, 3, 4, 0, 1 + * + * This signal implies that the groups might have changed. Therefore, + * gropusChanged() is not emitted if this signal is emitted. */ void itemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes); void itemsChanged(const KItemRangeList& itemRanges, const QSet<QByteArray>& roles); + /** + * Is emitted if the groups have changed, even though the order of the + * items has not been modified. + */ + void groupsChanged(); + void groupedSortingChanged(bool current); void sortRoleChanged(const QByteArray& current, const QByteArray& previous); void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); diff --git a/src/kitemviews/kitemrange.h b/src/kitemviews/kitemrange.h new file mode 100644 index 000000000..70927b915 --- /dev/null +++ b/src/kitemviews/kitemrange.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * Based on the Itemviews NG project from Trolltech Labs: * + * http://qt.gitorious.org/qt-labs/itemviews-ng * + * * + * 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 * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KITEMRANGE_H +#define KITEMRANGE_H + +#include <QList> + +struct KItemRange +{ + KItemRange(int index = 0, int count = 0); + int index; + int count; + + bool operator == (const KItemRange& other) const; +}; + +inline KItemRange::KItemRange(int index, int count) : + index(index), + count(count) +{ +} + +inline bool KItemRange::operator == (const KItemRange& other) const +{ + return index == other.index && count == other.count; +} + + +class KItemRangeList : public QList<KItemRange> +{ +public: + KItemRangeList() : QList<KItemRange>() {} + KItemRangeList(const QList<KItemRange>& list) : QList<KItemRange>(list) {} + + template<class Container> + static KItemRangeList fromSortedContainer(const Container& container); + + KItemRangeList& operator<<(const KItemRange& range) + { + append(range); + return *this; + } +}; + +template<class Container> +KItemRangeList KItemRangeList::fromSortedContainer(const Container& container) +{ + typename Container::const_iterator it = container.constBegin(); + const typename Container::const_iterator end = container.constEnd(); + + if (it == end) { + return KItemRangeList(); + } + + KItemRangeList result; + + int index = *it; + int count = 1; + + ++it; + + while (it != end) { + if (*it == index + count) { + ++count; + } else { + result << KItemRange(index, count); + index = *it; + count = 1; + } + ++it; + } + + result << KItemRange(index, count); + return result; +} + +#endif diff --git a/src/kitemviews/kstandarditemlistview.cpp b/src/kitemviews/kstandarditemlistview.cpp index bd4f6081f..135cd0b7d 100644 --- a/src/kitemviews/kstandarditemlistview.cpp +++ b/src/kitemviews/kstandarditemlistview.cpp @@ -48,23 +48,8 @@ void KStandardItemListView::setItemLayout(ItemLayout layout) const ItemLayout previous = m_itemLayout; m_itemLayout = layout; - switch (layout) { - case IconsLayout: - setScrollOrientation(Qt::Vertical); - setSupportsItemExpanding(false); - break; - case DetailsLayout: - setScrollOrientation(Qt::Vertical); - setSupportsItemExpanding(true); - break; - case CompactLayout: - setScrollOrientation(Qt::Horizontal); - setSupportsItemExpanding(false); - break; - default: - Q_ASSERT(false); - break; - } + setSupportsItemExpanding(itemLayoutSupportsItemExpanding(layout)); + setScrollOrientation(layout == CompactLayout ? Qt::Horizontal : Qt::Vertical); onItemLayoutChanged(layout, previous); @@ -117,6 +102,11 @@ bool KStandardItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& c return false; } +bool KStandardItemListView::itemLayoutSupportsItemExpanding(ItemLayout layout) const +{ + return layout == DetailsLayout; +} + void KStandardItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) { Q_UNUSED(current); diff --git a/src/kitemviews/kstandarditemlistview.h b/src/kitemviews/kstandarditemlistview.h index fd4fa861c..f5b0bfd8c 100644 --- a/src/kitemviews/kstandarditemlistview.h +++ b/src/kitemviews/kstandarditemlistview.h @@ -63,6 +63,7 @@ protected: virtual KItemListGroupHeaderCreatorBase* defaultGroupHeaderCreator() const; virtual void initializeItemListWidget(KItemListWidget* item); virtual bool itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const; + virtual bool itemLayoutSupportsItemExpanding(ItemLayout layout) const; virtual void onItemLayoutChanged(ItemLayout current, ItemLayout previous); virtual void onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous); virtual void onSupportsItemExpandingChanged(bool supportsExpanding); diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index bc0503663..302150fec 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -57,13 +57,12 @@ KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant() QSizeF KStandardItemListWidgetInformant::itemSizeHint(int index, const KItemListView* view) const { - const QHash<QByteArray, QVariant> values = view->model()->data(index); const KItemListStyleOption& option = view->styleOption(); const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); switch (static_cast<const KStandardItemListView*>(view)->itemLayout()) { case KStandardItemListWidget::IconsLayout: { - const QString text = KStringHandler::preProcessWrap(values["text"].toString()); + const QString text = KStringHandler::preProcessWrap(itemText(index, view)); const qreal itemWidth = view->itemSize().width(); const qreal maxWidth = itemWidth - 2 * option.padding; @@ -100,6 +99,7 @@ QSizeF KStandardItemListWidgetInformant::itemSizeHint(int index, const KItemList // to show all roles without horizontal clipping. qreal maximumRequiredWidth = 0.0; + const QHash<QByteArray, QVariant> values = view->model()->data(index); foreach (const QByteArray& role, view->visibleRoles()) { const QString text = roleText(role, values); const qreal requiredWidth = option.fontMetrics.width(text); @@ -159,6 +159,11 @@ qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArra return width; } +QString KStandardItemListWidgetInformant::itemText(int index, const KItemListView* view) const +{ + return view->model()->data(index).value("text").toString(); +} + QString KStandardItemListWidgetInformant::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values) const { @@ -797,11 +802,12 @@ void KStandardItemListWidget::updateExpansionArea() const QHash<QByteArray, QVariant> values = data(); const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); if (expandedParentsCount >= 0) { + const KItemListStyleOption& option = styleOption(); const qreal widgetHeight = size().height(); - const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2; + const qreal inc = (widgetHeight - option.iconSize) / 2; const qreal x = expandedParentsCount * widgetHeight + inc; const qreal y = inc; - m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall); + m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize); return; } } diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h index 4bf6116fd..7dd93b2b8 100644 --- a/src/kitemviews/kstandarditemlistwidget.h +++ b/src/kitemviews/kstandarditemlistwidget.h @@ -45,6 +45,15 @@ public: const KItemListView* view) const; protected: /** + * @return The value of the "text" role. The default implementation returns + * view->model()->data(index)["text"]. If a derived class can + * prevent the (possibly expensive) construction of the + * QHash<QByteArray, QVariant> returned by KItemModelBase::data(int), + * it can reimplement this function. + */ + virtual QString itemText(int index, const KItemListView* view) const; + + /** * @return String representation of the role \a role. The representation of * a role might depend on other roles, so the values of all roles * are passed as parameter. diff --git a/src/kitemviews/private/kdirectorycontentscounter.cpp b/src/kitemviews/private/kdirectorycontentscounter.cpp new file mode 100644 index 000000000..fd8479feb --- /dev/null +++ b/src/kitemviews/private/kdirectorycontentscounter.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * 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 * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kdirectorycontentscounter.h" + +#include "kdirectorycontentscounterworker.h" +#include <kitemviews/kfileitemmodel.h> + +#include <KDirWatch> +#include <QThread> + +KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) : + QObject(parent), + m_model(model), + m_queue(), + m_workerThread(0), + m_worker(0), + m_workerIsBusy(false), + m_dirWatcher(0), + m_watchedDirs() +{ + connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), + this, SLOT(slotItemsRemoved())); + + m_workerThread = new QThread(this); + m_worker = new KDirectoryContentsCounterWorker(); + m_worker->moveToThread(m_workerThread); + + connect(this, SIGNAL(requestDirectoryContentsCount(QString,KDirectoryContentsCounterWorker::Options)), + m_worker, SLOT(countDirectoryContents(QString,KDirectoryContentsCounterWorker::Options))); + connect(m_worker, SIGNAL(result(QString,int)), + this, SLOT(slotResult(QString,int))); + + m_workerThread->start(); + + m_dirWatcher = new KDirWatch(this); + connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString))); +} + +KDirectoryContentsCounter::~KDirectoryContentsCounter() +{ + m_workerThread->quit(); + m_workerThread->wait(); + + delete m_worker; +} + +void KDirectoryContentsCounter::addDirectory(const QString& path) +{ + startWorker(path); +} + +int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path) +{ + if (!m_dirWatcher->contains(path)) { + m_dirWatcher->addDir(path); + m_watchedDirs.insert(path); + } + + KDirectoryContentsCounterWorker::Options options; + + if (m_model->showHiddenFiles()) { + options |= KDirectoryContentsCounterWorker::CountHiddenFiles; + } + + if (m_model->showDirectoriesOnly()) { + options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; + } + + return KDirectoryContentsCounterWorker::subItemsCount(path, options); +} + +void KDirectoryContentsCounter::slotResult(const QString& path, int count) +{ + m_workerIsBusy = false; + + if (!m_dirWatcher->contains(path)) { + m_dirWatcher->addDir(path); + m_watchedDirs.insert(path); + } + + if (!m_queue.isEmpty()) { + startWorker(m_queue.dequeue()); + } + + emit result(path, count); +} + +void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) +{ + const int index = m_model->index(KUrl(path)); + if (index >= 0) { + if (!m_model->fileItem(index).isDir()) { + // If INotify is used, KDirWatch issues the dirty() signal + // also for changed files inside the directory, even if we + // don't enable this behavior explicitly (see bug 309740). + return; + } + + startWorker(path); + } +} + +void KDirectoryContentsCounter::slotItemsRemoved() +{ + const bool allItemsRemoved = (m_model->count() == 0); + + if (!m_watchedDirs.isEmpty()) { + // Don't let KDirWatch watch for removed items + if (allItemsRemoved) { + foreach (const QString& path, m_watchedDirs) { + m_dirWatcher->removeDir(path); + } + m_watchedDirs.clear(); + m_queue.clear(); + } else { + QMutableSetIterator<QString> it(m_watchedDirs); + while (it.hasNext()) { + const QString& path = it.next(); + if (m_model->index(KUrl(path)) < 0) { + m_dirWatcher->removeDir(path); + it.remove(); + } + } + } + } +} + +void KDirectoryContentsCounter::startWorker(const QString& path) +{ + if (m_workerIsBusy) { + m_queue.enqueue(path); + } else { + KDirectoryContentsCounterWorker::Options options; + + if (m_model->showHiddenFiles()) { + options |= KDirectoryContentsCounterWorker::CountHiddenFiles; + } + + if (m_model->showDirectoriesOnly()) { + options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; + } + + emit requestDirectoryContentsCount(path, options); + m_workerIsBusy = true; + } +} diff --git a/src/kitemviews/private/kdirectorycontentscounter.h b/src/kitemviews/private/kdirectorycontentscounter.h new file mode 100644 index 000000000..425c3632a --- /dev/null +++ b/src/kitemviews/private/kdirectorycontentscounter.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * 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 * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KDIRECTORYCONTENTSCOUNTER_H +#define KDIRECTORYCONTENTSCOUNTER_H + +#include "kdirectorycontentscounterworker.h" + +#include <QSet> +#include <QQueue> + +class KDirWatch; +class KFileItemModel; +class QString; + +class KDirectoryContentsCounter : public QObject +{ + Q_OBJECT + +public: + explicit KDirectoryContentsCounter(KFileItemModel* model, QObject* parent = 0); + ~KDirectoryContentsCounter(); + + /** + * Requests the number of items inside the directory \a path. The actual + * counting is done asynchronously, and the result is announced via the + * signal \a result. + * + * The directory \a path is watched for changes, and the signal is emitted + * again if a change occurs. + */ + void addDirectory(const QString& path); + + /** + * In contrast to \a addDirectory, this function counts the items inside + * the directory \a path synchronously and returns the result. + * + * The directory is watched for changes, and the signal \a result is + * emitted if a change occurs. + */ + int countDirectoryContentsSynchronously(const QString& path); + +signals: + /** + * Signals that the directory \a path contains \a count items. + */ + void result(const QString& path, int count); + + void requestDirectoryContentsCount(const QString& path, KDirectoryContentsCounterWorker::Options options); + +private slots: + void slotResult(const QString& path, int count); + void slotDirWatchDirty(const QString& path); + void slotItemsRemoved(); + +private: + void startWorker(const QString& path); + +private: + KFileItemModel* m_model; + + QQueue<QString> m_queue; + + QThread* m_workerThread; + KDirectoryContentsCounterWorker* m_worker; + bool m_workerIsBusy; + + KDirWatch* m_dirWatcher; + QSet<QString> m_watchedDirs; // Required as sadly KDirWatch does not offer a getter method + // to get all watched directories. +}; + +#endif
\ No newline at end of file diff --git a/src/kitemviews/private/kdirectorycontentscounterworker.cpp b/src/kitemviews/private/kdirectorycontentscounterworker.cpp new file mode 100644 index 000000000..e649c20e1 --- /dev/null +++ b/src/kitemviews/private/kdirectorycontentscounterworker.cpp @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * 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 * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kdirectorycontentscounterworker.h" + +// Required includes for subItemsCount(): +#ifdef Q_WS_WIN + #include <QDir> +#else + #include <dirent.h> + #include <QFile> +#endif + +KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject* parent) : + QObject(parent) +{ + qRegisterMetaType<KDirectoryContentsCounterWorker::Options>(); +} + +int KDirectoryContentsCounterWorker::subItemsCount(const QString& path, Options options) +{ + const bool countHiddenFiles = options & CountHiddenFiles; + const bool countDirectoriesOnly = options & CountDirectoriesOnly; + +#ifdef Q_WS_WIN + QDir dir(path); + QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System; + if (countHiddenFiles) { + filters |= QDir::Hidden; + } + if (countDirectoriesOnly) { + filters |= QDir::Dirs; + } else { + filters |= QDir::AllEntries; + } + return dir.entryList(filters).count(); +#else + // Taken from kdelibs/kio/kio/kdirmodel.cpp + // Copyright (C) 2006 David Faure <[email protected]> + + int count = -1; + DIR* dir = ::opendir(QFile::encodeName(path)); + if (dir) { // krazy:exclude=syscalls + count = 0; + struct dirent *dirEntry = 0; + while ((dirEntry = ::readdir(dir))) { + if (dirEntry->d_name[0] == '.') { + if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) { + // Skip "." or hidden files + continue; + } + if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { + // Skip ".." + continue; + } + } + + // If only directories are counted, consider an unknown file type and links also + // as directory instead of trying to do an expensive stat() + // (see bugs 292642 and 299997). + const bool countEntry = !countDirectoriesOnly || + dirEntry->d_type == DT_DIR || + dirEntry->d_type == DT_LNK || + dirEntry->d_type == DT_UNKNOWN; + if (countEntry) { + ++count; + } + } + ::closedir(dir); + } + return count; +#endif +} + +void KDirectoryContentsCounterWorker::countDirectoryContents(const QString& path, Options options) +{ + emit result(path, subItemsCount(path, options)); +} diff --git a/src/kitemviews/private/kdirectorycontentscounterworker.h b/src/kitemviews/private/kdirectorycontentscounterworker.h new file mode 100644 index 000000000..96831ef81 --- /dev/null +++ b/src/kitemviews/private/kdirectorycontentscounterworker.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * 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 * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef KDIRECTORYCONTENTENTSCOUNTERWORKER_H +#define KDIRECTORYCONTENTENTSCOUNTERWORKER_H + +#include <QFlags> +#include <QMetaType> +#include <QObject> + +class QString; + +class KDirectoryContentsCounterWorker : public QObject +{ + Q_OBJECT + +public: + enum Option { + NoOptions = 0x0, + CountHiddenFiles = 0x1, + CountDirectoriesOnly = 0x2 + }; + Q_DECLARE_FLAGS(Options, Option) + + explicit KDirectoryContentsCounterWorker(QObject* parent = 0); + + /** + * Counts the items inside the directory \a path using the options + * \a options. + * + * @return The number of items. + */ + static int subItemsCount(const QString& path, Options options); + +signals: + /** + * Signals that the directory \a path contains \a count items. + */ + void result(const QString& path, int count); + +public slots: + /** + * Requests the number of items inside the directory \a path using the + * options \a options. The result is announced via the signal \a result. + */ + // Note that the full type name KDirectoryContentsCounterWorker::Options + // is needed here. Just using 'Options' is OK for the compiler, but + // confuses moc. + void countDirectoryContents(const QString& path, KDirectoryContentsCounterWorker::Options options); +}; + +Q_DECLARE_METATYPE(KDirectoryContentsCounterWorker::Options) +Q_DECLARE_OPERATORS_FOR_FLAGS(KDirectoryContentsCounterWorker::Options) + +#endif diff --git a/src/kitemviews/private/kitemlistsizehintresolver.cpp b/src/kitemviews/private/kitemlistsizehintresolver.cpp index e44630243..0e2286b45 100644 --- a/src/kitemviews/private/kitemlistsizehintresolver.cpp +++ b/src/kitemviews/private/kitemlistsizehintresolver.cpp @@ -120,7 +120,7 @@ void KItemListSizeHintResolver::itemsMoved(const KItemRange& range, const QList< const int movedRangeEnd = range.index + range.count; for (int i = range.index; i < movedRangeEnd; ++i) { - const int newIndex = movedToIndexes.at(i); + const int newIndex = movedToIndexes.at(i - range.index); newSizeHintCache[newIndex] = m_sizeHintCache.at(i); } @@ -139,8 +139,5 @@ void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QB void KItemListSizeHintResolver::clearCache() { - const int count = m_sizeHintCache.count(); - for (int i = 0; i < count; ++i) { - m_sizeHintCache[i] = QSizeF(); - } + m_sizeHintCache.fill(QSizeF()); } diff --git a/src/kitemviews/private/kitemlistviewlayouter.cpp b/src/kitemviews/private/kitemlistviewlayouter.cpp index da569b3dc..d6e78ae1a 100644 --- a/src/kitemviews/private/kitemlistviewlayouter.cpp +++ b/src/kitemviews/private/kitemlistviewlayouter.cpp @@ -375,12 +375,9 @@ void KItemListViewLayouter::doLayout() if (horizontalScrolling) { // Flip everything so that the layout logically can work like having // a vertical scrolling - itemSize.setWidth(m_itemSize.height()); - itemSize.setHeight(m_itemSize.width()); - itemMargin.setWidth(m_itemMargin.height()); - itemMargin.setHeight(m_itemMargin.width()); - size.setWidth(m_size.height()); - size.setHeight(m_size.width()); + itemSize.transpose(); + itemMargin.transpose(); + size.transpose(); if (grouped) { // In the horizontal scrolling case all groups are aligned @@ -411,7 +408,7 @@ void KItemListViewLayouter::doLayout() ++rowCount; } - m_itemInfos.reserve(itemCount); + m_itemInfos.resize(itemCount); qreal y = m_headerHeight + itemMargin.height(); int row = 0; @@ -458,18 +455,10 @@ void KItemListViewLayouter::doLayout() } } - const QRectF bounds(x, y, itemSize.width(), requiredItemHeight); - if (index < m_itemInfos.count()) { - m_itemInfos[index].rect = bounds; - m_itemInfos[index].column = column; - m_itemInfos[index].row = row; - } else { - ItemInfo itemInfo; - itemInfo.rect = bounds; - itemInfo.column = column; - itemInfo.row = row; - m_itemInfos.append(itemInfo); - } + ItemInfo& itemInfo = m_itemInfos[index]; + itemInfo.rect = QRectF(x, y, itemSize.width(), requiredItemHeight); + itemInfo.column = column; + itemInfo.row = row; if (grouped && horizontalScrolling) { // When grouping is enabled in the horizontal mode, the header alignment @@ -505,10 +494,6 @@ void KItemListViewLayouter::doLayout() y += maxItemHeight + itemMargin.height(); ++row; } - if (m_itemInfos.count() > itemCount) { - m_itemInfos.erase(m_itemInfos.begin() + itemCount, - m_itemInfos.end()); - } if (itemCount > 0) { // Calculate the maximum y-range of the last row for m_maximumScrollOffset |
