diff options
Diffstat (limited to 'src/kitemviews')
33 files changed, 1709 insertions, 475 deletions
diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index 1ffd5019b..fd01f2c4c 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -119,7 +119,7 @@ QStringList KFileItemListView::enabledPlugins() const return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList(); } -QPixmap KFileItemListView::createDragPixmap(const QSet<int>& indexes) const +QPixmap KFileItemListView::createDragPixmap(const KItemSet& indexes) const { if (!model()) { return QPixmap(); @@ -165,10 +165,8 @@ QPixmap KFileItemListView::createDragPixmap(const QSet<int>& indexes) const QPainter painter(&dragPixmap); int x = 0; int y = 0; - QSetIterator<int> it(indexes); - while (it.hasNext()) { - const int index = it.next(); + foreach (int index, indexes) { QPixmap pixmap = model()->data(index).value("iconPixmap").value<QPixmap>(); if (pixmap.isNull()) { KIcon icon(model()->data(index).value("iconName").toString()); @@ -221,12 +219,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/kfileitemlistview.h b/src/kitemviews/kfileitemlistview.h index bdc63b01b..2fd21edbf 100644 --- a/src/kitemviews/kfileitemlistview.h +++ b/src/kitemviews/kfileitemlistview.h @@ -73,7 +73,7 @@ public: QStringList enabledPlugins() const; /** @reimp */ - virtual QPixmap createDragPixmap(const QSet<int>& indexes) const; + virtual QPixmap createDragPixmap(const KItemSet& indexes) const; protected: virtual KItemListWidgetCreatorBase* defaultWidgetCreator() const; 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..4c8577543 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; @@ -232,7 +237,7 @@ bool KFileItemModel::showDirectoriesOnly() const return m_dirLister->dirOnlyMode(); } -QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const +QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const { QMimeData* data = new QMimeData(); @@ -243,9 +248,7 @@ QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const KUrl::List mostLocalUrls; bool canUseMostLocalUrls = true; - QSetIterator<int> it(indexes); - while (it.hasNext()) { - const int index = it.next(); + foreach (int index, indexes) { const KFileItem item = fileItem(index); if (!item.isNull()) { urls << item.targetUrl(); @@ -274,12 +277,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; } } @@ -435,15 +438,37 @@ bool KFileItemModel::setExpanded(int index, bool expanded) if (expanded) { m_expandedDirs.insert(targetUrl, url); m_dirLister->openUrl(url, KDirLister::Keep); + + const KUrl::List previouslyExpandedChildren = m_itemData.at(index)->values.value("previouslyExpandedChildren").value<KUrl::List>(); + foreach (const KUrl& url, previouslyExpandedChildren) { + m_urlsToExpand.insert(url); + } } else { 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; + + KUrl::List expandedChildren; + + int childIndex = firstChildIndex; + while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { + ItemData* itemData = m_itemData.at(childIndex); + if (itemData->values.value("isExpanded").toBool()) { + const KUrl targetUrl = itemData->item.targetUrl(); + m_expandedDirs.remove(targetUrl); + expandedChildren.append(targetUrl); + } + ++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); + + m_itemData.at(index)->values.insert("previouslyExpandedChildren", expandedChildren); } return true; @@ -460,7 +485,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 +495,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 +571,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 +608,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 +715,6 @@ void KFileItemModel::resortAllItems() oldUrls.append(itemData->item.url()); } - m_groups.clear(); m_items.clear(); // Resort the items @@ -694,20 +723,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 +875,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); } } + } + + std::sort(indexesToRemove.begin(), indexesToRemove.end()); - if (m_requestRole[ExpandedParentsCountRole]) { - removeFilteredChildren(itemsToRemove); + 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 +969,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); } @@ -983,6 +1034,14 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems) m_groups.clear(); + if (m_sortRole == NameRole && m_naturalSorting) { + // Natural sorting of items can be very slow. However, it becomes much + // faster if the input sequence is already mostly sorted. Therefore, we + // first sort 'newItems' according to the QStrings returned by + // KFileItem::text() using QString::operator<(), which is quite fast. + parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount()); + } + sort(newItems.begin(), newItems.end()); #ifdef KFILEITEMMODEL_DEBUG @@ -1056,53 +1115,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 +1144,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 +1162,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 +1192,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 +1470,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 +1494,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 +1515,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 +1725,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 +2008,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 +2134,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..c57329fc8 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -27,6 +27,7 @@ #include <kitemviews/private/kfileitemmodelfilter.h> #include <QHash> +#include <QSet> class KFileItemModelDirLister; class QTimer; @@ -99,7 +100,7 @@ public: bool showDirectoriesOnly() const; /** @reimp */ - virtual QMimeData* createMimeData(const QSet<int>& indexes) const; + virtual QMimeData* createMimeData(const KItemSet& indexes) const; /** @reimp */ virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const; @@ -309,7 +310,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 +320,8 @@ private: */ QList<ItemData*> createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const; + static int expandedParentsCount(const ItemData* data); + void removeExpandedItems(); /** @@ -348,6 +351,12 @@ private: QHash<QByteArray, QVariant> retrieveData(const KFileItem& item, const ItemData* parent) const; /** + * @return True if \a a has a KFileItem whose text is 'less than' the one + * of \a b according to QString::operator<(const QString&). + */ + static bool nameLessThan(const ItemData* a, const ItemData* b); + + /** * @return True if the item-data \a a should be ordered before the item-data * \b. The item-data may have different parent-items. */ @@ -388,11 +397,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 +411,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 @@ -488,9 +492,19 @@ private: friend class DolphinPart; // Accesses m_dirLister }; +inline bool KFileItemModel::nameLessThan(const ItemData* a, const ItemData* b) +{ + return a->item.text() < b->item.text(); +} + + 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/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 158c12de1..7344b9960 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -364,11 +364,11 @@ bool KItemListController::keyPressEvent(QKeyEvent* event) case Qt::Key_Enter: case Qt::Key_Return: { - const QSet<int> selectedItems = m_selectionManager->selectedItems(); + const KItemSet selectedItems = m_selectionManager->selectedItems(); if (selectedItems.count() >= 2) { emit itemsActivated(selectedItems); } else if (selectedItems.count() == 1) { - emit itemActivated(selectedItems.toList().first()); + emit itemActivated(selectedItems.first()); } else { emit itemActivated(index); } @@ -378,14 +378,14 @@ bool KItemListController::keyPressEvent(QKeyEvent* event) case Qt::Key_Menu: { // Emit the signal itemContextMenuRequested() in case if at least one // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted. - const QSet<int> selectedItems = m_selectionManager->selectedItems(); + const KItemSet selectedItems = m_selectionManager->selectedItems(); int index = -1; if (selectedItems.count() >= 2) { const int currentItemIndex = m_selectionManager->currentItem(); index = selectedItems.contains(currentItemIndex) - ? currentItemIndex : selectedItems.toList().first(); + ? currentItemIndex : selectedItems.first(); } else if (selectedItems.count() == 1) { - index = selectedItems.toList().first(); + index = selectedItems.first(); } if (index >= 0) { @@ -403,6 +403,7 @@ bool KItemListController::keyPressEvent(QKeyEvent* event) m_selectionManager->clearSelection(); } m_keyboardManager->cancelSearch(); + emit escapePressed(); break; case Qt::Key_Space: @@ -1086,7 +1087,7 @@ void KItemListController::slotRubberBandChanged() } } - QSet<int> selectedItems; + KItemSet selectedItems; // Select all visible items that intersect with the rubberband foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) { @@ -1134,7 +1135,7 @@ void KItemListController::slotRubberBandChanged() // Therefore, the new selection contains: // 1. All previously selected items which are not inside the rubberband, and // 2. all items inside the rubberband which have not been selected previously. - m_selectionManager->setSelectedItems((m_oldSelection - selectedItems) + (selectedItems - m_oldSelection)); + m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems); } else { m_selectionManager->setSelectedItems(selectedItems + m_oldSelection); @@ -1147,7 +1148,7 @@ void KItemListController::startDragging() return; } - const QSet<int> selectedItems = m_selectionManager->selectedItems(); + const KItemSet selectedItems = m_selectionManager->selectedItems(); if (selectedItems.isEmpty()) { return; } diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index 4d5fee345..e9b70cdda 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -25,10 +25,11 @@ #include <libdolphin_export.h> +#include "kitemset.h" + #include <QObject> #include <QPixmap> #include <QPointF> -#include <QSet> class KItemModelBase; class KItemListKeyboardSearchManager; @@ -129,7 +130,7 @@ public: /** * If set to true, the signals itemActivated() and itemsActivated() are emitted - * after a single-click of the left mouse button. If set to false (the default), + * after a single-click of the left mouse button. If set to false (the default), * the setting from KGlobalSettings::singleClick() is used. */ void setSingleClickActivationEnforced(bool singleClick); @@ -165,7 +166,7 @@ signals: * Is emitted if more than one item has been activated by pressing Return/Enter * when having a selection. */ - void itemsActivated(const QSet<int>& indexes); + void itemsActivated(const KItemSet& indexes); void itemMiddleClicked(int index); @@ -230,6 +231,11 @@ signals: */ void aboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event); + /** + * Is emitted if the Escape key is pressed. + */ + void escapePressed(); + void modelChanged(KItemModelBase* current, KItemModelBase* previous); void viewChanged(KItemListView* current, KItemListView* previous); @@ -321,7 +327,7 @@ private: * the current selection it is remembered in m_oldSelection before the * rubberband gets activated. */ - QSet<int> m_oldSelection; + KItemSet m_oldSelection; /** * Assuming a view is given with a vertical scroll-orientation, grouped items and diff --git a/src/kitemviews/kitemlistselectionmanager.cpp b/src/kitemviews/kitemlistselectionmanager.cpp index 833f7aad0..ebff1a30e 100644 --- a/src/kitemviews/kitemlistselectionmanager.cpp +++ b/src/kitemviews/kitemlistselectionmanager.cpp @@ -43,7 +43,7 @@ KItemListSelectionManager::~KItemListSelectionManager() void KItemListSelectionManager::setCurrentItem(int current) { const int previous = m_currentItem; - const QSet<int> previousSelection = selectedItems(); + const KItemSet previousSelection = selectedItems(); if (m_model && current >= 0 && current < m_model->count()) { m_currentItem = current; @@ -55,7 +55,7 @@ void KItemListSelectionManager::setCurrentItem(int current) emit currentChanged(m_currentItem, previous); if (m_isAnchoredSelectionActive) { - const QSet<int> selection = selectedItems(); + const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } @@ -68,18 +68,18 @@ int KItemListSelectionManager::currentItem() const return m_currentItem; } -void KItemListSelectionManager::setSelectedItems(const QSet<int>& items) +void KItemListSelectionManager::setSelectedItems(const KItemSet& items) { if (m_selectedItems != items) { - const QSet<int> previous = m_selectedItems; + const KItemSet previous = m_selectedItems; m_selectedItems = items; emit selectionChanged(m_selectedItems, previous); } } -QSet<int> KItemListSelectionManager::selectedItems() const +KItemSet KItemListSelectionManager::selectedItems() const { - QSet<int> selectedItems = m_selectedItems; + KItemSet selectedItems = m_selectedItems; if (m_isAnchoredSelectionActive && m_anchorItem != m_currentItem) { Q_ASSERT(m_anchorItem >= 0); @@ -127,7 +127,7 @@ void KItemListSelectionManager::setSelected(int index, int count, SelectionMode } endAnchoredSelection(); - const QSet<int> previous = selectedItems(); + const KItemSet previous = selectedItems(); count = qMin(count, m_model->count() - index); @@ -160,7 +160,7 @@ void KItemListSelectionManager::setSelected(int index, int count, SelectionMode break; } - const QSet<int> selection = selectedItems(); + const KItemSet selection = selectedItems(); if (selection != previous) { emit selectionChanged(selection, previous); } @@ -168,11 +168,11 @@ void KItemListSelectionManager::setSelected(int index, int count, SelectionMode void KItemListSelectionManager::clearSelection() { - const QSet<int> previous = selectedItems(); + const KItemSet previous = selectedItems(); if (!previous.isEmpty()) { m_selectedItems.clear(); m_isAnchoredSelectionActive = false; - emit selectionChanged(QSet<int>(), previous); + emit selectionChanged(KItemSet(), previous); } } @@ -221,7 +221,7 @@ void KItemListSelectionManager::setModel(KItemModelBase* model) void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) { // Store the current selection (needed in the selectionChanged() signal) - const QSet<int> previousSelection = selectedItems(); + const KItemSet previousSelection = selectedItems(); // Update the current item if (m_currentItem < 0) { @@ -257,12 +257,10 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) // Update the selections if (!m_selectedItems.isEmpty()) { - const QSet<int> previous = m_selectedItems; + const KItemSet previous = m_selectedItems; m_selectedItems.clear(); - m_selectedItems.reserve(previous.count()); - QSetIterator<int> it(previous); - while (it.hasNext()) { - const int index = it.next(); + + foreach (int index, previous) { int inc = 0; foreach (const KItemRange& itemRange, itemRanges) { if (index < itemRange.index) { @@ -274,7 +272,7 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) } } - const QSet<int> selection = selectedItems(); + const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } @@ -283,7 +281,7 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) { // Store the current selection (needed in the selectionChanged() signal) - const QSet<int> previousSelection = selectedItems(); + const KItemSet previousSelection = selectedItems(); const int previousCurrent = m_currentItem; // Update the current item @@ -308,19 +306,18 @@ void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) // Update the selections and the anchor item if (!m_selectedItems.isEmpty()) { - const QSet<int> previous = m_selectedItems; + const KItemSet previous = m_selectedItems; m_selectedItems.clear(); - m_selectedItems.reserve(previous.count()); - QSetIterator<int> it(previous); - while (it.hasNext()) { - const int index = indexAfterRangesRemoving(it.next(), itemRanges, DiscardRemovedIndex); + + foreach (int oldIndex, previous) { + const int index = indexAfterRangesRemoving(oldIndex, itemRanges, DiscardRemovedIndex); if (index >= 0) { m_selectedItems.insert(index); } } } - const QSet<int> selection = selectedItems(); + const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } @@ -332,7 +329,7 @@ void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) void KItemListSelectionManager::itemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes) { // Store the current selection (needed in the selectionChanged() signal) - const QSet<int> previousSelection = selectedItems(); + const KItemSet previousSelection = selectedItems(); // Update the current item if (m_currentItem >= itemRange.index && m_currentItem < itemRange.index + itemRange.count) { @@ -352,12 +349,10 @@ void KItemListSelectionManager::itemsMoved(const KItemRange& itemRange, const QL // Update the selections if (!m_selectedItems.isEmpty()) { - const QSet<int> previous = m_selectedItems; + const KItemSet previous = m_selectedItems; m_selectedItems.clear(); - m_selectedItems.reserve(previous.count()); - QSetIterator<int> it(previous); - while (it.hasNext()) { - const int index = it.next(); + + foreach (int index, previous) { if (index >= itemRange.index && index < itemRange.index + itemRange.count) { m_selectedItems.insert(movedToIndexes.at(index - itemRange.index)); } @@ -367,7 +362,7 @@ void KItemListSelectionManager::itemsMoved(const KItemRange& itemRange, const QL } } - const QSet<int> selection = selectedItems(); + const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } diff --git a/src/kitemviews/kitemlistselectionmanager.h b/src/kitemviews/kitemlistselectionmanager.h index c89b8a4b8..c4decd39e 100644 --- a/src/kitemviews/kitemlistselectionmanager.h +++ b/src/kitemviews/kitemlistselectionmanager.h @@ -26,9 +26,9 @@ #include <libdolphin_export.h> #include <kitemviews/kitemmodelbase.h> +#include <kitemviews/kitemset.h> #include <QObject> -#include <QSet> class KItemModelBase; @@ -57,8 +57,8 @@ public: void setCurrentItem(int current); int currentItem() const; - void setSelectedItems(const QSet<int>& items); - QSet<int> selectedItems() const; + void setSelectedItems(const KItemSet& items); + KItemSet selectedItems() const; bool isSelected(int index) const; bool hasSelection() const; @@ -73,7 +73,7 @@ public: signals: void currentChanged(int current, int previous); - void selectionChanged(const QSet<int>& current, const QSet<int>& previous); + void selectionChanged(const KItemSet& current, const KItemSet& previous); private: void setModel(KItemModelBase* model); @@ -91,7 +91,7 @@ private: private: int m_currentItem; int m_anchorItem; - QSet<int> m_selectedItems; + KItemSet m_selectedItems; bool m_isAnchoredSelectionActive; KItemModelBase* m_model; diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index a66715090..7f497210c 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -609,12 +609,12 @@ KItemListHeader* KItemListView::header() const return m_header; } -QPixmap KItemListView::createDragPixmap(const QSet<int>& indexes) const +QPixmap KItemListView::createDragPixmap(const KItemSet& indexes) const { QPixmap pixmap; if (indexes.count() == 1) { - KItemListWidget* item = m_visibleItems.value(indexes.toList().first()); + KItemListWidget* item = m_visibleItems.value(indexes.first()); QGraphicsView* graphicsView = scene()->views()[0]; if (item && graphicsView) { pixmap = item->createDragPixmap(0, graphicsView); @@ -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; @@ -1298,7 +1305,7 @@ void KItemListView::slotCurrentChanged(int current, int previous) QAccessible::updateAccessibility(this, current+1, QAccessible::Focus); } -void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous) +void KItemListView::slotSelectionChanged(const KItemSet& current, const KItemSet& previous) { Q_UNUSED(previous); @@ -1495,7 +1502,7 @@ void KItemListView::setController(KItemListController* controller) if (previous) { KItemListSelectionManager* selectionManager = previous->selectionManager(); disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int))); - disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>))); + disconnect(selectionManager, SIGNAL(selectionChanged(KItemSet,KItemSet)), this, SLOT(slotSelectionChanged(KItemSet,KItemSet))); } m_controller = controller; @@ -1503,7 +1510,7 @@ void KItemListView::setController(KItemListController* controller) if (controller) { KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int))); - connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>))); + connect(selectionManager, SIGNAL(selectionChanged(KItemSet,KItemSet)), this, SLOT(slotSelectionChanged(KItemSet,KItemSet))); } onControllerChanged(controller, previous); @@ -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..dbe923086 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -271,7 +271,7 @@ public: * @return Pixmap that is used for a drag operation based on the * items given by \a indexes. */ - virtual QPixmap createDragPixmap(const QSet<int>& indexes) const; + virtual QPixmap createDragPixmap(const KItemSet& indexes) const; /** * Lets the user edit the role \a role for item with the index \a index. @@ -394,12 +394,13 @@ 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); virtual void slotSortRoleChanged(const QByteArray& current, const QByteArray& previous); virtual void slotCurrentChanged(int current, int previous); - virtual void slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous); + virtual void slotSelectionChanged(const KItemSet& current, const KItemSet& previous); private slots: void slotAnimationFinished(QGraphicsWidget* widget, diff --git a/src/kitemviews/kitemlistviewaccessible.cpp b/src/kitemviews/kitemlistviewaccessible.cpp index 565c2151e..d9ddd58f8 100644 --- a/src/kitemviews/kitemlistviewaccessible.cpp +++ b/src/kitemviews/kitemlistviewaccessible.cpp @@ -109,7 +109,7 @@ int KItemListViewAccessible::rowCount() const int KItemListViewAccessible::selectedCellCount() const { - return view()->controller()->selectionManager()->selectedItems().size(); + return view()->controller()->selectionManager()->selectedItems().count(); } int KItemListViewAccessible::selectedColumnCount() const diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index c13c9f88c..4312640e4 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), @@ -134,7 +123,7 @@ int KItemModelBase::expandedParentsCount(int index) const return 0; } -QMimeData* KItemModelBase::createMimeData(const QSet<int>& indexes) const +QMimeData* KItemModelBase::createMimeData(const KItemSet& indexes) const { Q_UNUSED(indexes); return 0; diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h index 70f688390..283cfa552 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -25,23 +25,15 @@ #include <libdolphin_export.h> +#include <kitemviews/kitemrange.h> +#include <kitemviews/kitemset.h> + #include <QHash> #include <QObject> -#include <QSet> #include <QVariant> 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. * @@ -157,7 +149,7 @@ public: * caller of this method. The method must be implemented if dragging of * items should be possible. */ - virtual QMimeData* createMimeData(const QSet<int>& indexes) const; + virtual QMimeData* createMimeData(const KItemSet& indexes) const; /** * @return Reimplement this to return the index for the first item @@ -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/kitemset.cpp b/src/kitemviews/kitemset.cpp new file mode 100644 index 000000000..f855368c1 --- /dev/null +++ b/src/kitemviews/kitemset.cpp @@ -0,0 +1,348 @@ +/*************************************************************************** + * 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 "kitemset.h" + +#include <QVector> + +#include <algorithm> + +KItemSet::iterator KItemSet::insert(int i) +{ + if (m_itemRanges.empty()) { + m_itemRanges.push_back(KItemRange(i, 1)); + return iterator(m_itemRanges.begin(), 0); + } + + KItemRangeList::iterator rangeBegin = m_itemRanges.begin(); + if (i < rangeBegin->index) { + // The inserted index is smaller than all existing items. + if (i == rangeBegin->index - 1) { + // Move the beginning of the first range one item to the front. + --rangeBegin->index; + ++rangeBegin->count; + } else { + // Insert a new range at the beginning. + rangeBegin = m_itemRanges.insert(rangeBegin, KItemRange(i, 1)); + } + + return iterator(rangeBegin, 0); + } + + KItemRangeList::iterator rangeEnd = m_itemRanges.end(); + KItemRangeList::iterator lastRange = rangeEnd - 1; + if (i >= lastRange->index) { + // i either belongs to the last range, or it is larger than all existing items. + const int lastItemPlus1 = lastRange->index + lastRange->count; + if (i == lastItemPlus1) { + // Move the end of the last range one item to the back. + ++lastRange->count; + } else if (i > lastItemPlus1) { + // Append a new range. + lastRange = m_itemRanges.insert(rangeEnd, KItemRange(i, 1)); + } + + return iterator(lastRange, i - lastRange->index); + } + + // We know that i is between the smallest existing item and the first item + // of the last range. Find the lowest range whose 'index' is smaller than i. + KItemRangeList::iterator low = rangeBegin; + KItemRangeList::iterator high = lastRange; + + while (low + 1 != high) { + const int span = high - low; + Q_ASSERT(span >= 2); + + KItemRangeList::iterator mid = low + span / 2; + if (mid->index > i) { + high = mid; + } else { + low = mid; + } + } + + Q_ASSERT(low->index <= i && high->index > i); + + if (i == low->index + low->count) { + // i is just one item behind the range low. + if (i == high->index - 1) { + // i closes the gap between low and high. Merge the two ranges. + const int newRangeCount = low->count + 1 + high->count; + KItemRangeList::iterator behindNewRange = m_itemRanges.erase(high); + KItemRangeList::iterator newRange = behindNewRange - 1; + newRange->count = newRangeCount; + return iterator(newRange, i - newRange->index); + } else { + // Extend low by one item. + ++low->count; + return iterator(low, low->count - 1); + } + } else if (i > low->index + low->count) { + if (i == high->index - 1) { + // Extend high by one item to the front. + --high->index; + ++high->count; + return iterator(high, 0); + } else { + // Insert a new range between low and high. + KItemRangeList::iterator newRange = m_itemRanges.insert(high, KItemRange(i, 1)); + return iterator(newRange, 0); + } + } else { + // The range low already contains i. + return iterator(low, i - low->index); + } +} + +KItemSet::iterator KItemSet::erase(iterator it) +{ + KItemRangeList::iterator rangeIt = it.m_rangeIt; + + if (it.m_offset == 0) { + // Removed index is at the beginning of a range. + if (rangeIt->count > 1) { + ++rangeIt->index; + --rangeIt->count; + } else { + // The range only contains the removed index. + rangeIt = m_itemRanges.erase(rangeIt); + } + return iterator(rangeIt, 0); + } else if (it.m_offset == rangeIt->count - 1) { + // Removed index is at the end of a range. + --rangeIt->count; + ++rangeIt; + return iterator(rangeIt, 0); + } else { + // The removed index is in the middle of a range. + const int newRangeIndex = *it + 1; + const int newRangeCount = rangeIt->count - it.m_offset - 1; + const KItemRange newRange(newRangeIndex, newRangeCount); + + rangeIt->count = it.m_offset; + ++rangeIt; + rangeIt = m_itemRanges.insert(rangeIt, newRange); + + return iterator(rangeIt, 0); + } +} + +KItemSet KItemSet::operator+(const KItemSet& other) const +{ + KItemSet sum; + + KItemRangeList::const_iterator it1 = m_itemRanges.constBegin(); + KItemRangeList::const_iterator it2 = other.m_itemRanges.constBegin(); + + const KItemRangeList::const_iterator end1 = m_itemRanges.constEnd(); + const KItemRangeList::const_iterator end2 = other.m_itemRanges.constEnd(); + + while (it1 != end1 || it2 != end2) { + if (it1 == end1) { + // We are past the end of 'this' already. Append all remaining + // item ranges from 'other'. + while (it2 != end2) { + sum.m_itemRanges.append(*it2); + ++it2; + } + } else if (it2 == end2) { + // We are past the end of 'other' already. Append all remaining + // item ranges from 'this'. + while (it1 != end1) { + sum.m_itemRanges.append(*it1); + ++it1; + } + } else { + // Find the beginning of the next range. + int index = qMin(it1->index, it2->index); + int count = 0; + + do { + if (it1 != end1 && it1->index <= index + count) { + // The next range from 'this' overlaps with the current range in the sum. + count = qMax(count, it1->index + it1->count - index); + ++it1; + } + + if (it2 != end2 && it2->index <= index + count) { + // The next range from 'other' overlaps with the current range in the sum. + count = qMax(count, it2->index + it2->count - index); + ++it2; + } + } while ((it1 != end1 && it1->index <= index + count) + || (it2 != end2 && it2->index <= index + count)); + + sum.m_itemRanges.append(KItemRange(index, count)); + } + } + + return sum; +} + +KItemSet KItemSet::operator^(const KItemSet& other) const +{ + // We are looking for all ints which are either in *this or in other, + // but not in both. + KItemSet result; + + // When we go through all integers from INT_MIN to INT_MAX and start + // in the state "do not add to result", every beginning/end of a range + // of *this and other toggles the "add/do not add to result" state. + // Therefore, we just have to put ints where any range starts/ends to + // a sorted array, and then we can calculate the result quite easily. + QVector<int> rangeBoundaries; + rangeBoundaries.resize(2 * (m_itemRanges.count() + other.m_itemRanges.count())); + const QVector<int>::iterator begin = rangeBoundaries.begin(); + const QVector<int>::iterator end = rangeBoundaries.end(); + QVector<int>::iterator it = begin; + + foreach (const KItemRange& range, m_itemRanges) { + *it++ = range.index; + *it++ = range.index + range.count; + } + + const QVector<int>::iterator middle = it; + + foreach (const KItemRange& range, other.m_itemRanges) { + *it++ = range.index; + *it++ = range.index + range.count; + } + Q_ASSERT(it == end); + + std::inplace_merge(begin, middle, end); + + it = begin; + while (it != end) { + const int rangeBegin = *it; + ++it; + + if (*it == rangeBegin) { + // It seems that ranges from both *this and other start at + // rangeBegin. Do not start a new range, but read the next int. + // + // Example: Consider the symmetric difference of the sets + // {1, 2, 3, 4} and {1, 2}. The sorted list of range boundaries is + // 1 1 3 5. Discarding the duplicate 1 yields the result + // rangeBegin = 3, rangeEnd = 5, which corresponds to the set {3, 4}. + ++it; + } else { + // The end of the current range is the next *single* int that we + // find. If an int appears twice in rangeBoundaries, the range does + // not end. + // + // Example: Consider the symmetric difference of the sets + // {1, 2, 3, 4, 8, 9, 10} and {5, 6, 7}. The sorted list of range + // boundaries is 1 5 5 8 8 11, and discarding all duplicates yields + // the result rangeBegin = 1, rangeEnd = 11, which corresponds to + // the set {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}. + bool foundEndOfRange = false; + int rangeEnd; + do { + rangeEnd = *it; + ++it; + + if (it == end || *it != rangeEnd) { + foundEndOfRange = true; + } else { + ++it; + } + } while (!foundEndOfRange); + + result.m_itemRanges.append(KItemRange(rangeBegin, rangeEnd - rangeBegin)); + } + } + + return result; +} + +bool KItemSet::isValid() const +{ + const KItemRangeList::const_iterator begin = m_itemRanges.constBegin(); + const KItemRangeList::const_iterator end = m_itemRanges.constEnd(); + + for (KItemRangeList::const_iterator it = begin; it != end; ++it) { + if (it->count <= 0) { + return false; + } + + if (it != begin) { + const KItemRangeList::const_iterator previous = it - 1; + if (previous->index + previous->count >= it->index) { + return false; + } + } + } + + return true; +} + +KItemRangeList::iterator KItemSet::rangeForItem(int i) +{ + const KItemRangeList::iterator end = m_itemRanges.end(); + KItemRangeList::iterator low = m_itemRanges.begin(); + KItemRangeList::iterator high = end; + + if (low == end || low->index > i) { + return end; + } + + while (low != high && low + 1 != high) { + KItemRangeList::iterator mid = low + (high - low) / 2; + if (mid->index > i) { + high = mid; + } else { + low = mid; + } + } + + Q_ASSERT(low->index <= i); + if (low->index + low->count > i) { + return low; + } + + return end; +} + +KItemRangeList::const_iterator KItemSet::constRangeForItem(int i) const +{ + const KItemRangeList::const_iterator end = m_itemRanges.constEnd(); + KItemRangeList::const_iterator low = m_itemRanges.constBegin(); + KItemRangeList::const_iterator high = end; + + if (low == end || low->index > i) { + return end; + } + + while (low != high && low + 1 != high) { + KItemRangeList::const_iterator mid = low + (high - low) / 2; + if (mid->index > i) { + high = mid; + } else { + low = mid; + } + } + + Q_ASSERT(low->index <= i); + if (low->index + low->count > i) { + return low; + } + + return end; +} diff --git a/src/kitemviews/kitemset.h b/src/kitemviews/kitemset.h new file mode 100644 index 000000000..385010f7d --- /dev/null +++ b/src/kitemviews/kitemset.h @@ -0,0 +1,413 @@ +/*************************************************************************** + * 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 KITEMSET_H +#define KITEMSET_H + +#include <kitemviews/kitemrange.h> + +/** + * @brief Stores a set of integer numbers in a space-efficient way. + * + * This class is similar to QSet<int>, but it has the following advantages: + * + * 1. It uses less memory than a QSet<int> if many consecutive numbers are + * stored. This is achieved by not storing each number separately, but + * "ranges" of numbers. + * + * Example: The set {1, 2, 3, 4, 5} is represented by a single range which + * starts at 1 and has the length 5. + * + * 2. When iterating through a KItemSet using KItemSet::iterator or + * KItemSet::const_iterator, the numbers are traversed in ascending order. + * + * The complexity of most operations depends on the number of ranges. + */ + +class KItemSet +{ +public: + KItemSet(); + KItemSet(const KItemSet& other); + + /** + * Returns the number of items in the set. + * Complexity: O(log(number of ranges)). + */ + int count() const; + + bool isEmpty() const; + void clear(); + + bool operator==(const KItemSet& other) const; + bool operator!=(const KItemSet& other) const; + + class iterator + { + iterator(const KItemRangeList::iterator& rangeIt, int offset) : + m_rangeIt(rangeIt), + m_offset(offset) + { + } + + public: + iterator(const iterator& other) : + m_rangeIt(other.m_rangeIt), + m_offset(other.m_offset) + { + } + + iterator& operator=(const iterator& other) + { + m_rangeIt = other.m_rangeIt; + m_offset = other.m_offset; + return *this; + } + + int operator*() const + { + return m_rangeIt->index + m_offset; + } + + inline bool operator==(const iterator& other) const + { + return m_rangeIt == other.m_rangeIt && m_offset == other.m_offset; + } + + inline bool operator!=(const iterator& other) const + { + return !(*this == other); + } + + inline iterator& operator++() + { + ++m_offset; + + if (m_offset == m_rangeIt->count) { + ++m_rangeIt; + m_offset = 0; + } + + return *this; + } + + inline iterator operator++(int) + { + iterator r = *this; + ++(*this); + return r; + } + + inline iterator& operator--() + { + if (m_offset == 0) { + --m_rangeIt; + m_offset = m_rangeIt->count - 1; + } else { + --m_offset; + } + + return *this; + } + + inline iterator operator--(int) + { + iterator r = *this; + --(*this); + return r; + } + + private: + KItemRangeList::iterator m_rangeIt; + int m_offset; + + friend class const_iterator; + friend class KItemSet; + }; + + + class const_iterator + { + const_iterator(KItemRangeList::const_iterator rangeIt, int offset) : + m_rangeIt(rangeIt), + m_offset(offset) + { + } + + public: + const_iterator(const const_iterator& other) : + m_rangeIt(other.m_rangeIt), + m_offset(other.m_offset) + { + } + + const_iterator(const iterator& other) : + m_rangeIt(other.m_rangeIt), + m_offset(other.m_offset) + { + } + + const_iterator& operator=(const const_iterator& other) + { + m_rangeIt = other.m_rangeIt; + m_offset = other.m_offset; + return *this; + } + + int operator*() const + { + return m_rangeIt->index + m_offset; + } + + inline bool operator==(const const_iterator& other) const + { + return m_rangeIt == other.m_rangeIt && m_offset == other.m_offset; + } + + inline bool operator!=(const const_iterator& other) const + { + return !(*this == other); + } + + inline const_iterator& operator++() + { + ++m_offset; + + if (m_offset == m_rangeIt->count) { + ++m_rangeIt; + m_offset = 0; + } + + return *this; + } + + inline const_iterator operator++(int) + { + const_iterator r = *this; + ++(*this); + return r; + } + + inline const_iterator& operator--() + { + if (m_offset == 0) { + --m_rangeIt; + m_offset = m_rangeIt->count - 1; + } else { + --m_offset; + } + + return *this; + } + + inline const_iterator operator--(int) + { + const_iterator r = *this; + --(*this); + return r; + } + + private: + KItemRangeList::const_iterator m_rangeIt; + int m_offset; + + friend class KItemSet; + }; + + iterator begin(); + const_iterator begin() const; + const_iterator constBegin() const; + iterator end(); + const_iterator end() const; + const_iterator constEnd() const; + + int first() const; + int last() const; + + bool contains(int i) const; + iterator insert(int i); + iterator find(int i); + const_iterator constFind(int i) const; + bool remove(int i); + iterator erase(iterator it); + + /** + * Returns a new set which contains all items that are contained in this + * KItemSet, in \a other, or in both. + */ + KItemSet operator+(const KItemSet& other) const; + + /** + * Returns a new set which contains all items that are contained either in + * this KItemSet, or in \a other, but not in both (the symmetric difference + * of both KItemSets). + */ + KItemSet operator^(const KItemSet& other) const; + + KItemSet& operator<<(int i); + +private: + /** + * Returns true if the KItemSet is valid, and false otherwise. + * A valid KItemSet must store the item ranges in ascending order, and + * the ranges must not overlap. + */ + bool isValid() const; + + /** + * This function returns an iterator that points to the KItemRange which + * contains i, or m_itemRanges.end() if no such range exists. + */ + KItemRangeList::iterator rangeForItem(int i); + + /** + * This function returns an iterator that points to the KItemRange which + * contains i, or m_itemRanges.constEnd() if no such range exists. + */ + KItemRangeList::const_iterator constRangeForItem(int i) const; + + KItemRangeList m_itemRanges; + + friend class KItemSetTest; +}; + +inline KItemSet::KItemSet() : + m_itemRanges() +{ +} + +inline KItemSet::KItemSet(const KItemSet& other) : + m_itemRanges(other.m_itemRanges) +{ +} + +inline int KItemSet::count() const +{ + int result = 0; + foreach (const KItemRange& range, m_itemRanges) { + result += range.count; + } + return result; +} + +inline bool KItemSet::isEmpty() const +{ + return m_itemRanges.isEmpty(); +} + +inline void KItemSet::clear() +{ + m_itemRanges.clear(); +} + +inline bool KItemSet::operator==(const KItemSet& other) const +{ + return m_itemRanges == other.m_itemRanges; +} + +inline bool KItemSet::operator!=(const KItemSet& other) const +{ + return m_itemRanges != other.m_itemRanges; +} + +inline bool KItemSet::contains(int i) const +{ + const KItemRangeList::const_iterator it = constRangeForItem(i); + return it != m_itemRanges.end(); +} + +inline KItemSet::iterator KItemSet::find(int i) +{ + const KItemRangeList::iterator it = rangeForItem(i); + if (it != m_itemRanges.end()) { + return iterator(it, i - it->index); + } else { + return end(); + } +} + +inline KItemSet::const_iterator KItemSet::constFind(int i) const +{ + const KItemRangeList::const_iterator it = constRangeForItem(i); + if (it != m_itemRanges.constEnd()) { + return const_iterator(it, i - it->index); + } else { + return constEnd(); + } +} + +inline bool KItemSet::remove(int i) +{ + iterator it = find(i); + if (it != end()) { + erase(it); + return true; + } else { + return false; + } +} + +inline KItemSet::iterator KItemSet::begin() +{ + return iterator(m_itemRanges.begin(), 0); +} + +inline KItemSet::const_iterator KItemSet::begin() const +{ + return const_iterator(m_itemRanges.begin(), 0); +} + +inline KItemSet::const_iterator KItemSet::constBegin() const +{ + return const_iterator(m_itemRanges.constBegin(), 0); +} + +inline KItemSet::iterator KItemSet::end() +{ + return iterator(m_itemRanges.end(), 0); +} + +inline KItemSet::const_iterator KItemSet::end() const +{ + return const_iterator(m_itemRanges.end(), 0); +} + +inline KItemSet::const_iterator KItemSet::constEnd() const +{ + return const_iterator(m_itemRanges.constEnd(), 0); +} + +inline int KItemSet::first() const +{ + return m_itemRanges.first().index; +} + +inline int KItemSet::last() const +{ + const KItemRange& lastRange = m_itemRanges.last(); + return lastRange.index + lastRange.count - 1; +} + +inline KItemSet& KItemSet::operator<<(int i) +{ + insert(i); + return *this; +} + +#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/kstandarditemmodel.cpp b/src/kitemviews/kstandarditemmodel.cpp index 959d62cb8..e8c1b6204 100644 --- a/src/kitemviews/kstandarditemmodel.cpp +++ b/src/kitemviews/kstandarditemmodel.cpp @@ -175,7 +175,7 @@ bool KStandardItemModel::setData(int index, const QHash<QByteArray, QVariant>& v return true; } -QMimeData* KStandardItemModel::createMimeData(const QSet<int>& indexes) const +QMimeData* KStandardItemModel::createMimeData(const KItemSet& indexes) const { Q_UNUSED(indexes); return 0; diff --git a/src/kitemviews/kstandarditemmodel.h b/src/kitemviews/kstandarditemmodel.h index 0debd6a6f..721e15529 100644 --- a/src/kitemviews/kstandarditemmodel.h +++ b/src/kitemviews/kstandarditemmodel.h @@ -72,7 +72,7 @@ public: virtual int count() const; virtual QHash<QByteArray, QVariant> data(int index) const; virtual bool setData(int index, const QHash<QByteArray, QVariant>& values); - virtual QMimeData* createMimeData(const QSet<int>& indexes) const; + virtual QMimeData* createMimeData(const KItemSet& indexes) const; virtual int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const; virtual bool supportsDropping(int index) const; virtual QString roleDescription(const QByteArray& role) const; 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..f5f63d5ab 100644 --- a/src/kitemviews/private/kitemlistviewlayouter.cpp +++ b/src/kitemviews/private/kitemlistviewlayouter.cpp @@ -239,20 +239,30 @@ QRectF KItemListViewLayouter::itemRect(int index) const return QRectF(); } + QSizeF sizeHint; + if (m_sizeHintResolver) { + sizeHint = m_sizeHintResolver->sizeHint(index); + } else { + sizeHint = m_itemSize; + } + if (m_scrollOrientation == Qt::Horizontal) { // Rotate the logical direction which is always vertical by 90° // to get the physical horizontal direction - const QRectF& b = m_itemInfos[index].rect; - QRectF bounds(b.y(), b.x(), b.height(), b.width()); - QPointF pos = bounds.topLeft(); + const QPointF logicalPos = m_itemInfos[index].pos; + QPointF pos(logicalPos.y(), logicalPos.x()); pos.rx() -= m_scrollOffset; - bounds.moveTo(pos); - return bounds; + return QRectF(pos, sizeHint); + } + + if (sizeHint.width() <= 0) { + // In Details View, a size hint with negative width is used internally. + sizeHint.rwidth() = m_itemSize.width(); } - QRectF bounds = m_itemInfos[index].rect; - bounds.moveTo(bounds.topLeft() - QPointF(m_itemOffset, m_scrollOffset)); - return bounds; + QPointF pos = m_itemInfos[index].pos; + pos -= QPointF(m_itemOffset, m_scrollOffset); + return QRectF(pos, sizeHint); } QRectF KItemListViewLayouter::groupHeaderRect(int index) const @@ -278,23 +288,30 @@ QRectF KItemListViewLayouter::groupHeaderRect(int index) const // current column. As the scroll-direction is // Qt::Horizontal and m_itemRects is accessed directly, // the logical height represents the visual width. - qreal width = minimumGroupHeaderWidth(); - const qreal y = m_itemInfos[index].rect.y(); + qreal headerWidth = minimumGroupHeaderWidth(); + const qreal y = m_itemInfos[index].pos.y(); const int maxIndex = m_itemInfos.count() - 1; while (index <= maxIndex) { - QRectF bounds = m_itemInfos[index].rect; - if (bounds.y() != y) { + const QPointF pos = m_itemInfos[index].pos; + if (pos.y() != y) { break; } - if (bounds.height() > width) { - width = bounds.height(); + qreal itemWidth; + if (m_sizeHintResolver) { + itemWidth = m_sizeHintResolver->sizeHint(index).width(); + } else { + itemWidth = m_itemSize.width(); + } + + if (itemWidth > headerWidth) { + headerWidth = itemWidth; } ++index; } - size = QSizeF(width, m_size.height()); + size = QSizeF(headerWidth, m_size.height()); } return QRectF(pos, size); } @@ -375,12 +392,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 @@ -406,12 +420,7 @@ void KItemListViewLayouter::doLayout() } } - int rowCount = itemCount / m_columnCount; - if (itemCount % m_columnCount != 0) { - ++rowCount; - } - - m_itemInfos.reserve(itemCount); + m_itemInfos.resize(itemCount); qreal y = m_headerHeight + itemMargin.height(); int row = 0; @@ -458,18 +467,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.pos = QPointF(x, y); + itemInfo.column = column; + itemInfo.row = row; if (grouped && horizontalScrolling) { // When grouping is enabled in the horizontal mode, the header alignment @@ -505,24 +506,9 @@ 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 - m_maximumScrollOffset = m_itemInfos.last().rect.bottom(); - const qreal rowY = m_itemInfos.last().rect.y(); - - int index = m_itemInfos.count() - 2; - while (index >= 0 && m_itemInfos[index].rect.bottom() >= rowY) { - m_maximumScrollOffset = qMax(m_maximumScrollOffset, m_itemInfos[index].rect.bottom()); - --index; - } - - m_maximumScrollOffset += itemMargin.height(); - + m_maximumScrollOffset = y; m_maximumItemOffset = m_columnCount * m_columnWidth; } else { m_maximumScrollOffset = 0; @@ -561,7 +547,7 @@ void KItemListViewLayouter::updateVisibleIndexes() int mid = 0; do { mid = (min + max) / 2; - if (m_itemInfos[mid].rect.top() < m_scrollOffset) { + if (m_itemInfos[mid].pos.y() < m_scrollOffset) { min = mid + 1; } else { max = mid - 1; @@ -571,13 +557,13 @@ void KItemListViewLayouter::updateVisibleIndexes() if (mid > 0) { // Include the row before the first fully visible index, as it might // be partly visible - if (m_itemInfos[mid].rect.top() >= m_scrollOffset) { + if (m_itemInfos[mid].pos.y() >= m_scrollOffset) { --mid; - Q_ASSERT(m_itemInfos[mid].rect.top() < m_scrollOffset); + Q_ASSERT(m_itemInfos[mid].pos.y() < m_scrollOffset); } - const qreal rowTop = m_itemInfos[mid].rect.top(); - while (mid > 0 && m_itemInfos[mid - 1].rect.top() == rowTop) { + const qreal rowTop = m_itemInfos[mid].pos.y(); + while (mid > 0 && m_itemInfos[mid - 1].pos.y() == rowTop) { --mid; } } @@ -594,14 +580,14 @@ void KItemListViewLayouter::updateVisibleIndexes() max = maxIndex; do { mid = (min + max) / 2; - if (m_itemInfos[mid].rect.y() <= bottom) { + if (m_itemInfos[mid].pos.y() <= bottom) { min = mid + 1; } else { max = mid - 1; } } while (min <= max); - while (mid > 0 && m_itemInfos[mid].rect.y() > bottom) { + while (mid > 0 && m_itemInfos[mid].pos.y() > bottom) { --mid; } m_lastVisibleIndex = mid; diff --git a/src/kitemviews/private/kitemlistviewlayouter.h b/src/kitemviews/private/kitemlistviewlayouter.h index 306fcd360..a3b0893a1 100644 --- a/src/kitemviews/private/kitemlistviewlayouter.h +++ b/src/kitemviews/private/kitemlistviewlayouter.h @@ -227,7 +227,7 @@ private: qreal m_groupHeaderMargin; struct ItemInfo { - QRectF rect; + QPointF pos; int column; int row; }; |
