#include "private/kfileitemmodeldirlister.h"
#include "private/kfileitemmodelsortalgorithm.h"
+#include <kio_version.h>
#include <KLocalizedString>
#include <KUrlMimeData>
#include <QElapsedTimer>
#include <QMimeData>
+#include <QMimeDatabase>
#include <QTimer>
#include <QWidget>
-#include <QMutex>
+#include <QRecursiveMutex>
+#include <QIcon>
-Q_GLOBAL_STATIC_WITH_ARGS(QMutex, s_collatorMutex, (QMutex::Recursive))
+Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
// #define KFILEITEMMODEL_DEBUG
KItemModelBase("text", parent),
m_dirLister(nullptr),
m_sortDirsFirst(true),
+ m_sortHiddenLast(true),
m_sortRole(NameRole),
m_sortingProgressPercent(-1),
m_roles(),
connect(m_dirLister, &KFileItemModelDirLister::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, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection);
connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError);
+#if KIO_VERSION < QT_VERSION_CHECK(5, 79, 0)
+ connect(m_dirLister, QOverload<const QUrl&>::of(&KCoreDirLister::completed), this, &KFileItemModel::slotCompleted);
+#else
+ connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted);
+#endif
+
// Apply default roles that should be determined
resetRoles();
m_requestRole[NameRole] = true;
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);
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 {
}
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;
QListIterator<QPair<KFileItem, KFileItem> > it(items);
while (it.hasNext()) {
m_items.remove(oldItem.url());
m_items.insert(newItem.url(), indexForItem);
+ changedFiles.append(newItem);
indexes.append(indexForItem);
} else {
// Check if 'oldItem' is one of the filtered items.
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();
// 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) {
break;
case SizeRole: {
- if (itemA.isDir()) {
- // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
- Q_ASSERT(itemB.isDir());
-
- QVariant valueA, valueB;
- if (DetailsModeSettings::directorySizeCount()) {
- valueA = a->values.value("count");
- valueB = b->values.value("count");
- } else {
- // use dir size then
- valueA = a->values.value("size");
- 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()) {
+ result = 0;
+ break;
+ } else {
+ result = -1;
+ break;
+ }
} else if (valueB.isNull()) {
result = +1;
+ break;
} else {
if (valueA.toLongLong() < valueB.toLongLong()) {
- return -1;
+ result = -1;
+ break;
+ } else if (valueA.toLongLong() > valueB.toLongLong()) {
+ result = +1;
+ break;
} else {
- return +1;
+ result = 0;
+ 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) {
+ result = +1;
+ } else if (sizeA < sizeB) {
+ result = -1;
+ } else {
+ result = 0;
}
break;
}
}
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