-/*****************************************************************************
- * 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 *
- *****************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2013 Frank Reininghaus <frank78ac@googlemail.com>
+ * SPDX-FileCopyrightText: 2013 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
#include "kfileitemmodel.h"
#include "dolphin_generalsettings.h"
+#include "dolphin_detailsmodesettings.h"
#include "dolphindebug.h"
-#include "private/kfileitemmodeldirlister.h"
#include "private/kfileitemmodelsortalgorithm.h"
+#include <KDirLister>
+#include <KIO/Job>
#include <KLocalizedString>
#include <KUrlMimeData>
#include <QElapsedTimer>
#include <QMimeData>
+#include <QMimeDatabase>
#include <QTimer>
#include <QWidget>
+#include <QRecursiveMutex>
+#include <QIcon>
+
+Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
// #define KFILEITEMMODEL_DEBUG
KItemModelBase("text", parent),
m_dirLister(nullptr),
m_sortDirsFirst(true),
+ m_sortHiddenLast(false),
m_sortRole(NameRole),
m_sortingProgressPercent(-1),
m_roles(),
loadSortingSettings();
- m_dirLister = new KFileItemModelDirLister(this);
+ m_dirLister = new KDirLister(this);
+ m_dirLister->setAutoErrorHandlingEnabled(false);
m_dirLister->setDelayedMimeTypes(true);
const QWidget* parentWidget = qobject_cast<QWidget*>(parent);
m_dirLister->setMainWindow(parentWidget->window());
}
- connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
+ connect(m_dirLister, &KCoreDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled);
- connect(m_dirLister, QOverload<const QUrl&>::of(&KCoreDirLister::completed), this, &KFileItemModel::slotCompleted);
- connect(m_dirLister, &KFileItemModelDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded);
- connect(m_dirLister, &KFileItemModelDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted);
- connect(m_dirLister, &KFileItemModelDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems);
+ connect(m_dirLister, &KCoreDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded);
+ connect(m_dirLister, &KCoreDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted);
+ connect(m_dirLister, &KCoreDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems);
connect(m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, &KFileItemModel::slotClear);
- connect(m_dirLister, &KFileItemModelDirLister::infoMessage, this, &KFileItemModel::infoMessage);
- connect(m_dirLister, &KFileItemModelDirLister::errorMessage, this, &KFileItemModel::errorMessage);
- connect(m_dirLister, &KFileItemModelDirLister::percent, this, &KFileItemModel::directoryLoadingProgress);
+ connect(m_dirLister, &KCoreDirLister::infoMessage, this, &KFileItemModel::infoMessage);
+ connect(m_dirLister, &KCoreDirLister::jobError, this, &KFileItemModel::slotListerError);
+ connect(m_dirLister, &KCoreDirLister::percent, this, &KFileItemModel::directoryLoadingProgress);
connect(m_dirLister, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection);
- connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError);
+ connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted);
// Apply default roles that should be determined
resetRoles();
return m_sortDirsFirst;
}
+void KFileItemModel::setSortHiddenLast(bool hiddenLast)
+{
+ if (hiddenLast != m_sortHiddenLast) {
+ m_sortHiddenLast = hiddenLast;
+ resortAllItems();
+ }
+}
+
+bool KFileItemModel::sortHiddenLast() const
+{
+ return m_sortHiddenLast;
+}
+
void KFileItemModel::setShowHiddenFiles(bool show)
{
m_dirLister->setShowingDotFiles(show);
// The following code has been taken from KDirModel::mimeData()
// (kdelibs/kio/kio/kdirmodel.cpp)
- // Copyright (C) 2006 David Faure <faure@kde.org>
+ // SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
QList<QUrl> urls;
QList<QUrl> mostLocalUrls;
const ItemData* lastAddedItem = nullptr;
indexesForUrl.insert(m_itemData.at(i)->item.url(), i);
}
- foreach (const QUrl& url, indexesForUrl.uniqueKeys()) {
+ const auto uniqueKeys = indexesForUrl.uniqueKeys();
+ for (const QUrl& url : uniqueKeys) {
if (indexesForUrl.count(url) > 1) {
qCWarning(DolphinDebug) << "Multiple items found with the URL" << url;
m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent);
}
- emit itemsChanged(KItemRangeList() << KItemRange(0, count()), changedRoles);
+ Q_EMIT itemsChanged(KItemRangeList() << KItemRange(0, count()), changedRoles);
}
// Clear the 'values' of all filtered items. They will be re-populated with the
m_dirLister->openUrl(url, KDirLister::Keep);
const QVariantList previouslyExpandedChildren = m_itemData.at(index)->values.value("previouslyExpandedChildren").value<QVariantList>();
- foreach (const QVariant& var, previouslyExpandedChildren) {
+ for (const QVariant& var : previouslyExpandedChildren) {
m_urlsToExpand.insert(var.toUrl());
}
} else {
// first subdir can be empty, if m_dirLister->url().path() does not end with '/'
// this happens if baseUrl is not root but a home directory, see FoldersPanel,
// so using QString::SkipEmptyParts
- const QStringList subDirs = url.path().mid(pos).split(QDir::separator(), QString::SkipEmptyParts);
+ const QStringList subDirs = url.path().mid(pos).split(QDir::separator(), Qt::SkipEmptyParts);
for (int i = 0; i < subDirs.count() - 1; ++i) {
QString path = urlToExpand.path();
if (!path.endsWith(QLatin1Char('/'))) {
return m_filter.mimeTypes();
}
-
void KFileItemModel::applyFilters()
{
- // Check which shown items from m_itemData must get
- // hidden and hence moved to m_filteredItems.
- QVector<int> newFilteredIndexes;
+ // ===STEP 1===
+ // Check which previously shown items from m_itemData must now get
+ // hidden and hence moved from m_itemData into m_filteredItems.
- const int itemCount = m_itemData.count();
- for (int index = 0; index < itemCount; ++index) {
- ItemData* itemData = m_itemData.at(index);
-
- // 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)) {
- newFilteredIndexes.append(index);
- m_filteredItems.insert(item, itemData);
- }
+ QList<int> newFilteredIndexes; // This structure is good for prepending. We will want an ascending sorted Container at the end, this will do fine.
+
+ // This pointer will refer to the next confirmed shown item from the point of
+ // view of the current "itemData" in the upcoming "for" loop.
+ ItemData *itemShownBelow = nullptr;
+
+ // We will iterate backwards because it's convenient to know beforehand if the item just below is its child or not.
+ for (int index = m_itemData.count() - 1; index >= 0; --index) {
+ ItemData *itemData = m_itemData.at(index);
+
+ if (m_filter.matches(itemData->item)
+ || (itemShownBelow && itemShownBelow->parent == itemData && itemData->values.value("isExpanded").toBool())) {
+ // We could've entered here for two reasons:
+ // 1. This item passes the filter itself
+ // 2. This is an expanded folder that doesn't pass the filter but sees a filter-passing child just below
+
+ // So this item must remain shown.
+ // Lets register this item as the next shown item from the point of view of the next iteration of this for loop
+ itemShownBelow = itemData;
+ } else {
+ // We hide this item for now, however, for expanded folders this is not final:
+ // if after the next "for" loop we discover that its children must now be shown with the newly applied fliter, we shall re-insert it
+ newFilteredIndexes.prepend(index);
+ m_filteredItems.insert(itemData->item, itemData);
+ // indexShownBelow doesn't get updated since this item will be hidden
}
}
- const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
- removeItems(removedRanges, KeepItemData);
+ // This will remove the newly filtered items from m_itemData
+ removeItems(KItemRangeList::fromSortedContainer(newFilteredIndexes), KeepItemData);
+ // ===STEP 2===
// Check which hidden items from m_filteredItems should
- // get visible again and hence removed from m_filteredItems.
- QList<ItemData*> newVisibleItems;
+ // become visible again and hence moved from m_filteredItems back into m_itemData.
- QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+ QList<ItemData *> newVisibleItems;
+
+ QHash<KFileItem, ItemData *> ancestorsOfNewVisibleItems; // We will make sure these also become visible in step 3.
+
+ QHash<KFileItem, ItemData *>::iterator it = m_filteredItems.begin();
while (it != m_filteredItems.end()) {
if (m_filter.matches(it.key())) {
newVisibleItems.append(it.value());
+
+ // If this is a child of an expanded folder, we must make sure that its whole parental chain will also be shown.
+ // We will go up through its parental chain until we either:
+ // 1 - reach the "root item" of the current view, i.e the currently opened folder on Dolphin. Their children have their ItemData::parent set to nullptr.
+ // or
+ // 2 - we reach an unfiltered parent or a previously discovered ancestor.
+ for (ItemData *parent = it.value()->parent; parent && !ancestorsOfNewVisibleItems.contains(parent->item) && m_filteredItems.contains(parent->item);
+ parent = parent->parent) {
+ // We wish we could remove this parent from m_filteredItems right now, but we are iterating over it
+ // and it would mess up the iteration. We will mark it to be removed in step 3.
+ ancestorsOfNewVisibleItems.insert(parent->item, parent);
+ }
+
it = m_filteredItems.erase(it);
} else {
+ // Item remains filtered for now
+ // However, for expanded folders this is not final, we may discover later that it has unfiltered descendants.
++it;
}
}
+ // ===STEP 3===
+ // Handles the ancestorsOfNewVisibleItems.
+ // Now that we are done iterating through m_filteredItems we can safely move the ancestorsOfNewVisibleItems from m_filteredItems to newVisibleItems.
+ for (it = ancestorsOfNewVisibleItems.begin(); it != ancestorsOfNewVisibleItems.end(); it++) {
+ if (m_filteredItems.remove(it.key())) {
+ // m_filteredItems still contained this ancestor until now so we can be sure that we aren't adding a duplicate ancestor to newVisibleItems.
+ newVisibleItems.append(it.value());
+ }
+ }
+
+ // This will insert the newly discovered unfiltered items into m_itemData
insertItems(newVisibleItems);
}
}
QSet<ItemData*> parents;
- foreach (const KItemRange& range, itemRanges) {
+ for (const KItemRange& range : itemRanges) {
for (int index = range.index; index < range.index + range.count; ++index) {
parents.insert(m_itemData.at(index));
}
// been moved because of the resorting.
QList<QUrl> oldUrls;
oldUrls.reserve(itemCount);
- foreach (const ItemData* itemData, m_itemData) {
+ for (const ItemData* itemData : qAsConst(m_itemData)) {
oldUrls.append(itemData->item.url());
}
movedToIndexes.append(newIndex);
}
- emit itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes);
+ Q_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();
+ Q_EMIT groupsChanged();
}
}
// Note that the parent folder must be expanded before any of its subfolders become visible.
// Therefore, some URLs in m_restoredExpandedUrls might not be visible yet
// -> we expand the first visible URL we find in m_restoredExpandedUrls.
- foreach (const QUrl& url, m_urlsToExpand) {
+ // Iterate over a const copy because items are deleted and inserted within the loop
+ const auto urlsToExpand = m_urlsToExpand;
+ for(const QUrl &url : urlsToExpand) {
const int indexForUrl = index(url);
if (indexForUrl >= 0) {
m_urlsToExpand.remove(url);
m_urlsToExpand.clear();
}
- emit directoryLoadingCompleted();
+ Q_EMIT directoryLoadingCompleted();
}
void KFileItemModel::slotCanceled()
m_maximumUpdateIntervalTimer->stop();
dispatchPendingItemsToInsert();
- emit directoryLoadingCanceled();
+ Q_EMIT directoryLoadingCanceled();
}
void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemList& items)
}
}
- QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+ const QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
if (!m_filter.hasSetFilters()) {
m_pendingItemsToInsert.append(itemDataList);
// The name or type filter is active. Hide filtered items
// before inserting them into the model and remember
// the filtered items in m_filteredItems.
- foreach (ItemData* itemData, itemDataList) {
+ for (ItemData* itemData : itemDataList) {
if (m_filter.matches(itemData->item)) {
m_pendingItemsToInsert.append(itemData);
} else {
// emitted during the maximum update interval.
m_maximumUpdateIntervalTimer->start();
}
+
+ Q_EMIT fileItemsChanged({KFileItem(directoryUrl)});
}
void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
QVector<int> indexesToRemove;
indexesToRemove.reserve(items.count());
+ KFileItemList dirsChanged;
- foreach (const KFileItem& item, items) {
+ for (const KFileItem& item : items) {
const int indexForItem = index(item);
if (indexForItem >= 0) {
indexesToRemove.append(indexForItem);
m_filteredItems.erase(it);
}
}
+
+ QUrl parentUrl = item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
+ if (dirsChanged.findByUrl(parentUrl).isNull()) {
+ dirsChanged << KFileItem(parentUrl);
+ }
}
std::sort(indexesToRemove.begin(), indexesToRemove.end());
indexesToRemoveWithChildren.reserve(m_itemData.count());
const int itemCount = m_itemData.count();
- foreach (int index, indexesToRemove) {
+ for (int index : qAsConst(indexesToRemove)) {
indexesToRemoveWithChildren.append(index);
const int parentLevel = expandedParentsCount(index);
const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
removeFilteredChildren(itemRanges);
removeItems(itemRanges, DeleteItemData);
+
+ Q_EMIT fileItemsChanged(dirsChanged);
}
void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
indexes.reserve(items.count());
QSet<QByteArray> changedRoles;
+ KFileItemList changedFiles;
+
+ // Contains the indexes of the currently visible items
+ // that should get hidden and hence moved to m_filteredItems.
+ QVector<int> newFilteredIndexes;
+
+ // Contains currently hidden items that should
+ // get visible and hence removed from m_filteredItems
+ QList<ItemData*> newVisibleItems;
QListIterator<QPair<KFileItem, KFileItem> > it(items);
while (it.hasNext()) {
const KFileItem& oldItem = itemPair.first;
const KFileItem& newItem = itemPair.second;
const int indexForItem = index(oldItem);
+ const bool newItemMatchesFilter = m_filter.matches(newItem);
if (indexForItem >= 0) {
m_itemData[indexForItem]->item = newItem;
// 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, m_itemData.at(indexForItem)->parent));
- QHash<QByteArray, QVariant>& values = m_itemData[indexForItem]->values;
+ ItemData * const itemData = m_itemData.at(indexForItem);
+ QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, itemData->parent));
while (it.hasNext()) {
it.next();
const QByteArray& role = it.key();
- if (values.value(role) != it.value()) {
- values.insert(role, it.value());
+ if (itemData->values.value(role) != it.value()) {
+ itemData->values.insert(role, it.value());
changedRoles.insert(role);
}
}
m_items.remove(oldItem.url());
- m_items.insert(newItem.url(), indexForItem);
- indexes.append(indexForItem);
+ if (newItemMatchesFilter) {
+ m_items.insert(newItem.url(), indexForItem);
+ changedFiles.append(newItem);
+ indexes.append(indexForItem);
+ } else {
+ newFilteredIndexes.append(indexForItem);
+ m_filteredItems.insert(newItem, itemData);
+ }
} else {
// Check if 'oldItem' is one of the filtered items.
QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
itemData->values.clear();
m_filteredItems.erase(it);
- m_filteredItems.insert(newItem, itemData);
+ if (newItemMatchesFilter) {
+ newVisibleItems.append(itemData);
+ } else {
+ m_filteredItems.insert(newItem, itemData);
+ }
}
}
}
+ // Hide items, previously visible that should get hidden
+ const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+ removeItems(removedRanges, KeepItemData);
+
+ // Show previously hidden items that should get visible
+ insertItems(newVisibleItems);
+
// If the changed items have been created recently, they might not be in m_items yet.
// In that case, the list 'indexes' might be empty.
if (indexes.isEmpty()) {
std::sort(indexes.begin(), indexes.end());
const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
+
+ Q_EMIT fileItemsChanged(changedFiles);
}
void KFileItemModel::slotClear()
qDeleteAll(m_itemData);
m_itemData.clear();
m_items.clear();
- emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
+ Q_EMIT itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
}
m_expandedDirs.clear();
m_groups.clear();
prepareItemsForSorting(newItems);
- 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());
+ // 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 using QString::operator<(), which is quite fast.
+ if (m_naturalSorting) {
+ if (m_sortRole == NameRole) {
+ parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount());
+ } else if (isRoleValueNatural(m_sortRole)) {
+ auto lambdaLessThan = [&] (const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b)
+ {
+ const QByteArray role = roleForType(m_sortRole);
+ return a->values.value(role).toString() < b->values.value(role).toString();
+ };
+ parallelMergeSort(newItems.begin(), newItems.end(), lambdaLessThan, QThread::idealThreadCount());
+ }
}
sort(newItems.begin(), newItems.end());
// It will be re-populated with the updated indices if index(const QUrl&) is called.
m_items.clear();
- emit itemsInserted(itemRanges);
+ Q_EMIT itemsInserted(itemRanges);
#ifdef KFILEITEMMODEL_DEBUG
qCDebug(DolphinDebug) << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
// Step 1: Remove the items from m_itemData, and free the ItemData.
int removedItemsCount = 0;
- foreach (const KItemRange& range, itemRanges) {
+ for (const KItemRange& range : itemRanges) {
removedItemsCount += range.count;
for (int index = range.index; index < range.index + range.count; ++index) {
// It will be re-populated with the updated indices if index(const QUrl&) is called.
m_items.clear();
- emit itemsRemoved(itemRanges);
+ Q_EMIT itemsRemoved(itemRanges);
}
QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl& parentUrl, const KFileItemList& items) const
QList<ItemData*> itemDataList;
itemDataList.reserve(items.count());
- foreach (const KFileItem& item, items) {
+ for (const KFileItem& item : items) {
ItemData* itemData = new ItemData();
itemData->item = item;
itemData->parent = parentItem;
case DeletionTimeRole:
// These roles can be determined with retrieveData, and they have to be stored
// in the QHash "values" for the sorting.
- foreach (ItemData* itemData, itemDataList) {
+ for (ItemData* itemData : qAsConst(itemDataList)) {
if (itemData->values.isEmpty()) {
itemData->values = retrieveData(itemData->item, itemData->parent);
}
case TypeRole:
// At least store the data including the file type for items with known MIME type.
- foreach (ItemData* itemData, itemDataList) {
+ for (ItemData* itemData : qAsConst(itemDataList)) {
if (itemData->values.isEmpty()) {
const KFileItem item = itemData->item;
if (item.isDir() || item.isMimeTypeKnown()) {
void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet<QByteArray>& changedRoles)
{
- emit itemsChanged(itemRanges, changedRoles);
+ Q_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) {
+ for (const KItemRange& range : itemRanges) {
bool needsResorting = false;
const int first = range.index;
}
if (item.isMimeTypeKnown()) {
- data.insert(sharedValue("iconName"), item.iconName());
+ QString iconName = item.iconName();
+ if (!QIcon::hasThemeIcon(iconName)) {
+ QMimeType mimeType = QMimeDatabase().mimeTypeForName(item.mimetype());
+ iconName = mimeType.genericIconName();
+ }
+
+ data.insert(sharedValue("iconName"), iconName);
if (m_requestRole[TypeRole]) {
data.insert(sharedValue("type"), item.mimeComment());
}
}
- if (m_sortDirsFirst || m_sortRole == SizeRole) {
+ // Show hidden files and folders last
+ if (m_sortHiddenLast) {
+ const bool isHiddenA = a->item.isHidden();
+ const bool isHiddenB = b->item.isHidden();
+ if (isHiddenA && !isHiddenB) {
+ return false;
+ } else if (!isHiddenA && isHiddenB) {
+ return true;
+ }
+ }
+
+ if (m_sortDirsFirst || (DetailsModeSettings::directorySizeCount() && m_sortRole == SizeRole)) {
const bool isDirA = a->item.isDir();
const bool isDirB = b->item.isDir();
if (isDirA && !isDirB) {
return lessThan(a, b, m_collator);
};
- if (m_sortRole == NameRole) {
- // Sorting by name can be expensive, in particular if natural sorting is
+ if (m_sortRole == NameRole || isRoleValueNatural(m_sortRole)) {
+ // Sorting by string can be expensive, in particular if natural sorting is
// enabled. Use all CPU cores to speed up the sorting process.
static const int numberOfThreads = QThread::idealThreadCount();
parallelMergeSort(begin, end, lambdaLessThan, numberOfThreads);
int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const QCollator& collator) const
{
+ // This function must never return 0, because that would break stable
+ // sorting, which leads to all kinds of bugs.
+ // See: https://bugs.kde.org/show_bug.cgi?id=433247
+ // If two items have equal sort values, let the fallbacks at the bottom of
+ // the function handle it.
const KFileItem& itemA = a->item;
const KFileItem& itemB = b->item;
break;
case SizeRole: {
- if (itemA.isDir()) {
- // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
- Q_ASSERT(itemB.isDir());
-
- const QVariant valueA = a->values.value("size");
- const QVariant valueB = b->values.value("size");
- if (valueA.isNull() && valueB.isNull()) {
- result = 0;
- } else if (valueA.isNull()) {
- result = -1;
+ if (DetailsModeSettings::directorySizeCount() && itemA.isDir()) {
+ // folders first then
+ // items A and B are folders thanks to lessThan checks
+ auto valueA = a->values.value("count");
+ auto valueB = b->values.value("count");
+ if (valueA.isNull()) {
+ if (!valueB.isNull()) {
+ return -1;
+ }
} else if (valueB.isNull()) {
- result = +1;
+ return +1;
} else {
- result = valueA.toInt() - valueB.toInt();
+ if (valueA.toLongLong() < valueB.toLongLong()) {
+ return -1;
+ } else if (valueA.toLongLong() > valueB.toLongLong()) {
+ return +1;
+ }
}
+ break;
+ }
+
+ KIO::filesize_t sizeA = 0;
+ if (itemA.isDir()) {
+ sizeA = a->values.value("size").toULongLong();
} else {
- // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
- Q_ASSERT(!itemB.isDir());
- const KIO::filesize_t sizeA = itemA.size();
- const KIO::filesize_t sizeB = itemB.size();
- if (sizeA > sizeB) {
- result = +1;
- } else if (sizeA < sizeB) {
- result = -1;
- } else {
- result = 0;
- }
+ sizeA = itemA.size();
+ }
+ KIO::filesize_t sizeB = 0;
+ if (itemB.isDir()) {
+ sizeB = b->values.value("size").toULongLong();
+ } else {
+ sizeB = itemB.size();
+ }
+ if (sizeA < sizeB) {
+ return -1;
+ } else if (sizeA > sizeB) {
+ return +1;
}
break;
}
const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
if (dateTimeA < dateTimeB) {
- result = -1;
+ return -1;
} else if (dateTimeA > dateTimeB) {
- result = +1;
+ return +1;
}
break;
}
const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
if (dateTimeA < dateTimeB) {
- result = -1;
+ return -1;
} else if (dateTimeA > dateTimeB) {
- result = +1;
+ return +1;
}
break;
}
const QDateTime dateTimeA = a->values.value("deletiontime").toDateTime();
const QDateTime dateTimeB = b->values.value("deletiontime").toDateTime();
if (dateTimeA < dateTimeB) {
- result = -1;
+ return -1;
} else if (dateTimeA > dateTimeB) {
- result = +1;
+ return +1;
}
break;
}
const QString roleValueA = a->values.value(role).toString();
const QString roleValueB = b->values.value(role).toString();
if (!roleValueA.isEmpty() && roleValueB.isEmpty()) {
- result = -1;
+ return -1;
} else if (roleValueA.isEmpty() && !roleValueB.isEmpty()) {
- result = +1;
+ return +1;
+ } else if (isRoleValueNatural(m_sortRole)) {
+ result = stringCompare(roleValueA, roleValueB, collator);
} else {
result = QString::compare(roleValueA, roleValueB);
}
int KFileItemModel::stringCompare(const QString& a, const QString& b, const QCollator& collator) const
{
+ QMutexLocker collatorLock(s_collatorMutex());
+
if (m_naturalSorting) {
return collator.compare(a, b);
}
}
const KFileItem& item = m_itemData.at(i)->item;
- const KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
+ KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
QString newGroupValue;
if (!item.isNull() && item.isDir()) {
- newGroupValue = i18nc("@title:group Size", "Folders");
- } else if (fileSize < 5 * 1024 * 1024) {
- newGroupValue = i18nc("@title:group Size", "Small");
- } else if (fileSize < 10 * 1024 * 1024) {
- newGroupValue = i18nc("@title:group Size", "Medium");
- } else {
- newGroupValue = i18nc("@title:group Size", "Big");
+ if (DetailsModeSettings::directorySizeCount() || m_sortDirsFirst) {
+ newGroupValue = i18nc("@title:group Size", "Folders");
+ } else {
+ fileSize = m_itemData.at(i)->values.value("size").toULongLong();
+ }
+ }
+
+ if (newGroupValue.isEmpty()) {
+ if (fileSize < 5 * 1024 * 1024) { // < 5 MB
+ newGroupValue = i18nc("@title:group Size", "Small");
+ } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB
+ newGroupValue = i18nc("@title:group Size", "Medium");
+ } else {
+ newGroupValue = i18nc("@title:group Size", "Big");
+ }
}
if (newGroupValue != groupValue) {
if (daysDistance == 1) {
const KLocalizedString format = ki18nc("@title:group Date: "
"MMMM is full month name in current locale, and yyyy is "
- "full year number", "'Yesterday' (MMMM, yyyy)");
+ "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Yesterday' (MMMM, yyyy)");
const QString translatedFormat = format.toString();
if (translatedFormat.count(QLatin1Char('\'')) == 2) {
newGroupValue = fileTime.toString(translatedFormat);
} else if (daysDistance <= 7) {
newGroupValue = fileTime.toString(i18nc("@title:group Date: "
"The week day name: dddd, MMMM is full month name "
- "in current locale, and yyyy is full year number",
+ "in current locale, and yyyy is full year number.",
"dddd (MMMM, yyyy)"));
newGroupValue = i18nc("Can be used to script translation of "
"\"dddd (MMMM, yyyy)\" with context @title:group Date",
} else if (daysDistance <= 7 * 2) {
const KLocalizedString format = ki18nc("@title:group Date: "
"MMMM is full month name in current locale, and yyyy is "
- "full year number", "'One Week Ago' (MMMM, yyyy)");
+ "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'One Week Ago' (MMMM, yyyy)");
const QString translatedFormat = format.toString();
if (translatedFormat.count(QLatin1Char('\'')) == 2) {
newGroupValue = fileTime.toString(translatedFormat);
} else if (daysDistance <= 7 * 3) {
const KLocalizedString format = ki18nc("@title:group Date: "
"MMMM is full month name in current locale, and yyyy is "
- "full year number", "'Two Weeks Ago' (MMMM, yyyy)");
+ "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Two Weeks Ago' (MMMM, yyyy)");
const QString translatedFormat = format.toString();
if (translatedFormat.count(QLatin1Char('\'')) == 2) {
newGroupValue = fileTime.toString(translatedFormat);
} else if (daysDistance <= 7 * 4) {
const KLocalizedString format = ki18nc("@title:group Date: "
"MMMM is full month name in current locale, and yyyy is "
- "full year number", "'Three Weeks Ago' (MMMM, yyyy)");
+ "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Three Weeks Ago' (MMMM, yyyy)");
const QString translatedFormat = format.toString();
if (translatedFormat.count(QLatin1Char('\'')) == 2) {
newGroupValue = fileTime.toString(translatedFormat);
} else {
const KLocalizedString format = ki18nc("@title:group Date: "
"MMMM is full month name in current locale, and yyyy is "
- "full year number", "'Earlier on' MMMM, yyyy");
+ "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Earlier on' MMMM, yyyy");
const QString translatedFormat = format.toString();
if (translatedFormat.count(QLatin1Char('\'')) == 2) {
newGroupValue = fileTime.toString(translatedFormat);
resortAllItems();
}
- emit directorySortingProgress(100);
+ Q_EMIT directorySortingProgress(100);
} else if (itemCount > 0) {
resolvedCount = qBound(0, resolvedCount, itemCount);
const int progress = resolvedCount * 100 / itemCount;
if (m_sortingProgressPercent != progress) {
m_sortingProgressPercent = progress;
- emit directorySortingProgress(progress);
+ Q_EMIT directorySortingProgress(progress);
}
}
}
{
QElapsedTimer timer;
timer.start();
- foreach (const KFileItem& item, items) { // krazy:exclude=foreach
+ for (const KFileItem& item : items) {
// 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
return true;
}
+
+void KFileItemModel::slotListerError(KIO::Job *job)
+{
+ if (job->error() == KIO::ERR_IS_FILE) {
+ if (auto *listJob = qobject_cast<KIO::ListJob *>(job)) {
+ Q_EMIT urlIsFileError(listJob->url());
+ }
+ } else {
+ const QString errorString = job->errorString();
+ Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error."));
+ }
+}