diff options
| author | Méven Car <[email protected]> | 2023-06-28 09:49:46 +0200 |
|---|---|---|
| committer | Méven Car <[email protected]> | 2023-06-28 09:49:46 +0200 |
| commit | cd2e64154fd5446a7e19aff4cb147efe2f2ba31e (patch) | |
| tree | 37f4e2c8644129f809a66fd2f6b2c9b28d52fed8 /src/kitemviews | |
| parent | dcd5c994bb1d331b94fdea8a5c6cd55a471b34b8 (diff) | |
| parent | ea56e1f75eae435c18e3934c402c94ae76ec9c11 (diff) | |
Merge branch 'master' into kf6
Diffstat (limited to 'src/kitemviews')
| -rw-r--r-- | src/kitemviews/kfileitemlisttostring.cpp | 2 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlisttostring.h | 2 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistview.cpp | 15 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistview.h | 8 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistwidget.cpp | 14 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodel.cpp | 51 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodel.h | 11 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodelrolesupdater.cpp | 88 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodelrolesupdater.h | 6 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistcontroller.cpp | 35 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistwidget.cpp | 2 | ||||
| -rw-r--r-- | src/kitemviews/kitemset.h | 93 | ||||
| -rw-r--r-- | src/kitemviews/private/kdirectorycontentscounter.cpp | 279 | ||||
| -rw-r--r-- | src/kitemviews/private/kdirectorycontentscounter.h | 18 | ||||
| -rw-r--r-- | src/kitemviews/private/kdirectorycontentscounterworker.cpp | 183 | ||||
| -rw-r--r-- | src/kitemviews/private/kdirectorycontentscounterworker.h | 33 | ||||
| -rw-r--r-- | src/kitemviews/private/kfileitemmodelfilter.cpp | 32 | ||||
| -rw-r--r-- | src/kitemviews/private/kfileitemmodelfilter.h | 8 |
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 |
