diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp --- a/src/dolphinviewcontainer.cpp +++ b/src/dolphinviewcontainer.cpp @@ -29,6 +29,8 @@ #include "trash/dolphintrash.h" #include "views/viewmodecontroller.h" #include "views/viewproperties.h" +#include "dolphin_detailsmodesettings.h" +#include "views/dolphinview.h" #ifdef HAVE_KACTIVITIES #include @@ -249,6 +251,12 @@ setSearchModeEnabled(isSearchUrl(url)); + connect(DetailsModeSettings::self(), &KCoreConfigSkeleton::configChanged, this, [=]() { + if (view()->mode() == DolphinView::Mode::DetailsView) { + view()->reload(); + } + }); + // Initialize kactivities resource instance #ifdef HAVE_KACTIVITIES diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -21,6 +21,8 @@ #include "kfileitemmodel.h" #include "kitemlistview.h" +#include "dolphin_detailsmodesettings.h" + #include #include @@ -64,14 +66,24 @@ if (role == "size") { if (values.value("isDir").toBool()) { - // The item represents a directory. Show the number of sub directories - // instead of the file size of the directory. + // The item represents a directory. if (!roleValue.isNull()) { - const int count = roleValue.toInt(); + const int count = values.value("count").toInt(); if (count < 0) { text = i18nc("@item:intable", "Unknown"); } else { - text = i18ncp("@item:intable", "%1 item", "%1 items", count); + if (DetailsModeSettings::directorySizeCount()) { + // Show the number of sub directories instead of the file size of the directory. + text = i18ncp("@item:intable", "%1 item", "%1 items", count); + } else { + // if we have directory size available + if (roleValue == -1) { + text = i18nc("@item:intable", "Unknown"); + } else { + const KIO::filesize_t size = roleValue.value(); + text = KFormat().formatByteSize(size); + } + } } } } else { diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -22,6 +22,7 @@ #include "kfileitemmodel.h" #include "dolphin_generalsettings.h" +#include "dolphin_detailsmodesettings.h" #include "dolphindebug.h" #include "private/kfileitemmodeldirlister.h" #include "private/kfileitemmodelsortalgorithm.h" @@ -1767,16 +1768,27 @@ // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan(): Q_ASSERT(itemB.isDir()); - const QVariant valueA = a->values.value("size"); - const QVariant valueB = b->values.value("size"); + QVariant valueA, valueB; + if (DetailsModeSettings::directorySizeCount()) { + // use dir size then + valueA = a->values.value("size"); + valueB = b->values.value("size"); + } else { + valueA = a->values.value("count"); + valueB = b->values.value("count"); + } if (valueA.isNull() && valueB.isNull()) { result = 0; } else if (valueA.isNull()) { result = -1; } else if (valueB.isNull()) { result = +1; } else { - result = valueA.toInt() - valueB.toInt(); + if (valueA < valueB) { + return -1; + } else { + return +1; + } } } else { // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan(): diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h --- a/src/kitemviews/kfileitemmodelrolesupdater.h +++ b/src/kitemviews/kfileitemmodelrolesupdater.h @@ -212,7 +212,7 @@ void applyChangedBalooRoles(const QString& file); void applyChangedBalooRolesForItem(const KFileItem& file); - void slotDirectoryContentsCountReceived(const QString& path, int count); + void slotDirectoryContentsCountReceived(const QString& path, int count, long size); private: /** diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -44,7 +44,6 @@ #include #include - // #define KFILEITEMMODELROLESUPDATER_DEBUG namespace { @@ -108,9 +107,9 @@ this, &KFileItemModelRolesUpdater::slotSortRoleChanged); // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous - // resolving of the roles. Postpone the resolving until no update has been done for 1 second. + // resolving of the roles. Postpone the resolving until no update has been done for 100 ms. m_recentlyChangedItemsTimer = new QTimer(this); - m_recentlyChangedItemsTimer->setInterval(1000); + m_recentlyChangedItemsTimer->setInterval(100); m_recentlyChangedItemsTimer->setSingleShot(true); connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems); @@ -750,7 +749,7 @@ #endif } -void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count) +void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count, long size) { const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); @@ -761,17 +760,16 @@ QHash data; if (getSizeRole) { - data.insert("size", count); + data.insert("count", count); + if (size != -1) { + data.insert("size", QVariant::fromValue(size)); + } } if (getIsExpandableRole) { data.insert("isExpandable", count > 0); } - disconnect(m_model, &KFileItemModel::itemsChanged, - this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); - connect(m_model, &KFileItemModel::itemsChanged, - this, &KFileItemModelRolesUpdater::slotItemsChanged); } } } @@ -997,7 +995,7 @@ data.insert("type", item.mimeComment()); } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { const QString path = item.localPath(); - data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path)); + m_directoryContentsCounter->scanDirectory(path); } else { // Probably the sort role is a baloo role - just determine all roles. data = rolesData(item); @@ -1070,7 +1068,7 @@ // Tell m_directoryContentsCounter that we want to count the items // inside the directory. The result will be received in slotDirectoryContentsCountReceived. const QString path = item.localPath(); - m_directoryContentsCounter->addDirectory(path); + m_directoryContentsCounter->scanDirectory(path); } else if (getSizeRole) { data.insert("size", -1); // -1 indicates an unknown number of items } diff --git a/src/kitemviews/private/kdirectorycontentscounter.h b/src/kitemviews/private/kdirectorycontentscounter.h --- a/src/kitemviews/private/kdirectorycontentscounter.h +++ b/src/kitemviews/private/kdirectorycontentscounter.h @@ -25,6 +25,7 @@ #include #include +#include class KDirWatch; class KFileItemModel; @@ -45,28 +46,23 @@ * * The directory \a path is watched for changes, and the signal is emitted * again if a change occurs. - */ - void addDirectory(const QString& path); - - /** - * In contrast to \a addDirectory, this function counts the items inside - * the directory \a path synchronously and returns the result. * - * The directory is watched for changes, and the signal \a result is - * emitted if a change occurs. + * Uses a cache internally to speed up first result, + * but emit again result when the cache was updated */ - int countDirectoryContentsSynchronously(const QString& path); + void scanDirectory(const QString& path); signals: /** - * Signals that the directory \a path contains \a count items. + * Signals that the directory \a path contains \a count items of size \a + * Size calculation depends on parameter DetailsModeSettings::recursiveDirectorySizeLimit */ - void result(const QString& path, int count); + void result(const QString& path, int count, long size); void requestDirectoryContentsCount(const QString& path, KDirectoryContentsCounterWorker::Options options); private slots: - void slotResult(const QString& path, int count); + void slotResult(const QString& path, int count, long size); void slotDirWatchDirty(const QString& path); void slotItemsRemoved(); @@ -79,7 +75,6 @@ QQueue m_queue; static QThread* m_workerThread; - static int m_workersCount; KDirectoryContentsCounterWorker* m_worker; bool m_workerIsBusy; diff --git a/src/kitemviews/private/kdirectorycontentscounter.cpp b/src/kitemviews/private/kdirectorycontentscounter.cpp --- a/src/kitemviews/private/kdirectorycontentscounter.cpp +++ b/src/kitemviews/private/kdirectorycontentscounter.cpp @@ -24,8 +24,14 @@ #include #include +#include #include +namespace { + /// cache of directory counting result + static QHash> *s_cache; +} + KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) : QObject(parent), m_model(model), @@ -43,9 +49,12 @@ m_workerThread->start(); } + if (s_cache == nullptr) { + s_cache = new QHash>(); + } + m_worker = new KDirectoryContentsCounterWorker(); m_worker->moveToThread(m_workerThread); - ++m_workersCount; connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); @@ -58,9 +67,7 @@ KDirectoryContentsCounter::~KDirectoryContentsCounter() { - --m_workersCount; - - if (m_workersCount > 0) { + 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. @@ -79,38 +86,17 @@ } } -void KDirectoryContentsCounter::addDirectory(const QString& path) +void KDirectoryContentsCounter::scanDirectory(const QString& path) { startWorker(path); } -int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path) -{ - const QString resolvedPath = QFileInfo(path).canonicalFilePath(); - - if (!m_dirWatcher->contains(resolvedPath)) { - m_dirWatcher->addDir(resolvedPath); - m_watchedDirs.insert(resolvedPath); - } - - KDirectoryContentsCounterWorker::Options options; - - if (m_model->showHiddenFiles()) { - options |= KDirectoryContentsCounterWorker::CountHiddenFiles; - } - - if (m_model->showDirectoriesOnly()) { - options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; - } - - return KDirectoryContentsCounterWorker::subItemsCount(path, options); -} - -void KDirectoryContentsCounter::slotResult(const QString& path, int count) +void KDirectoryContentsCounter::slotResult(const QString& path, int count, long size) { m_workerIsBusy = false; - const QString resolvedPath = QFileInfo(path).canonicalFilePath(); + const QFileInfo info = QFileInfo(path); + const QString resolvedPath = info.canonicalFilePath(); if (!m_dirWatcher->contains(resolvedPath)) { m_dirWatcher->addDir(resolvedPath); @@ -121,7 +107,22 @@ startWorker(m_queue.dequeue()); } - emit result(path, count); + 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(count, size)); + } + + // sends the results + emit result(resolvedPath, count, size); } void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) @@ -146,7 +147,7 @@ if (!m_watchedDirs.isEmpty()) { // Don't let KDirWatch watch for removed items if (allItemsRemoved) { - foreach (const QString& path, m_watchedDirs) { + for (const QString& path : qAsConst(m_watchedDirs)) { m_dirWatcher->removeDir(path); } m_watchedDirs.clear(); @@ -166,6 +167,13 @@ void KDirectoryContentsCounter::startWorker(const QString& path) { + if (s_cache->contains(path)) { + // fast path when in cache + // will be updated later if result has changed + const auto pair = s_cache->value(path); + emit result(path, pair.first, pair.second); + } + if (m_workerIsBusy) { m_queue.enqueue(path); } else { @@ -185,4 +193,3 @@ } QThread* KDirectoryContentsCounter::m_workerThread = nullptr; -int KDirectoryContentsCounter::m_workersCount = 0; diff --git a/src/kitemviews/private/kdirectorycontentscounterworker.h b/src/kitemviews/private/kdirectorycontentscounterworker.h --- a/src/kitemviews/private/kdirectorycontentscounterworker.h +++ b/src/kitemviews/private/kdirectorycontentscounterworker.h @@ -37,21 +37,29 @@ }; 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. */ - static int subItemsCount(const QString& path, Options options); + static CountResult subItemsCount(const QString& path, Options options); signals: /** - * Signals that the directory \a path contains \a count items. + * Signals that the directory \a path contains \a count items and optionally the size of its content. */ - void result(const QString& path, int count); + void result(const QString& path, int count, long size); public slots: /** diff --git a/src/kitemviews/private/kdirectorycontentscounterworker.cpp b/src/kitemviews/private/kdirectorycontentscounterworker.cpp --- a/src/kitemviews/private/kdirectorycontentscounterworker.cpp +++ b/src/kitemviews/private/kdirectorycontentscounterworker.cpp @@ -22,44 +22,33 @@ // Required includes for subItemsCount(): #ifdef Q_OS_WIN - #include +#include #else - #include - #include +#include +#include #endif +#include "dolphin_detailsmodesettings.h" + KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject* parent) : QObject(parent) { qRegisterMetaType(); } -int KDirectoryContentsCounterWorker::subItemsCount(const QString& path, Options options) +KDirectoryContentsCounterWorker::CountResult walkDir(const QString &dirPath, + const bool countHiddenFiles, + const bool countDirectoriesOnly, + QT_DIRENT *dirEntry, + const uint allowedRecursiveLevel) { - const bool countHiddenFiles = options & CountHiddenFiles; - const bool countDirectoriesOnly = options & CountDirectoriesOnly; - -#ifdef Q_OS_WIN - QDir dir(path); - QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System; - if (countHiddenFiles) { - filters |= QDir::Hidden; - } - if (countDirectoriesOnly) { - filters |= QDir::Dirs; - } else { - filters |= QDir::AllEntries; - } - return dir.entryList(filters).count(); -#else - // Taken from kio/src/widgets/kdirmodel.cpp - // Copyright (C) 2006 David Faure - int count = -1; - auto dir = QT_OPENDIR(QFile::encodeName(path)); + long size = -1; + auto dir = QT_OPENDIR(QFile::encodeName(dirPath)); if (dir) { count = 0; - QT_DIRENT *dirEntry = nullptr; + QT_STATBUF buf; + while ((dirEntry = QT_READDIR(dir))) { if (dirEntry->d_name[0] == '.') { if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) { @@ -76,20 +65,69 @@ // 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; + dirEntry->d_type == DT_DIR || + dirEntry->d_type == DT_LNK || + dirEntry->d_type == DT_UNKNOWN; if (countEntry) { ++count; } + + if (allowedRecursiveLevel > 0) { + + bool linkFound = false; + QString nameBuf = QStringLiteral("%1/%2").arg(dirPath, dirEntry->d_name); + + if (dirEntry->d_type == DT_REG || dirEntry->d_type == DT_LNK) { + if (QT_STAT(nameBuf.toLocal8Bit(), &buf) == 0) { + if (S_ISDIR(buf.st_mode)) { + // was a dir link, recurse + linkFound = true; + } + size += buf.st_size; + } + } + if (dirEntry->d_type == DT_DIR || linkFound) { + // recursion for dirs and dir links + size += walkDir(nameBuf, countHiddenFiles, countDirectoriesOnly, dirEntry, allowedRecursiveLevel - 1).size; + } + } } QT_CLOSEDIR(dir); } - return count; + return KDirectoryContentsCounterWorker::CountResult{count, size}; +} + +KDirectoryContentsCounterWorker::CountResult KDirectoryContentsCounterWorker::subItemsCount(const QString& path, Options options) +{ + const bool countHiddenFiles = options & CountHiddenFiles; + const bool countDirectoriesOnly = options & CountDirectoriesOnly; + +#ifdef Q_OS_WIN + QDir dir(path); + QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System; + if (countHiddenFiles) { + filters |= QDir::Hidden; + } + if (countDirectoriesOnly) { + filters |= QDir::Dirs; + } else { + filters |= QDir::AllEntries; + } + return {dir.entryList(filters).count(), 0}; +#else + + const uint maxRecursiveLevel = DetailsModeSettings::directorySizeCount() ? 1 : DetailsModeSettings::recursiveDirectorySizeLimit(); + + QT_DIRENT *dirEntry = nullptr; + + auto res = walkDir(QFile::encodeName(path), countHiddenFiles, countDirectoriesOnly, dirEntry, maxRecursiveLevel); + + return res; #endif } void KDirectoryContentsCounterWorker::countDirectoryContents(const QString& path, Options options) { - emit result(path, subItemsCount(path, options)); + auto res = subItemsCount(path, options); + emit result(path, res.count, res.size); } diff --git a/src/settings/dolphin_detailsmodesettings.kcfg b/src/settings/dolphin_detailsmodesettings.kcfg --- a/src/settings/dolphin_detailsmodesettings.kcfg +++ b/src/settings/dolphin_detailsmodesettings.kcfg @@ -44,5 +44,13 @@ true + + + true + + + + 10 + diff --git a/src/settings/viewmodes/viewsettingstab.h b/src/settings/viewmodes/viewsettingstab.h --- a/src/settings/viewmodes/viewsettingstab.h +++ b/src/settings/viewmodes/viewsettingstab.h @@ -28,6 +28,8 @@ class QComboBox; class QCheckBox; class QSlider; +class QSpinBox; +class QRadioButton; /** * @brief Represents one tab of the view-settings page. @@ -72,6 +74,9 @@ QComboBox* m_widthBox; QComboBox* m_maxLinesBox; QCheckBox* m_expandableFolders; + QRadioButton* m_numberOfItems; + QRadioButton* m_sizeOfContents; + QSpinBox* m_recursiveDirectorySizeLimit; }; #endif diff --git a/src/settings/viewmodes/viewsettingstab.cpp b/src/settings/viewmodes/viewsettingstab.cpp --- a/src/settings/viewmodes/viewsettingstab.cpp +++ b/src/settings/viewmodes/viewsettingstab.cpp @@ -33,6 +33,10 @@ #include #include #include +#include +#include +#include +#include ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) : QWidget(parent), @@ -42,11 +46,11 @@ m_fontRequester(nullptr), m_widthBox(nullptr), m_maxLinesBox(nullptr), - m_expandableFolders(nullptr) + m_expandableFolders(nullptr), + m_recursiveDirectorySizeLimit(nullptr) { QFormLayout* topLayout = new QFormLayout(this); - // Create "Icon Size" section const int minRange = ZoomLevelInfo::minimumLevel(); const int maxRange = ZoomLevelInfo::maximumLevel(); @@ -75,7 +79,6 @@ m_fontRequester = new DolphinFontRequester(this); topLayout->addRow(i18nc("@label:listbox", "Label font:"), m_fontRequester); - switch (m_mode) { case IconsMode: { m_widthBox = new QComboBox(); @@ -107,8 +110,30 @@ case DetailsMode: m_expandableFolders = new QCheckBox(i18nc("@option:check", "Expandable")); topLayout->addRow(i18nc("@label:checkbox", "Folders:"), m_expandableFolders); - break; - default: + +#ifndef Q_OS_WIN + // Sorting properties + m_numberOfItems = new QRadioButton(i18nc("option:radio", "Number of items")); + m_sizeOfContents = new QRadioButton(i18nc("option:radio", "Size of contents, up to ")); + + QButtonGroup* sortingModeGroup = new QButtonGroup(this); + sortingModeGroup->addButton(m_numberOfItems); + sortingModeGroup->addButton(m_sizeOfContents); + + m_recursiveDirectorySizeLimit = new QSpinBox(); + connect(m_recursiveDirectorySizeLimit, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { + m_recursiveDirectorySizeLimit->setSuffix(i18np(" level deep", " levels deep", value)); + }); + m_recursiveDirectorySizeLimit->setRange(1, 20); + m_recursiveDirectorySizeLimit->setSingleStep(1); + + QHBoxLayout *contentsSizeLayout = new QHBoxLayout(); + contentsSizeLayout->addWidget(m_sizeOfContents); + contentsSizeLayout->addWidget(m_recursiveDirectorySizeLimit); + + topLayout->addRow(i18nc("@title:group", "Folder size displays:"), m_numberOfItems); + topLayout->addRow(QString(), contentsSizeLayout); +#endif break; } @@ -128,6 +153,11 @@ break; case DetailsMode: connect(m_expandableFolders, &QCheckBox::toggled, this, &ViewSettingsTab::changed); + connect(m_recursiveDirectorySizeLimit, QOverload::of(&QSpinBox::valueChanged), this, &ViewSettingsTab::changed); + connect(m_numberOfItems, &QRadioButton::toggled, this, &ViewSettingsTab::changed); + connect(m_sizeOfContents, &QRadioButton::toggled, this, [=]() { + m_recursiveDirectorySizeLimit->setEnabled(m_sizeOfContents->isChecked()); + }); break; default: break; @@ -153,6 +183,8 @@ break; case DetailsMode: DetailsModeSettings::setExpandableFolders(m_expandableFolders->isChecked()); + DetailsModeSettings::setDirectorySizeCount(m_numberOfItems->isChecked()); + DetailsModeSettings::setRecursiveDirectorySizeLimit(m_recursiveDirectorySizeLimit->value()); break; default: break; @@ -201,6 +233,14 @@ break; case DetailsMode: m_expandableFolders->setChecked(DetailsModeSettings::expandableFolders()); + if (DetailsModeSettings::directorySizeCount()) { + m_numberOfItems->setChecked(true); + m_recursiveDirectorySizeLimit->setEnabled(false); + } else { + m_sizeOfContents->setChecked(true); + m_recursiveDirectorySizeLimit->setEnabled(true); + } + m_recursiveDirectorySizeLimit->setValue(DetailsModeSettings::recursiveDirectorySizeLimit()); break; default: break;