-/***************************************************************************
- * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
- * *
- * 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 *
- ***************************************************************************/
+/*****************************************************************************
+ * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
+ * Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
+ * Copyright (C) 2013 by Emmanuel Pescosta <emmanuelpescosta099@gmail.com> *
+ * *
+ * 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 "kfileitemmodel.h"
-#include <KDirModel>
#include <KGlobalSettings>
#include <KLocale>
#include <KStringHandler>
#include <QTimer>
#include <QWidget>
+#include <algorithm>
+#include <vector>
+
// #define KFILEITEMMODEL_DEBUG
KFileItemModel::KFileItemModel(QObject* parent) :
m_resortAllItemsTimer(0),
m_pendingItemsToInsert(),
m_groups(),
- m_expandedParentsCountRoot(UninitializedExpandedParentsCountRoot),
m_expandedDirs(),
m_urlsToExpand()
{
connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted()));
connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
connect(m_dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted()));
- connect(m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
+ connect(m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)), this, SLOT(slotItemsAdded(KUrl,KFileItemList)));
connect(m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
connect(m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
KFileItemModel::~KFileItemModel()
{
qDeleteAll(m_itemData);
- m_itemData.clear();
+ qDeleteAll(m_filteredItems.values());
+ qDeleteAll(m_pendingItemsToInsert);
}
void KFileItemModel::loadDirectory(const KUrl& url)
void KFileItemModel::refreshDirectory(const KUrl& url)
{
+ // Refresh all expanded directories first (Bug 295300)
+ QHashIterator<KUrl, KUrl> expandedDirs(m_expandedDirs);
+ while (expandedDirs.hasNext()) {
+ expandedDirs.next();
+ m_dirLister->openUrl(expandedDirs.value(), KDirLister::Reload);
+ }
+
m_dirLister->openUrl(url, KDirLister::Reload);
}
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>();
}
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;
QHashIterator<QByteArray, QVariant> it(values);
while (it.hasNext()) {
it.next();
- const QByteArray role = it.key();
+ const QByteArray role = sharedValue(it.key());
const QVariant value = it.value();
if (currentValues[role] != value) {
m_itemData[index]->item.setUrl(url);
}
- emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
-
- if (changedRoles.contains(sortRole())) {
- m_resortAllItemsTimer->start();
- }
+ emitItemsChangedAndTriggerResorting(KItemRangeList() << KItemRange(index, 1), changedRoles);
return true;
}
return m_dirLister->dirOnlyMode();
}
-QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
+QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
{
QMimeData* data = new QMimeData();
KUrl::List urls;
KUrl::List mostLocalUrls;
bool canUseMostLocalUrls = true;
+ const ItemData* lastAddedItem = 0;
- QSetIterator<int> it(indexes);
- while (it.hasNext()) {
- const int index = it.next();
- const KFileItem item = fileItem(index);
+ foreach (int index, indexes) {
+ const ItemData* itemData = m_itemData.at(index);
+ const ItemData* parent = itemData->parent;
+
+ while (parent && parent != lastAddedItem) {
+ parent = parent->parent;
+ }
+
+ if (parent && parent == lastAddedItem) {
+ // A parent of 'itemData' has been added already.
+ continue;
+ }
+
+ lastAddedItem = itemData;
+ const KFileItem& item = itemData->item;
if (!item.isNull()) {
- urls << item.url();
+ urls << item.targetUrl();
bool isLocal;
mostLocalUrls << item.mostLocalUrl(isLocal);
}
const bool different = canUseMostLocalUrls && mostLocalUrls != urls;
- urls = KDirModel::simplifiedUrlList(urls); // TODO: Check if we still need KDirModel for this in KDE 5.0
if (different) {
- mostLocalUrls = KDirModel::simplifiedUrlList(mostLocalUrls);
urls.populateMimeData(mostLocalUrls, data);
} else {
urls.populateMimeData(data);
{
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;
}
}
// Update m_data with the changed requested roles
const int maxIndex = count() - 1;
for (int i = 0; i <= maxIndex; ++i) {
- m_itemData[i]->values = retrieveData(m_itemData.at(i)->item);
+ m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent);
}
kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
emit itemsChanged(KItemRangeList() << KItemRange(0, count()), QSet<QByteArray>());
}
+
+ // Clear the 'values' of all filtered items. They will be re-populated with the
+ // correct roles the next time 'values' will be accessed via data(int).
+ QHash<KFileItem, ItemData*>::iterator filteredIt = m_filteredItems.begin();
+ const QHash<KFileItem, ItemData*>::iterator filteredEnd = m_filteredItems.end();
+ while (filteredIt != filteredEnd) {
+ (*filteredIt)->values.clear();
+ ++filteredIt;
+ }
}
QSet<QByteArray> KFileItemModel::roles() const
}
QHash<QByteArray, QVariant> values;
- values.insert("isExpanded", expanded);
+ values.insert(sharedValue("isExpanded"), expanded);
if (!setData(index, values)) {
return false;
}
- const KUrl url = m_itemData.at(index)->item.url();
+ const KFileItem item = m_itemData.at(index)->item;
+ const KUrl url = item.url();
+ const KUrl targetUrl = item.targetUrl();
if (expanded) {
- m_expandedDirs.insert(url);
+ 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(url);
+ m_expandedDirs.remove(targetUrl);
m_dirLister->stop(url);
+ const int parentLevel = expandedParentsCount(index);
+ const int itemCount = m_itemData.count();
+ const int firstChildIndex = index + 1;
+
+ KUrl::List expandedChildren;
- KFileItemList itemsToRemove;
- const int expandedParentsCount = data(index)["expandedParentsCount"].toInt();
- ++index;
- while (index < count() && data(index)["expandedParentsCount"].toInt() > expandedParentsCount) {
- itemsToRemove.append(m_itemData.at(index)->item);
- ++index;
+ 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;
}
- removeItems(itemsToRemove);
+ const int childrenCount = childIndex - firstChildIndex;
+
+ removeFilteredChildren(KItemRangeList() << KItemRange(index, 1 + childrenCount));
+ removeItems(KItemRangeList() << KItemRange(firstChildIndex, childrenCount), DeleteItemData);
+
+ m_itemData.at(index)->values.insert("previouslyExpandedChildren", expandedChildren);
}
return true;
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;
}
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;
}
QSet<KUrl> KFileItemModel::expandedDirectories() const
{
- return m_expandedDirs;
+ return m_expandedDirs.values().toSet();
}
void KFileItemModel::restoreExpandedDirectories(const QSet<KUrl>& urls)
{
// 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()) {
- if (!m_filter.matches(itemData->item)) {
- newFilteredItems.append(itemData->item);
- m_filteredItems.insert(itemData->item);
+ const KFileItem item = itemData->item;
+ if (!m_filter.matches(item)) {
+ newFilteredIndexes.append(index);
+ m_filteredItems.insert(item, itemData);
}
}
}
- removeItems(newFilteredItems);
+ 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.
- KFileItemList newVisibleItems;
+ QList<ItemData*> newVisibleItems;
- QMutableSetIterator<KFileItem> it(m_filteredItems);
- while (it.hasNext()) {
- const KFileItem item = it.next();
- if (m_filter.matches(item)) {
- newVisibleItems.append(item);
- it.remove();
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+ while (it != m_filteredItems.end()) {
+ if (m_filter.matches(it.key())) {
+ newVisibleItems.append(it.value());
+ it = m_filteredItems.erase(it);
+ } else {
+ ++it;
}
}
insertItems(newVisibleItems);
}
+void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges)
+{
+ 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;
+ }
+
+ 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()) {
+ if (parents.contains(it.value()->parent)) {
+ delete it.value();
+ it = m_filteredItems.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
{
static QList<RoleInfo> rolesInfo;
// menus tries to put the actions into sub menus otherwise.
info.group = QString();
}
- info.requiresNepomuk = map[i].requiresNepomuk;
+ info.requiresBaloo = map[i].requiresBaloo;
info.requiresIndexer = map[i].requiresIndexer;
rolesInfo.append(info);
}
Q_UNUSED(previous);
m_sortRole = typeForRole(current);
-#ifdef KFILEITEMMODEL_DEBUG
if (!m_requestRole[m_sortRole]) {
- kWarning() << "The sort-role has been changed to a role that has not been received yet";
+ QSet<QByteArray> newRoles = m_roles;
+ newRoles << current;
+ setRoles(newRoles);
}
-#endif
resortAllItems();
}
oldUrls.append(itemData->item.url());
}
- m_groups.clear();
m_items.clear();
// Resort the items
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).url());
- 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();
emit directoryLoadingCanceled();
}
-void KFileItemModel::slotNewItems(const KFileItemList& items)
+void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items)
{
Q_ASSERT(!items.isEmpty());
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
- // To be able to compare whether the new items may be inserted as children
- // of a parent item the pending items must be added to the model first.
- dispatchPendingItemsToInsert();
+ KUrl parentUrl;
+ if (m_expandedDirs.contains(directoryUrl)) {
+ parentUrl = m_expandedDirs.value(directoryUrl);
+ } else {
+ parentUrl = directoryUrl;
+ parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
+ }
+ if (m_requestRole[ExpandedParentsCountRole]) {
KFileItem item = items.first();
// If the expanding of items is enabled, the call
return;
}
+ if (directoryUrl != directory()) {
+ // To be able to compare whether the new items may be inserted as children
+ // of a parent item the pending items must be added to the model first.
+ dispatchPendingItemsToInsert();
+ }
+
// KDirLister keeps the children of items that got expanded once even if
// they got collapsed again with KFileItemModel::setExpanded(false). So it must be
// checked whether the parent for new items is still expanded.
- KUrl parentUrl = item.url().upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
const int parentIndex = m_items.value(parentUrl, -1);
if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) {
// The parent is not expanded.
}
}
+ QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+
if (!m_filter.hasSetFilters()) {
- m_pendingItemsToInsert.append(items);
+ m_pendingItemsToInsert.append(itemDataList);
} else {
// The name or type filter is active. Hide filtered items
// before inserting them into the model and remember
// the filtered items in m_filteredItems.
- KFileItemList filteredItems;
- foreach (const KFileItem& item, items) {
- if (m_filter.matches(item)) {
- filteredItems.append(item);
+ foreach (ItemData* itemData, itemDataList) {
+ if (m_filter.matches(itemData->item)) {
+ m_pendingItemsToInsert.append(itemData);
} else {
- m_filteredItems.insert(item);
+ m_filteredItems.insert(itemData->item, itemData);
}
}
-
- m_pendingItemsToInsert.append(filteredItems);
}
if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
{
dispatchPendingItemsToInsert();
- KFileItemList itemsToRemove = items;
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
- // 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());
+
+ foreach (const KFileItem& item, items) {
+ const KUrl url = item.url();
+ const int index = m_items.value(url, -1);
+ if (index >= 0) {
+ indexesToRemove.append(index);
+ } else {
+ // Probably the item has been filtered.
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
+ if (it != m_filteredItems.end()) {
+ delete it.value();
+ m_filteredItems.erase(it);
+ }
}
}
- if (!m_filteredItems.isEmpty()) {
- foreach (const KFileItem& item, itemsToRemove) {
- m_filteredItems.remove(item);
+ std::sort(indexesToRemove.begin(), indexesToRemove.end());
+
+ if (m_requestRole[ExpandedParentsCountRole] && !m_expandedDirs.isEmpty()) {
+ // Assure that removing a parent item also results in removing all children
+ QVector<int> indexesToRemoveWithChildren;
+ indexesToRemoveWithChildren.reserve(m_items.count());
+
+ const int itemCount = m_itemData.count();
+ foreach (int index, indexesToRemove) {
+ indexesToRemoveWithChildren.append(index);
+
+ const int parentLevel = expandedParentsCount(index);
+ int childIndex = index + 1;
+ while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) {
+ indexesToRemoveWithChildren.append(childIndex);
+ ++childIndex;
+ }
}
+
+ indexesToRemove = indexesToRemoveWithChildren;
}
- removeItems(itemsToRemove);
+ const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
+ removeFilteredChildren(itemRanges);
+ removeItems(itemRanges, DeleteItemData);
}
void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
kDebug() << "Refreshing" << items.count() << "items";
#endif
- m_groups.clear();
-
// Get the indexes of all items that have been refreshed
QList<int> indexes;
indexes.reserve(items.count());
+ QSet<QByteArray> changedRoles;
+
QListIterator<QPair<KFileItem, KFileItem> > it(items);
while (it.hasNext()) {
const QPair<KFileItem, KFileItem>& itemPair = it.next();
// Keep old values as long as possible if they could not retrieved synchronously yet.
// The update of the values will be done asynchronously by KFileItemModelRolesUpdater.
- QHashIterator<QByteArray, QVariant> it(retrieveData(newItem));
+ QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(index)->parent));
+ QHash<QByteArray, QVariant>& values = m_itemData[index]->values;
while (it.hasNext()) {
it.next();
- m_itemData[index]->values.insert(it.key(), it.value());
+ const QByteArray& role = it.key();
+ if (values.value(role) != it.value()) {
+ values.insert(role, it.value());
+ changedRoles.insert(role);
+ }
}
m_items.remove(oldItem.url());
m_items.insert(newItem.url(), index);
indexes.append(index);
+ } else {
+ // Check if 'oldItem' is one of the filtered items.
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
+ if (it != m_filteredItems.end()) {
+ ItemData* itemData = it.value();
+ itemData->item = newItem;
+
+ // The data stored in 'values' might have changed. Therefore, we clear
+ // 'values' and re-populate it the next time it is requested via data(int).
+ itemData->values.clear();
+
+ m_filteredItems.erase(it);
+ m_filteredItems.insert(newItem, itemData);
+ }
}
}
// 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));
- }
-
- emit itemsChanged(itemRangeList, m_roles);
-
- resortAllItems();
+ const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
+ emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
}
void KFileItemModel::slotClear()
kDebug() << "Clearing all items";
#endif
+ qDeleteAll(m_filteredItems.values());
m_filteredItems.clear();
m_groups.clear();
m_maximumUpdateIntervalTimer->stop();
m_resortAllItemsTimer->stop();
- m_pendingItemsToInsert.clear();
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
+ qDeleteAll(m_pendingItemsToInsert);
+ m_pendingItemsToInsert.clear();
const int removedCount = m_itemData.count();
if (removedCount > 0) {
}
}
-void KFileItemModel::insertItems(const KFileItemList& items)
+void KFileItemModel::insertItems(QList<ItemData*>& newItems)
{
- if (items.isEmpty()) {
+ if (newItems.isEmpty()) {
return;
}
- if (m_sortRole == TypeRole) {
- // Try to resolve the MIME-types synchronously to prevent a reordering of
- // the items when sorting by type (per default MIME-types are resolved
- // asynchronously by KFileItemModelRolesUpdater).
- determineMimeTypes(items, 200);
- }
-
#ifdef KFILEITEMMODEL_DEBUG
QElapsedTimer timer;
timer.start();
kDebug() << "===========================================================";
- kDebug() << "Inserting" << items.count() << "items";
+ kDebug() << "Inserting" << newItems.count() << "items";
#endif
m_groups.clear();
+ prepareItemsForSorting(newItems);
- QList<ItemData*> sortedItems = createItemDataList(items);
- sort(sortedItems.begin(), sortedItems.end());
+ 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
kDebug() << "[TIME] Sorting:" << timer.elapsed();
#endif
KItemRangeList itemRanges;
- int targetIndex = 0;
- int sourceIndex = 0;
- int insertedAtIndex = -1; // Index for the current item-range
- int insertedCount = 0; // Count for the current item-range
- int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
- while (sourceIndex < sortedItems.count()) {
- // Find target index from m_items to insert the current item
- // in a sorted order
- const int previousTargetIndex = targetIndex;
- while (targetIndex < m_itemData.count()) {
- if (!lessThan(m_itemData.at(targetIndex), sortedItems.at(sourceIndex))) {
- break;
+ const int existingItemCount = m_itemData.count();
+ const int newItemCount = newItems.count();
+ const int totalItemCount = existingItemCount + newItemCount;
+
+ if (existingItemCount == 0) {
+ // Optimization for the common special case that there are no
+ // items in the model yet. Happens, e.g., when entering a folder.
+ m_itemData = newItems;
+ itemRanges << KItemRange(0, newItemCount);
+ } else {
+ m_itemData.reserve(totalItemCount);
+ for (int i = existingItemCount; i < totalItemCount; ++i) {
+ m_itemData.append(0);
+ }
+
+ // We build the new list m_items in reverse order to minimize
+ // the number of moves and guarantee O(N) complexity.
+ int targetIndex = totalItemCount - 1;
+ int sourceIndexExistingItems = existingItemCount - 1;
+ int sourceIndexNewItems = newItemCount - 1;
+
+ int rangeCount = 0;
+
+ while (sourceIndexNewItems >= 0) {
+ ItemData* newItem = newItems.at(sourceIndexNewItems);
+ if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems))) {
+ // Move an existing item to its new position. If any new items
+ // are behind it, push the item range to itemRanges.
+ if (rangeCount > 0) {
+ itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
+ rangeCount = 0;
+ }
+
+ m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems);
+ --sourceIndexExistingItems;
+ } else {
+ // Insert a new item into the list.
+ ++rangeCount;
+ m_itemData[targetIndex] = newItem;
+ --sourceIndexNewItems;
}
- ++targetIndex;
+ --targetIndex;
}
- if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
- itemRanges << KItemRange(insertedAtIndex, insertedCount);
- previouslyInsertedCount += insertedCount;
- insertedAtIndex = targetIndex - previouslyInsertedCount;
- insertedCount = 0;
+ // Push the final item range to itemRanges.
+ if (rangeCount > 0) {
+ itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
}
- // Insert item at the position targetIndex by transferring
- // the ownership of the item-data from sortedItems to m_itemData.
- // m_items will be inserted after the loop (see comment below)
- m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
- ++insertedCount;
-
- if (insertedAtIndex < 0) {
- insertedAtIndex = targetIndex;
- Q_ASSERT(previouslyInsertedCount == 0);
- }
- ++targetIndex;
- ++sourceIndex;
+ // Note that itemRanges is still sorted in reverse order.
+ std::reverse(itemRanges.begin(), itemRanges.end());
}
- // The indexes of all m_items must be adjusted, not only the index
- // of the new items
- const int itemDataCount = m_itemData.count();
- for (int i = 0; i < itemDataCount; ++i) {
+ // The indexes starting from the first inserted item must be adjusted.
+ m_items.reserve(totalItemCount);
+ for (int i = itemRanges.front().index; i < totalItemCount; ++i) {
m_items.insert(m_itemData.at(i)->item.url(), i);
}
- itemRanges << KItemRange(insertedAtIndex, insertedCount);
emit itemsInserted(itemRanges);
#ifdef KFILEITEMMODEL_DEBUG
- kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
+ kDebug() << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
#endif
}
-static KItemRangeList sortedIndexesToKItemRangeList(const QList<int> sortedNumbers)
-{
- 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;
- }
-
- result << KItemRange(index, count);
- return result;
-}
-
-void KFileItemModel::removeItems(const KFileItemList& items)
+void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior)
{
- if (items.isEmpty()) {
+ if (itemRanges.isEmpty()) {
return;
}
-#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().
QHash<KUrl, int>::iterator it = m_items.find(url);
m_items.erase(it);
- ItemData* data = m_itemData.at(index);
- delete data;
+ if (behavior == DeleteItemData) {
+ delete m_itemData.at(index);
+ }
+
m_itemData[index] = 0;
- } else {
- kWarning() << "Item that should be deleted has not been found!";
}
}
- 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;
}
}
- 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. Note that all indexes
- // might have been changed by the removal of the items.
+ // Step 3: Adjust indexes in the hash m_items, starting from the
+ // index of the first removed item.
const int newItemDataCount = m_itemData.count();
- for (int i = 0; i < newItemDataCount; ++i) {
+ for (int i = itemRanges.front().index; i < newItemDataCount; ++i) {
m_items.insert(m_itemData.at(i)->item.url(), i);
}
- if (count() <= 0) {
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
- }
-
emit itemsRemoved(itemRanges);
}
-QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KFileItemList& items) const
+QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const
{
+ if (m_sortRole == TypeRole) {
+ // Try to resolve the MIME-types synchronously to prevent a reordering of
+ // the items when sorting by type (per default MIME-types are resolved
+ // asynchronously by KFileItemModelRolesUpdater).
+ determineMimeTypes(items, 200);
+ }
+
+ const int parentIndex = m_items.value(parentUrl, -1);
+ ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
+
QList<ItemData*> itemDataList;
itemDataList.reserve(items.count());
foreach (const KFileItem& item, items) {
ItemData* itemData = new ItemData();
itemData->item = item;
- itemData->values = retrieveData(item);
- itemData->parent = 0;
-
- const bool determineParent = m_requestRole[ExpandedParentsCountRole]
- && itemData->values["expandedParentsCount"].toInt() > 0;
- if (determineParent) {
- KUrl parentUrl = item.url().upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
- const int parentIndex = m_items.value(parentUrl, -1);
- if (parentIndex >= 0) {
- itemData->parent = m_itemData.at(parentIndex);
- } else {
- kWarning() << "Parent item not found for" << item.url();
+ itemData->parent = parentItem;
+ itemDataList.append(itemData);
+ }
+
+ return itemDataList;
+}
+
+void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
+{
+ 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) {
+ if (itemData->values.isEmpty()) {
+ itemData->values = retrieveData(itemData->item, itemData->parent);
}
}
+ break;
- itemDataList.append(itemData);
+ case TypeRole:
+ // At least store the data including the file type for items with known MIME type.
+ foreach (ItemData* itemData, itemDataList) {
+ if (itemData->values.isEmpty()) {
+ const KFileItem item = itemData->item;
+ if (item.isDir() || item.isMimeTypeKnown()) {
+ itemData->values = retrieveData(itemData->item, itemData->parent);
+ }
+ }
+ }
+ 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, SizeRole 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);
-
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
+ removeItems(KItemRangeList::fromSortedContainer(indexesToRemove), DeleteItemData);
m_expandedDirs.clear();
+
+ // Also remove all filtered items which have a parent.
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+ const QHash<KFileItem, ItemData*>::iterator end = m_filteredItems.end();
+
+ while (it != end) {
+ if (it.value()->parent) {
+ delete it.value();
+ it = m_filteredItems.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet<QByteArray>& changedRoles)
+{
+ emit itemsChanged(itemRanges, changedRoles);
+
+ // Trigger a resorting if necessary. Note that this can happen even if the sort
+ // role has not changed at all because the file name can be used as a fallback.
+ if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))) {
+ foreach (const KItemRange& range, itemRanges) {
+ bool needsResorting = false;
+
+ const int first = range.index;
+ const int last = range.index + range.count - 1;
+
+ // Resorting the model is necessary if
+ // (a) The first item in the range is "lessThan" its predecessor,
+ // (b) the successor of the last item is "lessThan" the last item, or
+ // (c) the internal order of the items in the range is incorrect.
+ if (first > 0
+ && lessThan(m_itemData.at(first), m_itemData.at(first - 1))) {
+ needsResorting = true;
+ } else if (last < count() - 1
+ && lessThan(m_itemData.at(last + 1), m_itemData.at(last))) {
+ needsResorting = true;
+ } else {
+ for (int index = first; index < last; ++index) {
+ if (lessThan(m_itemData.at(index + 1), m_itemData.at(index))) {
+ needsResorting = true;
+ break;
+ }
+ }
+ }
+
+ if (needsResorting) {
+ m_resortAllItemsTimer->start();
+ return;
+ }
+ }
+ }
+
+ if (groupedSorting() && changedRoles.contains(sortRole())) {
+ // The position is still correct, but the groups might have changed
+ // if the changed item is either the first or the last item in a
+ // group.
+ // In principle, we could try to find out if the item really is the
+ // first or last one in its group and then update the groups
+ // (possibly with a delayed timer to make sure that we don't
+ // re-calculate the groups very often if items are updated one by
+ // one), but starting m_resortAllItemsTimer is easier.
+ m_resortAllItemsTimer->start();
+ }
}
void KFileItemModel::resetRoles()
return roles.value(roleType);
}
-QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) const
+QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item, const ItemData* parent) const
{
// It is important to insert only roles that are fast to retrieve. E.g.
// KFileItem::iconName() can be very expensive if the MIME-type is unknown
// and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
QHash<QByteArray, QVariant> data;
- data.insert("url", item.url());
+ data.insert(sharedValue("url"), item.url());
const bool isDir = item.isDir();
- if (m_requestRole[IsDirRole]) {
- data.insert("isDir", isDir);
+ if (m_requestRole[IsDirRole] && isDir) {
+ data.insert(sharedValue("isDir"), true);
}
- if (m_requestRole[IsLinkRole]) {
- const bool isLink = item.isLink();
- data.insert("isLink", isLink);
+ if (m_requestRole[IsLinkRole] && item.isLink()) {
+ data.insert(sharedValue("isLink"), true);
}
if (m_requestRole[NameRole]) {
- data.insert("text", item.text());
+ data.insert(sharedValue("text"), item.text());
}
- if (m_requestRole[SizeRole]) {
- if (isDir) {
- data.insert("size", QVariant());
- } else {
- data.insert("size", item.size());
- }
+ if (m_requestRole[SizeRole] && !isDir) {
+ data.insert(sharedValue("size"), item.size());
}
if (m_requestRole[DateRole]) {
// having several thousands of items. Instead the formatting of the
// date-time will be done on-demand by the view when the date will be shown.
const KDateTime dateTime = item.time(KFileItem::ModificationTime);
- data.insert("date", dateTime.dateTime());
+ data.insert(sharedValue("date"), dateTime.dateTime());
}
if (m_requestRole[PermissionsRole]) {
- data.insert("permissions", item.permissionsString());
+ data.insert(sharedValue("permissions"), item.permissionsString());
}
if (m_requestRole[OwnerRole]) {
- data.insert("owner", item.user());
+ data.insert(sharedValue("owner"), item.user());
}
if (m_requestRole[GroupRole]) {
- data.insert("group", item.group());
+ data.insert(sharedValue("group"), item.group());
}
if (m_requestRole[DestinationRole]) {
if (destination.isEmpty()) {
destination = QLatin1String("-");
}
- data.insert("destination", destination);
+ data.insert(sharedValue("destination"), destination);
}
if (m_requestRole[PathRole]) {
const int index = path.lastIndexOf(item.text());
path = path.mid(0, index - 1);
- data.insert("path", path);
- }
-
- if (m_requestRole[IsExpandedRole]) {
- data.insert("isExpanded", false);
+ data.insert(sharedValue("path"), path);
}
- if (m_requestRole[IsExpandableRole]) {
- data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl());
+ if (m_requestRole[IsExpandableRole] && isDir) {
+ data.insert(sharedValue("isExpandable"), true);
}
if (m_requestRole[ExpandedParentsCountRole]) {
- if (m_expandedParentsCountRoot == UninitializedExpandedParentsCountRoot) {
- const KUrl rootUrl = m_dirLister->url();
- const QString protocol = rootUrl.protocol();
- const bool forceExpandedParentsCountRoot = (protocol == QLatin1String("trash") ||
- protocol == QLatin1String("nepomuk") ||
- protocol == QLatin1String("remote") ||
- protocol.contains(QLatin1String("search")));
- if (forceExpandedParentsCountRoot) {
- m_expandedParentsCountRoot = ForceExpandedParentsCountRoot;
- } else {
- const QString rootDir = rootUrl.path(KUrl::AddTrailingSlash);
- m_expandedParentsCountRoot = rootDir.count('/');
- }
- }
-
- if (m_expandedParentsCountRoot == ForceExpandedParentsCountRoot) {
- data.insert("expandedParentsCount", -1);
- } else {
- const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
- const int level = dir.count('/') - m_expandedParentsCountRoot;
- data.insert("expandedParentsCount", level);
+ if (parent) {
+ const int level = expandedParentsCount(parent) + 1;
+ data.insert(sharedValue("expandedParentsCount"), level);
}
}
if (item.isMimeTypeKnown()) {
- data.insert("iconName", item.iconName());
+ data.insert(sharedValue("iconName"), item.iconName());
if (m_requestRole[TypeRole]) {
- data.insert("type", item.mimeComment());
+ data.insert(sharedValue("type"), item.mimeComment());
}
+ } else if (m_requestRole[TypeRole] && isDir) {
+ static const QString folderMimeType = item.mimeComment();
+ data.insert(sharedValue("type"), folderMimeType);
}
return data;
{
int result = 0;
- if (m_expandedParentsCountRoot >= 0) {
- result = expandedParentsCountCompare(a, b);
- if (result != 0) {
- // The items have parents with different expansion levels
- return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+ if (a->parent != b->parent) {
+ 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.
+ for (int i = expansionLevelB; i > expansionLevelA; --i) {
+ if (b->parent == a) {
+ return true;
+ }
+ b = b->parent;
+ }
+
+ // If a has a higher expansion level than a, check if b is a parent
+ // of a, and make sure that both expansion levels are equal otherwise.
+ for (int i = expansionLevelA; i > expansionLevelB; --i) {
+ if (a->parent == b) {
+ return false;
+ }
+ a = a->parent;
+ }
+
+ Q_ASSERT(expandedParentsCount(a) == expandedParentsCount(b));
+
+ // Compare the last parents of a and b which are different.
+ while (a->parent != b->parent) {
+ a = a->parent;
+ b = b->parent;
}
}
: QString::compare(a, b, Qt::CaseSensitive);
}
-int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemData* b) const
+bool KFileItemModel::useMaximumUpdateInterval() const
{
- const KUrl urlA = a->item.url();
- const KUrl urlB = b->item.url();
- if (urlA.directory() == urlB.directory()) {
- // Both items have the same directory as parent
- return 0;
- }
-
- // Check whether one item is the parent of the other item
- if (urlA.isParentOf(urlB)) {
- return (sortOrder() == Qt::AscendingOrder) ? -1 : +1;
- } else if (urlB.isParentOf(urlA)) {
- return (sortOrder() == Qt::AscendingOrder) ? +1 : -1;
- }
-
- // Determine the maximum common path of both items and
- // remember the index in 'index'
- const QString pathA = urlA.path();
- const QString pathB = urlB.path();
-
- const int maxIndex = qMin(pathA.length(), pathB.length()) - 1;
- int index = 0;
- while (index <= maxIndex && pathA.at(index) == pathB.at(index)) {
- ++index;
- }
- if (index > maxIndex) {
- index = maxIndex;
- }
- while (index > 0 && (pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/'))) {
- --index;
- }
-
- // Determine the first sub-path after the common path and
- // check whether it represents a directory or already a file
- bool isDirA = true;
- const QString subPathA = subPath(a->item, pathA, index, &isDirA);
- bool isDirB = true;
- const QString subPathB = subPath(b->item, pathB, index, &isDirB);
-
- if (m_sortDirsFirst || m_sortRole == SizeRole) {
- if (isDirA && !isDirB) {
- return (sortOrder() == Qt::AscendingOrder) ? -1 : +1;
- } else if (!isDirA && isDirB) {
- return (sortOrder() == Qt::AscendingOrder) ? +1 : -1;
- }
- }
-
- // Compare the items of the parents that represent the first
- // different path after the common path.
- const QString parentPathA = pathA.left(index) + subPathA;
- const QString parentPathB = pathB.left(index) + subPathB;
-
- const ItemData* parentA = a;
- while (parentA && parentA->item.url().path() != parentPathA) {
- parentA = parentA->parent;
- }
-
- const ItemData* parentB = b;
- while (parentB && parentB->item.url().path() != parentPathB) {
- parentB = parentB->parent;
- }
-
- if (parentA && parentB) {
- return sortRoleCompare(parentA, parentB);
- }
-
- kWarning() << "Child items without parent detected:" << a->item.url() << b->item.url();
- return QString::compare(urlA.url(), urlB.url(), Qt::CaseSensitive);
+ return !m_dirLister->url().isLocalFile();
}
-QString KFileItemModel::subPath(const KFileItem& item,
- const QString& itemPath,
- int start,
- bool* isDir) const
+static bool localeAwareLessThan(const QChar& c1, const QChar& c2)
{
- Q_ASSERT(isDir);
- const int pathIndex = itemPath.indexOf('/', start + 1);
- *isDir = (pathIndex > 0) || item.isDir();
- return itemPath.mid(start, pathIndex - start);
-}
-
-bool KFileItemModel::useMaximumUpdateInterval() const
-{
- return !m_dirLister->url().isLocalFile();
+ return QString::localeAwareCompare(c1, c2) < 0;
}
QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
QString groupValue;
QChar firstChar;
- bool isLetter = false;
for (int i = 0; i <= maxIndex; ++i) {
if (isChildItem(i)) {
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();
if (firstChar != newFirstChar) {
QString newGroupValue;
- if (newFirstChar >= QLatin1Char('A') && newFirstChar <= QLatin1Char('Z')) {
- // Apply group 'A' - 'Z'
- newGroupValue = newFirstChar;
- isLetter = true;
+ if (newFirstChar.isLetter()) {
+ // Try to find a matching group in the range 'A' to 'Z'.
+ static std::vector<QChar> lettersAtoZ;
+ if (lettersAtoZ.empty()) {
+ for (char c = 'A'; c <= 'Z'; ++c) {
+ lettersAtoZ.push_back(QLatin1Char(c));
+ }
+ }
+
+ std::vector<QChar>::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan);
+ if (it != lettersAtoZ.end()) {
+ if (localeAwareLessThan(newFirstChar, *it) && it != lettersAtoZ.begin()) {
+ // newFirstChar belongs to the group preceding *it.
+ // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
+ --it;
+ }
+ newGroupValue = *it;
+ } else {
+ newGroupValue = newFirstChar;
+ }
} else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
// Apply group '0 - 9' for any name that starts with a digit
newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
- isLetter = false;
} else {
- if (isLetter) {
- // If the current group is 'A' - 'Z' check whether a locale character
- // fits into the existing group.
- // TODO: This does not work in the case if e.g. the group 'O' starts with
- // an umlaut 'O' -> provide unit-test to document this known issue
- const QChar prevChar(firstChar.unicode() - ushort(1));
- const QChar nextChar(firstChar.unicode() + ushort(1));
- const QString currChar(newFirstChar);
- const bool partOfCurrentGroup = currChar.localeAwareCompare(prevChar) > 0 &&
- currChar.localeAwareCompare(nextChar) < 0;
- if (partOfCurrentGroup) {
- continue;
- }
- }
newGroupValue = i18nc("@title:group", "Others");
- isLetter = false;
}
if (newGroupValue != groupValue) {
const QDate currentDate = KDateTime::currentLocalDateTime().date();
- int yearForCurrentWeek = 0;
- int currentWeek = currentDate.weekNumber(&yearForCurrentWeek);
- if (yearForCurrentWeek == currentDate.year() + 1) {
- currentWeek = 53;
- }
-
QDate previousModifiedDate;
QString groupValue;
for (int i = 0; i <= maxIndex; ++i) {
const int daysDistance = modifiedDate.daysTo(currentDate);
- int yearForModifiedWeek = 0;
- int modifiedWeek = modifiedDate.weekNumber(&yearForModifiedWeek);
- if (yearForModifiedWeek == modifiedDate.year() + 1) {
- modifiedWeek = 53;
- }
-
QString newGroupValue;
if (currentDate.year() == modifiedDate.year() && currentDate.month() == modifiedDate.month()) {
- if (modifiedWeek > currentWeek) {
- // Usecase: modified date = 2010-01-01, current date = 2010-01-22
- // modified week = 53, current week = 3
- modifiedWeek = 0;
- }
- switch (currentWeek - modifiedWeek) {
+ switch (daysDistance / 7) {
case 0:
switch (daysDistance) {
case 0: newGroupValue = i18nc("@title:group Date", "Today"); break;
}
break;
case 1:
- newGroupValue = i18nc("@title:group Date", "Last Week");
+ newGroupValue = i18nc("@title:group Date", "One Week Ago");
break;
case 2:
newGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
} else if (daysDistance <= 7) {
newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: %A, %B is full month name in current locale, and %Y is full year number", "%A (%B, %Y)"));
} else if (daysDistance <= 7 * 2) {
- newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Last Week (%B, %Y)"));
+ newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "One Week Ago (%B, %Y)"));
} else if (daysDistance <= 7 * 3) {
newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Two Weeks Ago (%B, %Y)"));
} else if (daysDistance <= 7 * 4) {
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.
const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
{
static const RoleInfoMap rolesInfoMap[] = {
- // | role | roleType | role translation | group translation | requires Nepomuk | requires indexer
+ // | role | roleType | role translation | group translation | requires Baloo | requires indexer
{ 0, NoRole, 0, 0, 0, 0, false, false },
{ "text", NameRole, I18N_NOOP2_NOSTRIP("@label", "Name"), 0, 0, false, false },
{ "size", SizeRole, I18N_NOOP2_NOSTRIP("@label", "Size"), 0, 0, false, false },
{
QElapsedTimer timer;
timer.start();
- foreach (KFileItem item, items) { // krazy:exclude=foreach
- item.determineMimeType();
+ foreach (const KFileItem& item, items) { // krazy:exclude=foreach
+ // Only determine mime types for files here. For directories,
+ // KFileItem::determineMimeType() reads the .directory file inside to
+ // load the icon, but this is not necessary at all if we just need the
+ // type. Some special code for setting the correct mime type for
+ // directories is in retrieveData().
+ if (!item.isDir()) {
+ item.determineMimeType();
+ }
+
if (timer.elapsed() > timeout) {
// Don't block the user interface, let the remaining items
// be resolved asynchronously.
}
}
+QByteArray KFileItemModel::sharedValue(const QByteArray& value)
+{
+ static QSet<QByteArray> pool;
+ const QSet<QByteArray>::const_iterator it = pool.constFind(value);
+
+ if (it != pool.constEnd()) {
+ return *it;
+ } else {
+ pool.insert(value);
+ return value;
+ }
+}
+
bool KFileItemModel::isConsistent() const
{
- // Check that m_items and m_itemData are consistent, and that the items are sorted.
if (m_items.count() != m_itemData.count()) {
return false;
}
for (int i = 0; i < count(); ++i) {
+ // Check if m_items and m_itemData are consistent.
const KFileItem item = fileItem(i);
if (item.isNull()) {
qWarning() << "Item" << i << "is null";
return false;
}
+ // Check if the items are sorted correctly.
if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i))) {
qWarning() << "The order of items" << i - 1 << "and" << i << "is wrong:"
<< fileItem(i - 1) << fileItem(i);
return false;
}
+
+ // Check if all parent-child relationships are consistent.
+ const ItemData* data = m_itemData.at(i);
+ const ItemData* parent = data->parent;
+ if (parent) {
+ if (expandedParentsCount(data) != expandedParentsCount(parent) + 1) {
+ qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item;
+ return false;
+ }
+
+ const int parentIndex = index(parent->item);
+ if (parentIndex >= i) {
+ qWarning() << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item;
+ return false;
+ }
+ }
}
return true;