┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews
diff options
context:
space:
mode:
Diffstat (limited to 'src/kitemviews')
-rw-r--r--src/kitemviews/kfileitemlisttostring.cpp2
-rw-r--r--src/kitemviews/kfileitemlisttostring.h2
-rw-r--r--src/kitemviews/kfileitemlistview.cpp15
-rw-r--r--src/kitemviews/kfileitemlistview.h8
-rw-r--r--src/kitemviews/kfileitemlistwidget.cpp14
-rw-r--r--src/kitemviews/kfileitemmodel.cpp51
-rw-r--r--src/kitemviews/kfileitemmodel.h11
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.cpp88
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.h6
-rw-r--r--src/kitemviews/kitemlistcontroller.cpp35
-rw-r--r--src/kitemviews/kitemlistwidget.cpp2
-rw-r--r--src/kitemviews/kitemset.h93
-rw-r--r--src/kitemviews/private/kdirectorycontentscounter.cpp279
-rw-r--r--src/kitemviews/private/kdirectorycontentscounter.h18
-rw-r--r--src/kitemviews/private/kdirectorycontentscounterworker.cpp183
-rw-r--r--src/kitemviews/private/kdirectorycontentscounterworker.h33
-rw-r--r--src/kitemviews/private/kfileitemmodelfilter.cpp32
-rw-r--r--src/kitemviews/private/kfileitemmodelfilter.h8
18 files changed, 596 insertions, 284 deletions
diff --git a/src/kitemviews/kfileitemlisttostring.cpp b/src/kitemviews/kfileitemlisttostring.cpp
index d10680adc..9f281c813 100644
--- a/src/kitemviews/kfileitemlisttostring.cpp
+++ b/src/kitemviews/kfileitemlisttostring.cpp
@@ -1,6 +1,6 @@
/*
This file is part of the KDE project
- SPDX-FileCopyrightText: 2022 Felix Ernst <[email protected]>
+ SPDX-FileCopyrightText: 2022 Felix Ernst <[email protected]>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
diff --git a/src/kitemviews/kfileitemlisttostring.h b/src/kitemviews/kfileitemlisttostring.h
index f1c2902b8..1cba1a43c 100644
--- a/src/kitemviews/kfileitemlisttostring.h
+++ b/src/kitemviews/kfileitemlisttostring.h
@@ -1,6 +1,6 @@
/*
This file is part of the KDE project
- SPDX-FileCopyrightText: 2022 Felix Ernst <[email protected]>
+ SPDX-FileCopyrightText: 2022 Felix Ernst <[email protected]>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp
index e2a27a5ea..668ebdfb2 100644
--- a/src/kitemviews/kfileitemlistview.cpp
+++ b/src/kitemviews/kfileitemlistview.cpp
@@ -41,7 +41,6 @@ KFileItemListView::KFileItemListView(QGraphicsWidget *parent)
, m_modelRolesUpdater(nullptr)
, m_updateVisibleIndexRangeTimer(nullptr)
, m_updateIconSizeTimer(nullptr)
- , m_scanDirectories(true)
{
setAcceptDrops(true);
@@ -119,19 +118,6 @@ qlonglong KFileItemListView::localFileSizePreviewLimit() const
return m_modelRolesUpdater ? m_modelRolesUpdater->localFileSizePreviewLimit() : 0;
}
-void KFileItemListView::setScanDirectories(bool enabled)
-{
- m_scanDirectories = enabled;
- if (m_modelRolesUpdater) {
- m_modelRolesUpdater->setScanDirectories(m_scanDirectories);
- }
-}
-
-bool KFileItemListView::scanDirectories()
-{
- return m_scanDirectories;
-}
-
QPixmap KFileItemListView::createDragPixmap(const KItemSet &indexes) const
{
if (!model()) {
@@ -269,7 +255,6 @@ void KFileItemListView::onModelChanged(KItemModelBase *current, KItemModelBase *
if (current) {
m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel *>(current), this);
m_modelRolesUpdater->setIconSize(availableIconSize());
- m_modelRolesUpdater->setScanDirectories(scanDirectories());
applyRolesToModel();
}
diff --git a/src/kitemviews/kfileitemlistview.h b/src/kitemviews/kfileitemlistview.h
index 63bcf9e75..b4be0093e 100644
--- a/src/kitemviews/kfileitemlistview.h
+++ b/src/kitemviews/kfileitemlistview.h
@@ -71,13 +71,6 @@ public:
void setLocalFileSizePreviewLimit(qlonglong size);
qlonglong localFileSizePreviewLimit() const;
- /**
- * If set to true, directories contents are scanned to determine their size
- * Default true
- */
- void setScanDirectories(bool enabled);
- bool scanDirectories();
-
QPixmap createDragPixmap(const KItemSet &indexes) const override;
/**
@@ -137,7 +130,6 @@ private:
KFileItemModelRolesUpdater *m_modelRolesUpdater;
QTimer *m_updateVisibleIndexRangeTimer;
QTimer *m_updateIconSizeTimer;
- bool m_scanDirectories;
friend class KFileItemListViewTest; // For unit testing
};
diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp
index d9644bef5..385067af0 100644
--- a/src/kitemviews/kfileitemlistwidget.cpp
+++ b/src/kitemviews/kfileitemlistwidget.cpp
@@ -9,7 +9,7 @@
#include "kfileitemmodel.h"
#include "kitemlistview.h"
-#include "dolphin_detailsmodesettings.h"
+#include "dolphin_contentdisplaysettings.h"
#include <KFormat>
#include <KLocalizedString>
@@ -56,7 +56,7 @@ QString KFileItemListWidgetInformant::roleText(const QByteArray &role, const QHa
// use a hash + switch for a linear runtime.
auto formatDate = [formatter, local](const QDateTime &time) {
- if (DetailsModeSettings::useShortRelativeDates()) {
+ if (ContentDisplaySettings::useShortRelativeDates()) {
return formatter.formatRelativeDateTime(time, QLocale::ShortFormat);
} else {
return local.toString(time, QLocale::ShortFormat);
@@ -67,7 +67,7 @@ QString KFileItemListWidgetInformant::roleText(const QByteArray &role, const QHa
if (values.value("isDir").toBool()) {
if (!roleValue.isNull() && roleValue != -1) {
// The item represents a directory.
- if (DetailsModeSettings::directorySizeCount()) {
+ if (ContentDisplaySettings::directorySizeCount() || roleValue == -2 /* size is invalid */) {
// Show the number of sub directories instead of the file size of the directory.
const int count = values.value("count").toInt();
text = i18ncp("@item:intable", "%1 item", "%1 items", count);
@@ -101,14 +101,14 @@ QString KFileItemListWidgetInformant::roleText(const QByteArray &role, const QHa
} else if (role == "permissions") {
const auto permissions = roleValue.value<QVariantList>();
- switch (DetailsModeSettings::usePermissionsFormat()) {
- case DetailsModeSettings::EnumUsePermissionsFormat::SymbolicFormat:
+ switch (ContentDisplaySettings::usePermissionsFormat()) {
+ case ContentDisplaySettings::EnumUsePermissionsFormat::SymbolicFormat:
text = permissions.at(0).toString();
break;
- case DetailsModeSettings::EnumUsePermissionsFormat::NumericFormat:
+ case ContentDisplaySettings::EnumUsePermissionsFormat::NumericFormat:
text = QString::number(permissions.at(1).toInt(), 8);
break;
- case DetailsModeSettings::EnumUsePermissionsFormat::CombinedFormat:
+ case ContentDisplaySettings::EnumUsePermissionsFormat::CombinedFormat:
text = QString("%1 (%2)").arg(permissions.at(0).toString()).arg(permissions.at(1).toInt(), 0, 8);
break;
}
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp
index 4b06525f5..28e0876b9 100644
--- a/src/kitemviews/kfileitemmodel.cpp
+++ b/src/kitemviews/kfileitemmodel.cpp
@@ -8,7 +8,7 @@
#include "kfileitemmodel.h"
-#include "dolphin_detailsmodesettings.h"
+#include "dolphin_contentdisplaysettings.h"
#include "dolphin_generalsettings.h"
#include "dolphindebug.h"
#include "private/kfileitemmodelsortalgorithm.h"
@@ -104,6 +104,8 @@ KFileItemModel::KFileItemModel(QObject *parent)
connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems);
connect(GeneralSettings::self(), &GeneralSettings::sortingChoiceChanged, this, &KFileItemModel::slotSortingChoiceChanged);
+
+ setShowTrashMime(m_dirLister->showHiddenFiles());
}
KFileItemModel::~KFileItemModel()
@@ -128,6 +130,8 @@ void KFileItemModel::refreshDirectory(const QUrl &url)
}
m_dirLister->openUrl(url, KDirLister::Reload);
+
+ Q_EMIT directoryRefreshing();
}
QUrl KFileItemModel::directory() const
@@ -235,9 +239,31 @@ bool KFileItemModel::sortHiddenLast() const
return m_sortHiddenLast;
}
+void KFileItemModel::setShowTrashMime(bool show)
+{
+ const auto trashMime = QStringLiteral("application/x-trash");
+ QStringList excludeFilter = m_filter.excludeMimeTypes();
+ bool wasShown = !excludeFilter.contains(trashMime);
+
+ if (show) {
+ if (!wasShown) {
+ excludeFilter.removeAll(trashMime);
+ }
+ } else {
+ if (wasShown) {
+ excludeFilter.append(trashMime);
+ }
+ }
+
+ if (wasShown != show) {
+ setExcludeMimeTypeFilter(excludeFilter);
+ }
+}
+
void KFileItemModel::setShowHiddenFiles(bool show)
{
m_dirLister->setShowHiddenFiles(show);
+ setShowTrashMime(show);
m_dirLister->emitChanges();
if (show) {
dispatchPendingItemsToInsert();
@@ -725,6 +751,20 @@ QStringList KFileItemModel::mimeTypeFilters() const
return m_filter.mimeTypes();
}
+void KFileItemModel::setExcludeMimeTypeFilter(const QStringList &filters)
+{
+ if (m_filter.excludeMimeTypes() != filters) {
+ dispatchPendingItemsToInsert();
+ m_filter.setExcludeMimeTypes(filters);
+ applyFilters();
+ }
+}
+
+QStringList KFileItemModel::excludeMimeTypeFilter() const
+{
+ return m_filter.excludeMimeTypes();
+}
+
void KFileItemModel::applyFilters()
{
// ===STEP 1===
@@ -1808,7 +1848,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem &item,
}
if (m_requestRole[IsHiddenRole]) {
- data.insert(sharedValue("isHidden"), item.isHidden());
+ data.insert(sharedValue("isHidden"), item.isHidden() || item.mimetype() == QStringLiteral("application/x-trash"));
}
if (m_requestRole[NameRole]) {
@@ -1816,6 +1856,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem &item,
}
if (m_requestRole[ExtensionRole] && !isDir) {
+ // TODO KF6 use KFileItem::suffix 464722
data.insert(sharedValue("extension"), QFileInfo(item.name()).suffix());
}
@@ -1975,7 +2016,7 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla
}
}
- if (m_sortDirsFirst || (DetailsModeSettings::directorySizeCount() && m_sortRole == SizeRole)) {
+ if (m_sortDirsFirst || (ContentDisplaySettings::directorySizeCount() && m_sortRole == SizeRole)) {
const bool isDirA = a->item.isDir();
const bool isDirB = b->item.isDir();
if (isDirA && !isDirB) {
@@ -2027,7 +2068,7 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const
break;
case SizeRole: {
- if (DetailsModeSettings::directorySizeCount() && itemA.isDir()) {
+ if (ContentDisplaySettings::directorySizeCount() && itemA.isDir()) {
// folders first then
// items A and B are folders thanks to lessThan checks
auto valueA = a->values.value("count");
@@ -2287,7 +2328,7 @@ QList<QPair<int, QVariant>> KFileItemModel::sizeRoleGroups() const
KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
QString newGroupValue;
if (!item.isNull() && item.isDir()) {
- if (DetailsModeSettings::directorySizeCount() || m_sortDirsFirst) {
+ if (ContentDisplaySettings::directorySizeCount() || m_sortDirsFirst) {
newGroupValue = i18nc("@title:group Size", "Folders");
} else {
fileSize = m_itemData.at(i)->values.value("size").toULongLong();
diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h
index afcd633b0..3c2721d8f 100644
--- a/src/kitemviews/kfileitemmodel.h
+++ b/src/kitemviews/kfileitemmodel.h
@@ -182,6 +182,9 @@ public:
void setMimeTypeFilters(const QStringList &filters);
QStringList mimeTypeFilters() const;
+ void setExcludeMimeTypeFilter(const QStringList &filters);
+ QStringList excludeMimeTypeFilter() const;
+
struct RoleInfo {
QByteArray role;
QString translation;
@@ -199,6 +202,9 @@ public:
*/
static QList<RoleInfo> rolesInformation();
+ /** set to true to hide application/x-trash files */
+ void setShowTrashMime(bool show);
+
Q_SIGNALS:
/**
* Is emitted if the loading of a directory has been started. It is
@@ -218,6 +224,11 @@ Q_SIGNALS:
void directoryLoadingCompleted();
/**
+ * Is emitted when the model is being refreshed (F5 key press)
+ */
+ void directoryRefreshing();
+
+ /**
* Is emitted after the loading of a directory has been canceled.
*/
void directoryLoadingCanceled();
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp
index 8d5656daf..09dd2eba1 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.cpp
+++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp
@@ -20,6 +20,8 @@
#include <KPluginMetaData>
#include <KSharedConfig>
+#include "dolphin_contentdisplaysettings.h"
+
#if HAVE_BALOO
#include "private/kbaloorolesprovider.h"
#include <Baloo/File>
@@ -72,7 +74,6 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel *model, QO
, m_resolvableRoles()
, m_enabledPlugins()
, m_localFileSizePreviewLimit(0)
- , m_scanDirectories(true)
, m_pendingSortRoleItems()
, m_pendingIndexes()
, m_pendingPreviewItems()
@@ -321,16 +322,6 @@ qlonglong KFileItemModelRolesUpdater::localFileSizePreviewLimit() const
return m_localFileSizePreviewLimit;
}
-void KFileItemModelRolesUpdater::setScanDirectories(bool enabled)
-{
- m_scanDirectories = enabled;
-}
-
-bool KFileItemModelRolesUpdater::scanDirectories() const
-{
- return m_scanDirectories;
-}
-
void KFileItemModelRolesUpdater::setHoverSequenceState(const QUrl &itemUrl, int seqIdx)
{
const KFileItem item = m_model->fileItem(itemUrl);
@@ -423,8 +414,7 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList &itemRang
m_hoverSequenceLoadedItems.clear();
killPreviewJob();
-
- if (m_scanDirectories) {
+ if (!m_model->showDirectoriesOnly()) {
m_directoryContentsCounter->stopWorker();
}
} else {
@@ -856,10 +846,10 @@ void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem &
#endif
}
-void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString &path, int count, long size)
+void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString &path, int count, long long size)
{
- const bool getSizeRole = m_roles.contains("size");
const bool getIsExpandableRole = m_roles.contains("isExpandable");
+ const bool getSizeRole = m_roles.contains("size");
if (getSizeRole || getIsExpandableRole) {
const int index = m_model->index(QUrl::fromLocalFile(path));
@@ -1278,18 +1268,71 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint)
void KFileItemModelRolesUpdater::startDirectorySizeCounting(const KFileItem &item, int index)
{
- if (item.isSlow()) {
+ if (ContentDisplaySettings::directorySizeCount() || item.isSlow() || !item.isLocalFile()) {
+ // fastpath no recursion necessary
+
+ auto data = m_model->data(index);
+ if (data.value("size") == -2) {
+ // means job already started
+ return;
+ }
+
+ auto url = item.url();
+ if (!item.localPath().isEmpty()) {
+ // optimization for desktop:/, trash:/
+ url = QUrl::fromLocalFile(item.localPath());
+ }
+
+ data.insert("isExpandable", false);
+ data.insert("count", 0);
+ data.insert("size", -2); // invalid size, -1 means size unknown
+
+ disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
+ m_model->setData(index, data);
+ connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
+
+ auto listJob = KIO::listDir(url);
+ QObject::connect(listJob, &KIO::ListJob::entries, this, [this, index](const KJob * /*job*/, const KIO::UDSEntryList &list) {
+ auto data = m_model->data(index);
+ int origCount = data.value("count").toInt();
+ int entryCount = origCount;
+
+ for (const KIO::UDSEntry &entry : list) {
+ const auto name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
+
+ if (name == QStringLiteral("..") || name == QStringLiteral(".")) {
+ continue;
+ }
+ if (!m_model->showHiddenFiles() && name.startsWith(QLatin1Char('.'))) {
+ continue;
+ }
+ if (m_model->showDirectoriesOnly() && !entry.isDir()) {
+ continue;
+ }
+ ++entryCount;
+ }
+
+ // count has changed
+ if (origCount < entryCount) {
+ QHash<QByteArray, QVariant> data;
+ data.insert("isExpandable", entryCount > 0);
+ data.insert("count", entryCount);
+
+ disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
+ m_model->setData(index, data);
+ connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
+ }
+ });
return;
}
// Tell m_directoryContentsCounter that we want to count the items
// inside the directory. The result will be received in slotDirectoryContentsCountReceived.
- if (m_scanDirectories && item.isLocalFile()) {
- const QString path = item.localPath();
- const auto priority = index >= m_firstVisibleIndex && index <= m_lastVisibleIndex ? KDirectoryContentsCounter::PathCountPriority::High
- : KDirectoryContentsCounter::PathCountPriority::Normal;
- m_directoryContentsCounter->scanDirectory(path, priority);
- }
+ const QString path = item.localPath();
+ const auto priority = index >= m_firstVisibleIndex && index <= m_lastVisibleIndex ? KDirectoryContentsCounter::PathCountPriority::High
+ : KDirectoryContentsCounter::PathCountPriority::Normal;
+
+ m_directoryContentsCounter->scanDirectory(path, priority);
}
QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem &item, int index)
@@ -1304,6 +1347,7 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte
}
if (m_roles.contains("extension")) {
+ // TODO KF6 use KFileItem::suffix 464722
data.insert("extension", QFileInfo(item.name()).suffix());
}
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h
index be6f23193..78fa757f8 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.h
+++ b/src/kitemviews/kfileitemmodelrolesupdater.h
@@ -260,7 +260,7 @@ private Q_SLOTS:
void applyChangedBalooRoles(const QString &file);
void applyChangedBalooRolesForItem(const KFileItem &file);
- void slotDirectoryContentsCountReceived(const QString &path, int count, long size);
+ void slotDirectoryContentsCountReceived(const QString &path, int count, long long size);
private:
/**
@@ -334,6 +334,9 @@ private:
void trimHoverSequenceLoadedItems();
private:
+ /**
+ * enqueue directory size counting for KFileItem item at index
+ */
void startDirectorySizeCounting(const KFileItem &item, int index);
enum State { Idle, Paused, ResolvingSortRole, ResolvingAllRoles, PreviewJobRunning };
@@ -370,7 +373,6 @@ private:
QSet<QByteArray> m_resolvableRoles;
QStringList m_enabledPlugins;
qulonglong m_localFileSizePreviewLimit;
- bool m_scanDirectories;
// Items for which the sort role still has to be determined.
QSet<KFileItem> m_pendingSortRoleItems;
diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp
index 2e7d2f057..74a631d8d 100644
--- a/src/kitemviews/kitemlistcontroller.cpp
+++ b/src/kitemviews/kitemlistcontroller.cpp
@@ -237,21 +237,34 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
{
int index = m_selectionManager->currentItem();
int key = event->key();
+ const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
// Handle the expanding/collapsing of items
- if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
- if (key == Qt::Key_Right) {
- if (m_model->setExpanded(index, true)) {
- return true;
+ // expand / collapse all selected directories
+ if (m_view->supportsItemExpanding() && m_model->isExpandable(index) && (key == Qt::Key_Right || key == Qt::Key_Left)) {
+ const bool expandOrCollapse = key == Qt::Key_Right ? true : false;
+ bool shouldReturn = m_model->setExpanded(index, expandOrCollapse);
+
+ // edit in reverse to preserve index of the first handled items
+ const auto selectedItems = m_selectionManager->selectedItems();
+ for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
+ shouldReturn |= m_model->setExpanded(*it, expandOrCollapse);
+ if (!shiftPressed) {
+ m_selectionManager->setSelected(*it);
}
- } else if (key == Qt::Key_Left) {
- if (m_model->setExpanded(index, false)) {
- return true;
+ }
+ if (shouldReturn) {
+ // update keyboard anchors
+ if (shiftPressed) {
+ m_keyboardAnchorIndex = selectedItems.count() > 0 ? qMin(index, selectedItems.last()) : index;
+ m_keyboardAnchorPos = keyboardAnchorPos(m_keyboardAnchorIndex);
}
+
+ event->ignore();
+ return true;
}
}
- const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
const bool controlPressed = event->modifiers() & Qt::ControlModifier;
const bool shiftOrControlPressed = shiftPressed || controlPressed;
const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up
@@ -327,11 +340,17 @@ bool KItemListController::keyPressEvent(QKeyEvent *event)
case Qt::Key_Up:
updateKeyboardAnchor();
+ if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
+ m_selectionManager->beginAnchoredSelection(index);
+ }
index = previousRowIndex(index);
break;
case Qt::Key_Down:
updateKeyboardAnchor();
+ if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
+ m_selectionManager->beginAnchoredSelection(index);
+ }
index = nextRowIndex(index);
break;
diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp
index 1d6b9641a..2c8ef70a5 100644
--- a/src/kitemviews/kitemlistwidget.cpp
+++ b/src/kitemviews/kitemlistwidget.cpp
@@ -40,6 +40,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI
, m_data()
, m_visibleRoles()
, m_columnWidths()
+ , m_sidePadding(0)
, m_styleOption()
, m_siblingsInfo()
, m_hoverOpacity(0)
@@ -49,7 +50,6 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI
, m_selectionToggle(nullptr)
, m_editedRole()
, m_iconSize(-1)
- , m_sidePadding(0)
{
connect(&m_hoverSequenceTimer, &QTimer::timeout, this, &KItemListWidget::slotHoverSequenceTimerTimeout);
}
diff --git a/src/kitemviews/kitemset.h b/src/kitemviews/kitemset.h
index fd73c0e02..b8ab6864d 100644
--- a/src/kitemviews/kitemset.h
+++ b/src/kitemviews/kitemset.h
@@ -50,7 +50,7 @@ public:
class iterator
{
- iterator(const KItemRangeList::iterator &rangeIt, int offset)
+ iterator(const KItemRangeList::iterator &rangeIt, int offset = 0)
: m_rangeIt(rangeIt)
, m_offset(offset)
{
@@ -135,7 +135,7 @@ public:
class const_iterator
{
- const_iterator(KItemRangeList::const_iterator rangeIt, int offset)
+ const_iterator(KItemRangeList::const_iterator rangeIt, int offset = 0)
: m_rangeIt(rangeIt)
, m_offset(offset)
{
@@ -223,6 +223,70 @@ public:
friend class KItemSet;
};
+ class const_reverse_iterator
+ {
+ public:
+ const_reverse_iterator(KItemSet::const_iterator rangeIt)
+ : m_current(rangeIt)
+ {
+ }
+
+ const_reverse_iterator(const KItemSet::const_reverse_iterator &other)
+ : m_current(other.base())
+ {
+ }
+
+ int operator*() const
+ {
+ // analog to std::prev
+ auto t = const_iterator(m_current);
+ --t;
+ return *t;
+ }
+
+ inline bool operator==(const const_reverse_iterator &other) const
+ {
+ return m_current == other.m_current;
+ }
+
+ bool operator!=(const const_reverse_iterator &other) const
+ {
+ return !(*this == other);
+ }
+
+ const_reverse_iterator &operator++()
+ {
+ --m_current;
+ return *this;
+ }
+ const_reverse_iterator operator++(int)
+ {
+ auto tmp = *this;
+ ++(*this);
+ return tmp;
+ }
+
+ const_reverse_iterator &operator--()
+ {
+ ++m_current;
+ return *this;
+ }
+ const_reverse_iterator operator--(int)
+ {
+ auto tmp = *this;
+ --(*this);
+ return tmp;
+ }
+
+ KItemSet::const_iterator base() const
+ {
+ return m_current;
+ }
+
+ private:
+ KItemSet::const_iterator m_current;
+ };
+
iterator begin();
const_iterator begin() const;
const_iterator constBegin() const;
@@ -230,6 +294,9 @@ public:
const_iterator end() const;
const_iterator constEnd() const;
+ const_reverse_iterator rend() const;
+ const_reverse_iterator rbegin() const;
+
int first() const;
int last() const;
@@ -366,32 +433,32 @@ inline bool KItemSet::remove(int i)
inline KItemSet::iterator KItemSet::begin()
{
- return iterator(m_itemRanges.begin(), 0);
+ return iterator(m_itemRanges.begin());
}
inline KItemSet::const_iterator KItemSet::begin() const
{
- return const_iterator(m_itemRanges.begin(), 0);
+ return const_iterator(m_itemRanges.begin());
}
inline KItemSet::const_iterator KItemSet::constBegin() const
{
- return const_iterator(m_itemRanges.constBegin(), 0);
+ return const_iterator(m_itemRanges.constBegin());
}
inline KItemSet::iterator KItemSet::end()
{
- return iterator(m_itemRanges.end(), 0);
+ return iterator(m_itemRanges.end());
}
inline KItemSet::const_iterator KItemSet::end() const
{
- return const_iterator(m_itemRanges.end(), 0);
+ return const_iterator(m_itemRanges.end());
}
inline KItemSet::const_iterator KItemSet::constEnd() const
{
- return const_iterator(m_itemRanges.constEnd(), 0);
+ return const_iterator(m_itemRanges.constEnd());
}
inline int KItemSet::first() const
@@ -405,6 +472,16 @@ inline int KItemSet::last() const
return lastRange.index + lastRange.count - 1;
}
+inline KItemSet::const_reverse_iterator KItemSet::rend() const
+{
+ return KItemSet::const_reverse_iterator(constBegin());
+}
+
+inline KItemSet::const_reverse_iterator KItemSet::rbegin() const
+{
+ return KItemSet::const_reverse_iterator(constEnd());
+}
+
inline KItemSet &KItemSet::operator<<(int i)
{
insert(i);
diff --git a/src/kitemviews/private/kdirectorycontentscounter.cpp b/src/kitemviews/private/kdirectorycontentscounter.cpp
index 37e852ab9..648b20b6f 100644
--- a/src/kitemviews/private/kdirectorycontentscounter.cpp
+++ b/src/kitemviews/private/kdirectorycontentscounter.cpp
@@ -6,6 +6,7 @@
*/
#include "kdirectorycontentscounter.h"
+#include "dolphin_contentdisplaysettings.h"
#include "kitemviews/kfileitemmodel.h"
#include <KDirWatch>
@@ -16,36 +17,102 @@
namespace
{
+
+class LocalCache
+{
+public:
+ struct cacheData {
+ int count = 0;
+ long long size = 0;
+ ushort refCount = 0;
+ qint64 timestamp = 0;
+
+ inline operator bool() const
+ {
+ return timestamp != 0 && count != -1;
+ }
+ };
+
+ LocalCache()
+ : m_cache()
+ {
+ }
+
+ cacheData insert(const QString &key, cacheData data, bool inserted)
+ {
+ data.timestamp = QDateTime::currentMSecsSinceEpoch();
+ if (inserted) {
+ data.refCount += 1;
+ }
+ m_cache.insert(key, data);
+ return data;
+ }
+
+ cacheData value(const QString &key) const
+ {
+ return m_cache.value(key);
+ }
+
+ void unRefAll(const QSet<QString> &keys)
+ {
+ for (const auto &key : keys) {
+ auto entry = m_cache[key];
+ entry.refCount -= 1;
+ if (entry.refCount == 0) {
+ m_cache.remove(key);
+ }
+ }
+ }
+
+ void removeAll(const QSet<QString> &keys)
+ {
+ for (const auto &key : keys) {
+ m_cache.remove(key);
+ }
+ }
+
+private:
+ QHash<QString, cacheData> m_cache;
+};
+
/// cache of directory counting result
-static QHash<QString, QPair<int, long>> *s_cache;
+static LocalCache *s_cache;
+static QThread *s_workerThread;
+static KDirectoryContentsCounterWorker *s_worker;
}
KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObject *parent)
: QObject(parent)
, m_model(model)
+ , m_priorityQueue()
, m_queue()
- , m_worker(nullptr)
, m_workerIsBusy(false)
, m_dirWatcher(nullptr)
, m_watchedDirs()
{
- connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved);
+ if (s_cache == nullptr) {
+ s_cache = new LocalCache();
+ }
- if (!m_workerThread) {
- m_workerThread = new QThread();
- m_workerThread->start();
+ if (!s_workerThread) {
+ s_workerThread = new QThread();
+ s_workerThread->setObjectName(QStringLiteral("KDirectoryContentsCounterThread"));
+ s_workerThread->start();
}
- if (s_cache == nullptr) {
- s_cache = new QHash<QString, QPair<int, long>>();
+ if (!s_worker) {
+ s_worker = new KDirectoryContentsCounterWorker();
+ s_worker->moveToThread(s_workerThread);
}
- m_worker = new KDirectoryContentsCounterWorker();
- m_worker->moveToThread(m_workerThread);
+ connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved);
+ connect(m_model, &KFileItemModel::directoryRefreshing, this, &KDirectoryContentsCounter::slotDirectoryRefreshing);
+
+ connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, s_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
- connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
- connect(this, &KDirectoryContentsCounter::stop, m_worker, &KDirectoryContentsCounterWorker::stop);
- connect(m_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult);
+ connect(s_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult);
+ connect(s_worker, &KDirectoryContentsCounterWorker::intermediateResult, this, &KDirectoryContentsCounter::result);
+ connect(s_worker, &KDirectoryContentsCounterWorker::finished, this, &KDirectoryContentsCounter::scheduleNext);
m_dirWatcher = new KDirWatch(this);
connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty);
@@ -53,60 +120,20 @@ KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObj
KDirectoryContentsCounter::~KDirectoryContentsCounter()
{
- if (m_workerThread->isRunning()) {
- // The worker thread will continue running. It could even be running
- // a method of m_worker at the moment, so we delete it using
- // deleteLater() to prevent a crash.
- m_worker->deleteLater();
- } else {
- // There are no remaining workers -> stop the worker thread.
- m_workerThread->quit();
- m_workerThread->wait();
- delete m_workerThread;
- m_workerThread = nullptr;
-
- // The worker thread has finished running now, so it's safe to delete
- // m_worker. deleteLater() would not work at all because the event loop
- // which would deliver the event to m_worker is not running any more.
- delete m_worker;
- }
+ s_cache->unRefAll(m_watchedDirs);
}
-void KDirectoryContentsCounter::slotResult(const QString &path, int count, long size)
+void KDirectoryContentsCounter::slotResult(const QString &path, int count, long long size)
{
- m_workerIsBusy = false;
-
- const QFileInfo info = QFileInfo(path);
- const QString resolvedPath = info.canonicalFilePath();
-
- if (!m_dirWatcher->contains(resolvedPath)) {
+ const auto fileInfo = QFileInfo(path);
+ const QString resolvedPath = fileInfo.canonicalFilePath();
+ if (fileInfo.isReadable() && !m_watchedDirs.contains(resolvedPath)) {
m_dirWatcher->addDir(resolvedPath);
- m_watchedDirs.insert(resolvedPath);
}
+ bool inserted = m_watchedDirs.insert(resolvedPath) == m_watchedDirs.end();
- if (!m_priorityQueue.empty()) {
- const QString firstPath = m_priorityQueue.front();
- m_priorityQueue.pop_front();
- scanDirectory(firstPath, PathCountPriority::High);
- } else if (!m_queue.empty()) {
- const QString firstPath = m_queue.front();
- m_queue.pop_front();
- scanDirectory(firstPath, PathCountPriority::Normal);
- }
-
- if (s_cache->contains(resolvedPath)) {
- const auto pair = s_cache->value(resolvedPath);
- if (pair.first == count && pair.second == size) {
- // no change no need to send another result event
- return;
- }
- }
-
- if (info.dir().path() == m_model->rootItem().url().path()) {
- // update cache or overwrite value
- // when path is a direct children of the current model root
- s_cache->insert(resolvedPath, QPair<int, long>(count, size));
- }
+ // update cache or overwrite value
+ s_cache->insert(resolvedPath, {count, size, true}, inserted);
// sends the results
Q_EMIT result(path, count, size);
@@ -131,6 +158,11 @@ void KDirectoryContentsCounter::slotItemsRemoved()
{
const bool allItemsRemoved = (m_model->count() == 0);
+ if (allItemsRemoved) {
+ s_cache->removeAll(m_watchedDirs);
+ stopWorker();
+ }
+
if (!m_watchedDirs.isEmpty()) {
// Don't let KDirWatch watch for removed items
if (allItemsRemoved) {
@@ -138,7 +170,6 @@ void KDirectoryContentsCounter::slotItemsRemoved()
m_dirWatcher->removeDir(path);
}
m_watchedDirs.clear();
- m_queue.clear();
} else {
QMutableSetIterator<QString> it(m_watchedDirs);
while (it.hasNext()) {
@@ -152,46 +183,102 @@ void KDirectoryContentsCounter::slotItemsRemoved()
}
}
-void KDirectoryContentsCounter::scanDirectory(const QString &path, PathCountPriority priority)
+void KDirectoryContentsCounter::slotDirectoryRefreshing()
+{
+ s_cache->removeAll(m_watchedDirs);
+}
+
+void KDirectoryContentsCounter::scheduleNext()
{
- const QString resolvedPath = QFileInfo(path).canonicalFilePath();
- const bool alreadyInCache = s_cache->contains(resolvedPath);
- if (alreadyInCache) {
+ if (!m_priorityQueue.empty()) {
+ m_currentPath = m_priorityQueue.front();
+ m_priorityQueue.pop_front();
+ } else if (!m_queue.empty()) {
+ m_currentPath = m_queue.front();
+ m_queue.pop_front();
+ } else {
+ m_currentPath.clear();
+ m_workerIsBusy = false;
+ return;
+ }
+
+ const auto fileInfo = QFileInfo(m_currentPath);
+ const QString resolvedPath = fileInfo.canonicalFilePath();
+ const auto pair = s_cache->value(resolvedPath);
+ if (pair) {
// fast path when in cache
// will be updated later if result has changed
- const auto pair = s_cache->value(resolvedPath);
- Q_EMIT result(path, pair.first, pair.second);
+ Q_EMIT result(m_currentPath, pair.count, pair.size);
}
- if (m_workerIsBusy) {
- // only enqueue path not yet in queue
- if (std::find(m_queue.begin(), m_queue.end(), path) == m_queue.end()
- && std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path) == m_priorityQueue.end()) {
- if (priority == PathCountPriority::Normal) {
- if (alreadyInCache) {
- // if we already knew the dir size, it gets lower priority
- m_queue.push_back(path);
- } else {
- m_queue.push_front(path);
- }
- } else {
- // append to priority queue
- m_priorityQueue.push_back(path);
- }
- }
- } else {
- KDirectoryContentsCounterWorker::Options options;
+ // if scanned fully recently, skip rescan
+ if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) {
+ scheduleNext();
+ return;
+ }
+
+ KDirectoryContentsCounterWorker::Options options;
+
+ if (m_model->showHiddenFiles()) {
+ options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
+ }
+
+ m_workerIsBusy = true;
+ Q_EMIT requestDirectoryContentsCount(m_currentPath, options, ContentDisplaySettings::recursiveDirectorySizeLimit());
+}
- if (m_model->showHiddenFiles()) {
- options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
+void KDirectoryContentsCounter::enqueuePathScanning(const QString &path, bool alreadyInCache, PathCountPriority priority)
+{
+ // ensure to update the entry in the queue
+ auto it = std::find(m_queue.begin(), m_queue.end(), path);
+ if (it != m_queue.end()) {
+ m_queue.erase(it);
+ } else {
+ it = std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path);
+ if (it != m_priorityQueue.end()) {
+ m_priorityQueue.erase(it);
}
+ }
- if (m_model->showDirectoriesOnly()) {
- options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly;
+ if (priority == PathCountPriority::Normal) {
+ if (alreadyInCache) {
+ // we already knew the dir size
+ // otherwise it gets lower priority
+ m_queue.push_back(path);
+ } else {
+ m_queue.push_front(path);
}
+ } else {
+ // append to priority queue
+ m_priorityQueue.push_front(path);
+ }
+}
- Q_EMIT requestDirectoryContentsCount(path, options);
- m_workerIsBusy = true;
+void KDirectoryContentsCounter::scanDirectory(const QString &path, PathCountPriority priority)
+{
+ if (m_workerIsBusy && m_currentPath == path) {
+ // already listing
+ return;
+ }
+
+ const auto fileInfo = QFileInfo(path);
+ const QString resolvedPath = fileInfo.canonicalFilePath();
+ const auto pair = s_cache->value(resolvedPath);
+ if (pair) {
+ // fast path when in cache
+ // will be updated later if result has changed
+ Q_EMIT result(path, pair.count, pair.size);
+ }
+
+ // if scanned fully recently, skip rescan
+ if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) {
+ return;
+ }
+
+ enqueuePathScanning(path, pair, priority);
+
+ if (!m_workerIsBusy && !s_worker->stopping()) {
+ scheduleNext();
}
}
@@ -199,7 +286,9 @@ void KDirectoryContentsCounter::stopWorker()
{
m_queue.clear();
m_priorityQueue.clear();
- Q_EMIT stop();
-}
-QThread *KDirectoryContentsCounter::m_workerThread = nullptr;
+ if (m_workerIsBusy && m_currentPath == s_worker->scannedPath()) {
+ s_worker->stop();
+ }
+ m_currentPath.clear();
+}
diff --git a/src/kitemviews/private/kdirectorycontentscounter.h b/src/kitemviews/private/kdirectorycontentscounter.h
index 9a5e4a86b..0da3ccd7d 100644
--- a/src/kitemviews/private/kdirectorycontentscounter.h
+++ b/src/kitemviews/private/kdirectorycontentscounter.h
@@ -47,34 +47,34 @@ public:
Q_SIGNALS:
/**
* Signals that the directory \a path contains \a count items of size \a
- * Size calculation depends on parameter DetailsModeSettings::recursiveDirectorySizeLimit
+ * Size calculation depends on parameter ContentDisplaySettings::recursiveDirectorySizeLimit
*/
- void result(const QString &path, int count, long size);
+ void result(const QString &path, int count, long long size);
- void requestDirectoryContentsCount(const QString &path, KDirectoryContentsCounterWorker::Options options);
-
- void stop();
+ void requestDirectoryContentsCount(const QString &path, KDirectoryContentsCounterWorker::Options options, int maxRecursiveLevel);
private Q_SLOTS:
- void slotResult(const QString &path, int count, long size);
+ void slotResult(const QString &path, int count, long long size);
void slotDirWatchDirty(const QString &path);
void slotItemsRemoved();
+ void slotDirectoryRefreshing();
+ void scheduleNext();
private:
+ void enqueuePathScanning(const QString &path, bool alreadyInCache, PathCountPriority priority);
+
KFileItemModel *m_model;
// Used as FIFO queues.
std::list<QString> m_priorityQueue;
std::list<QString> m_queue;
- static QThread *m_workerThread;
-
- KDirectoryContentsCounterWorker *m_worker;
bool m_workerIsBusy;
KDirWatch *m_dirWatcher;
QSet<QString> m_watchedDirs; // Required as sadly KDirWatch does not offer a getter method
// to get all watched directories.
+ QString m_currentPath;
};
#endif
diff --git a/src/kitemviews/private/kdirectorycontentscounterworker.cpp b/src/kitemviews/private/kdirectorycontentscounterworker.cpp
index 2227ebbc2..eb456da25 100644
--- a/src/kitemviews/private/kdirectorycontentscounterworker.cpp
+++ b/src/kitemviews/private/kdirectorycontentscounterworker.cpp
@@ -7,16 +7,16 @@
#include "kdirectorycontentscounterworker.h"
-// Required includes for subItemsCount():
+// Required includes for countDirectoryContents():
#ifdef Q_OS_WIN
#include <QDir>
#else
-#include <QFile>
-#include <qplatformdefs.h>
+#include <QElapsedTimer>
+#include <fts.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#endif
-#include "dolphin_detailsmodesettings.h"
-
KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent)
: QObject(parent)
{
@@ -24,100 +24,135 @@ KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent
}
#ifndef Q_OS_WIN
-KDirectoryContentsCounterWorker::CountResult
-KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, const bool countHiddenFiles, const bool countDirectoriesOnly, const uint allowedRecursiveLevel)
+void KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, bool countHiddenFiles, uint allowedRecursiveLevel)
{
- int count = -1;
- long size = -1;
- auto dir = QT_OPENDIR(QFile::encodeName(dirPath));
- if (dir) {
- count = 0;
- size = 0;
- QT_DIRENT *dirEntry;
- QT_STATBUF buf;
+ QByteArray text = dirPath.toLocal8Bit();
+ char *rootPath = new char[text.size() + 1];
+ ::strncpy(rootPath, text.constData(), text.size() + 1);
+ char *path[2]{rootPath, nullptr};
+
+ // follow symlink only for root dir
+ auto tree = ::fts_open(path, FTS_COMFOLLOW | FTS_PHYSICAL | FTS_XDEV, nullptr);
+ if (!tree) {
+ delete[] rootPath;
+ return;
+ }
+
+ FTSENT *node;
+ long long totalSize = -1;
+ int totalCount = -1;
+ QElapsedTimer timer;
+ timer.start();
+
+ while ((node = fts_read(tree)) && !m_stopping) {
+ auto info = node->fts_info;
+
+ if (info == FTS_DC) {
+ // ignore directories clausing cycles
+ continue;
+ }
+ if (info == FTS_DNR) {
+ // ignore directories that can’t be read
+ continue;
+ }
+ if (info == FTS_ERR) {
+ // ignore directories causing errors
+ fts_set(tree, node, FTS_SKIP);
+ continue;
+ }
+ if (info == FTS_DP) {
+ // ignore end traversal of dir
+ continue;
+ }
- while (!m_stopping && (dirEntry = QT_READDIR(dir))) {
- if (dirEntry->d_name[0] == '.') {
- if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
- // Skip "." or hidden files
- continue;
- }
- if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
- // Skip ".."
- continue;
- }
+ if (!countHiddenFiles && node->fts_name[0] == '.' && strncmp(".git", node->fts_name, 4) != 0) {
+ // skip hidden files, except .git dirs
+ if (info == FTS_D) {
+ fts_set(tree, node, FTS_SKIP);
}
+ continue;
+ }
- // If only directories are counted, consider an unknown file type and links also
- // as directory instead of trying to do an expensive stat()
- // (see bugs 292642 and 299997).
- const bool countEntry = !countDirectoriesOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_LNK || dirEntry->d_type == DT_UNKNOWN;
- if (countEntry) {
- ++count;
+ if (info == FTS_F) {
+ // only count files that are physical (aka skip /proc/kcore...)
+ // naive size counting not taking into account effective disk space used (aka size/block_size * block_size)
+ // skip directory size (usually a 4KB block)
+ if (node->fts_statp->st_blocks > 0) {
+ totalSize += node->fts_statp->st_size;
}
+ }
- if (allowedRecursiveLevel > 0) {
- QString nameBuf = QStringLiteral("%1/%2").arg(dirPath, dirEntry->d_name);
+ if (info == FTS_D) {
+ if (node->fts_level == 0) {
+ // first read was sucessful, we can init counters
+ totalSize = 0;
+ totalCount = 0;
+ }
- if (dirEntry->d_type == DT_REG) {
- if (QT_STAT(nameBuf.toLocal8Bit(), &buf) == 0) {
- size += buf.st_size;
- }
- }
- if (!m_stopping && dirEntry->d_type == DT_DIR) {
- // recursion for dirs
- auto subdirResult = walkDir(nameBuf, countHiddenFiles, countDirectoriesOnly, allowedRecursiveLevel - 1);
- if (subdirResult.size > 0) {
- size += subdirResult.size;
- }
- }
+ if (node->fts_level > (int)allowedRecursiveLevel) {
+ // skip too deep nodes
+ fts_set(tree, node, FTS_SKIP);
+ continue;
}
}
- QT_CLOSEDIR(dir);
+ // count first level elements
+ if (node->fts_level == 1) {
+ ++totalCount;
+ }
+
+ // delay intermediate results
+ if (timer.hasExpired(200) || node->fts_level == 0) {
+ Q_EMIT intermediateResult(dirPath, totalCount, totalSize);
+ timer.restart();
+ }
+ }
+
+ delete[] rootPath;
+ fts_close(tree);
+ if (errno != 0) {
+ return;
+ }
+
+ if (!m_stopping) {
+ Q_EMIT result(dirPath, totalCount, totalSize);
}
- return KDirectoryContentsCounterWorker::CountResult{count, size};
}
#endif
-KDirectoryContentsCounterWorker::CountResult KDirectoryContentsCounterWorker::subItemsCount(const QString &path, Options options)
+void KDirectoryContentsCounterWorker::stop()
+{
+ m_stopping = true;
+}
+
+bool KDirectoryContentsCounterWorker::stopping() const
+{
+ return m_stopping;
+}
+
+QString KDirectoryContentsCounterWorker::scannedPath() const
+{
+ return m_scannedPath;
+}
+
+void KDirectoryContentsCounterWorker::countDirectoryContents(const QString &path, Options options, int maxRecursiveLevel)
{
const bool countHiddenFiles = options & CountHiddenFiles;
- const bool countDirectoriesOnly = options & CountDirectoriesOnly;
#ifdef Q_OS_WIN
QDir dir(path);
- QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
+ QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System | QDir::AllEntries;
if (countHiddenFiles) {
filters |= QDir::Hidden;
}
- if (countDirectoriesOnly) {
- filters |= QDir::Dirs;
- } else {
- filters |= QDir::AllEntries;
- }
- return {static_cast<int>(dir.entryList(filters).count()), 0};
-#else
- const uint maxRecursiveLevel = DetailsModeSettings::directorySizeCount() ? 1 : DetailsModeSettings::recursiveDirectorySizeLimit();
+ Q_EMIT result(path, static_cast<int>(dir.entryList(filters).count()), 0);
+#else
- auto res = walkDir(path, countHiddenFiles, countDirectoriesOnly, maxRecursiveLevel);
+ m_scannedPath = path;
+ walkDir(path, countHiddenFiles, maxRecursiveLevel);
- return res;
#endif
-}
-
-void KDirectoryContentsCounterWorker::stop()
-{
- m_stopping = true;
-}
-void KDirectoryContentsCounterWorker::countDirectoryContents(const QString &path, Options options)
-{
m_stopping = false;
-
- auto res = subItemsCount(path, options);
-
- if (!m_stopping) {
- Q_EMIT result(path, res.count, res.size);
- }
+ Q_EMIT finished();
}
diff --git a/src/kitemviews/private/kdirectorycontentscounterworker.h b/src/kitemviews/private/kdirectorycontentscounterworker.h
index 5266960cd..077df9f69 100644
--- a/src/kitemviews/private/kdirectorycontentscounterworker.h
+++ b/src/kitemviews/private/kdirectorycontentscounterworker.h
@@ -17,32 +17,20 @@ class KDirectoryContentsCounterWorker : public QObject
Q_OBJECT
public:
- enum Option { NoOptions = 0x0, CountHiddenFiles = 0x1, CountDirectoriesOnly = 0x2 };
+ enum Option { NoOptions = 0x0, CountHiddenFiles = 0x1 };
Q_DECLARE_FLAGS(Options, Option)
- struct CountResult {
- /// number of elements in the directory
- int count;
- /// Recursive sum of the size of the directory content files and folders
- /// Calculation depends on DetailsModeSettings::recursiveDirectorySizeLimit
- long size;
- };
-
explicit KDirectoryContentsCounterWorker(QObject *parent = nullptr);
- /**
- * Counts the items inside the directory \a path using the options
- * \a options.
- *
- * @return The number of items.
- */
- CountResult subItemsCount(const QString &path, Options options);
-
+ bool stopping() const;
+ QString scannedPath() const;
Q_SIGNALS:
/**
* Signals that the directory \a path contains \a count items and optionally the size of its content.
*/
- void result(const QString &path, int count, long size);
+ void result(const QString &path, int count, long long size);
+ void intermediateResult(const QString &path, int count, long long size);
+ void finished();
public Q_SLOTS:
/**
@@ -52,16 +40,17 @@ public Q_SLOTS:
// Note that the full type name KDirectoryContentsCounterWorker::Options
// is needed here. Just using 'Options' is OK for the compiler, but
// confuses moc.
- void countDirectoryContents(const QString &path, KDirectoryContentsCounterWorker::Options options);
+ void countDirectoryContents(const QString &path, KDirectoryContentsCounterWorker::Options options, int maxRecursiveLevel);
void stop();
private:
#ifndef Q_OS_WIN
- KDirectoryContentsCounterWorker::CountResult
- walkDir(const QString &dirPath, const bool countHiddenFiles, const bool countDirectoriesOnly, const uint allowedRecursiveLevel);
+ void walkDir(const QString &dirPath, bool countHiddenFiles, uint allowedRecursiveLevel);
#endif
- bool m_stopping = false;
+ QString m_scannedPath;
+
+ std::atomic<bool> m_stopping = false;
};
Q_DECLARE_METATYPE(KDirectoryContentsCounterWorker::Options)
diff --git a/src/kitemviews/private/kfileitemmodelfilter.cpp b/src/kitemviews/private/kfileitemmodelfilter.cpp
index f199c314f..0f9530801 100644
--- a/src/kitemviews/private/kfileitemmodelfilter.cpp
+++ b/src/kitemviews/private/kfileitemmodelfilter.cpp
@@ -8,6 +8,8 @@
#include <QRegularExpression>
+#include <algorithm>
+
#include <KFileItem>
KFileItemModelFilter::KFileItemModelFilter()
@@ -56,15 +58,25 @@ QStringList KFileItemModelFilter::mimeTypes() const
return m_mimeTypes;
}
+void KFileItemModelFilter::setExcludeMimeTypes(const QStringList &types)
+{
+ m_excludeMimeTypes = types;
+}
+
+QStringList KFileItemModelFilter::excludeMimeTypes() const
+{
+ return m_excludeMimeTypes;
+}
+
bool KFileItemModelFilter::hasSetFilters() const
{
- return (!m_pattern.isEmpty() || !m_mimeTypes.isEmpty());
+ return (!m_pattern.isEmpty() || !m_mimeTypes.isEmpty() || !m_excludeMimeTypes.isEmpty());
}
bool KFileItemModelFilter::matches(const KFileItem &item) const
{
const bool hasPatternFilter = !m_pattern.isEmpty();
- const bool hasMimeTypesFilter = !m_mimeTypes.isEmpty();
+ const bool hasMimeTypesFilter = !m_mimeTypes.isEmpty() || !m_excludeMimeTypes.isEmpty();
// If no filter is set, return true.
if (!hasPatternFilter && !hasMimeTypesFilter) {
@@ -95,10 +107,18 @@ bool KFileItemModelFilter::matchesPattern(const KFileItem &item) const
bool KFileItemModelFilter::matchesType(const KFileItem &item) const
{
- for (const QString &mimeType : qAsConst(m_mimeTypes)) {
- if (item.mimetype() == mimeType) {
- return true;
- }
+ bool excluded = std::any_of(m_excludeMimeTypes.constBegin(), m_excludeMimeTypes.constEnd(), [item](const QString &excludeMimetype) {
+ return item.mimetype() == excludeMimetype;
+ });
+ if (excluded) {
+ return false;
+ }
+
+ bool included = std::any_of(m_mimeTypes.constBegin(), m_mimeTypes.constEnd(), [item](const QString &mimeType) {
+ return item.mimetype() == mimeType;
+ });
+ if (included) {
+ return true;
}
return m_mimeTypes.isEmpty();
diff --git a/src/kitemviews/private/kfileitemmodelfilter.h b/src/kitemviews/private/kfileitemmodelfilter.h
index 959590da8..ce6cbeebb 100644
--- a/src/kitemviews/private/kfileitemmodelfilter.h
+++ b/src/kitemviews/private/kfileitemmodelfilter.h
@@ -45,6 +45,13 @@ public:
QStringList mimeTypes() const;
/**
+ * Set the list of mimetypes that are used for comparison and excluded with the
+ * item in KFileItemModelFilter::matchesMimeType.
+ */
+ void setExcludeMimeTypes(const QStringList &types);
+ QStringList excludeMimeTypes() const;
+
+ /**
* @return True if either the pattern or mimetype filters has been set.
*/
bool hasSetFilters() const;
@@ -73,5 +80,6 @@ private:
// faster comparison in matches().
QString m_pattern; // Property set by setPattern().
QStringList m_mimeTypes; // Property set by setMimeTypes()
+ QStringList m_excludeMimeTypes; // Property set by setExcludeMimeTypes()
};
#endif