#include "trash/dolphintrash.h"
#include "views/viewmodecontroller.h"
#include "views/viewproperties.h"
+#include "dolphin_detailsmodesettings.h"
+#include "views/dolphinview.h"
#ifdef HAVE_KACTIVITIES
#include <KActivities/ResourceInstance>
setSearchModeEnabled(isSearchUrl(url));
+ connect(DetailsModeSettings::self(), &KCoreConfigSkeleton::configChanged, this, [=]() {
+ if (view()->mode() == DolphinView::Mode::DetailsView) {
+ view()->reload();
+ }
+ });
+
// Initialize kactivities resource instance
#ifdef HAVE_KACTIVITIES
#include "kfileitemmodel.h"
#include "kitemlistview.h"
+#include "dolphin_detailsmodesettings.h"
+
#include <KFormat>
#include <KLocalizedString>
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<KIO::filesize_t>();
+ text = KFormat().formatByteSize(size);
+ }
+ }
}
}
} else {
#include "kfileitemmodel.h"
#include "dolphin_generalsettings.h"
+#include "dolphin_detailsmodesettings.h"
#include "dolphindebug.h"
#include "private/kfileitemmodeldirlister.h"
#include "private/kfileitemmodelsortalgorithm.h"
// 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()) {
} 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():
#include <QElapsedTimer>
#include <QTimer>
-
// #define KFILEITEMMODELROLESUPDATER_DEBUG
namespace {
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);
#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");
QHash<QByteArray, QVariant> 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);
}
}
}
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);
// 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
}
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:
/**
#include <KDirWatch>
#include <QFileInfo>
+#include <QDir>
#include <QThread>
+namespace {
+ /// cache of directory counting result
+ static QHash<QString, QPair<int, long>> *s_cache;
+}
+
KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) :
QObject(parent),
m_model(model),
m_workerThread->start();
}
+ if (s_cache == nullptr) {
+ s_cache = new QHash<QString, QPair<int, long>>();
+ }
+
m_worker = new KDirectoryContentsCounterWorker();
m_worker->moveToThread(m_workerThread);
- ++m_workersCount;
connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount,
m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
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.
}
}
-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);
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<int, long>(count, size));
+ }
+
+ // sends the results
+ emit result(resolvedPath, count, size);
}
void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path)
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();
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 {
}
QThread* KDirectoryContentsCounter::m_workerThread = nullptr;
-int KDirectoryContentsCounter::m_workersCount = 0;
#include <QQueue>
#include <QSet>
+#include <QHash>
class KDirWatch;
class KFileItemModel;
*
* 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();
QQueue<QString> m_queue;
static QThread* m_workerThread;
- static int m_workersCount;
KDirectoryContentsCounterWorker* m_worker;
bool m_workerIsBusy;
// Required includes for subItemsCount():
#ifdef Q_OS_WIN
- #include <QDir>
+#include <QDir>
#else
- #include <QFile>
- #include <qplatformdefs.h>
+#include <QFile>
+#include <qplatformdefs.h>
#endif
+#include "dolphin_detailsmodesettings.h"
+
KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject* parent) :
QObject(parent)
{
qRegisterMetaType<KDirectoryContentsCounterWorker::Options>();
}
-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 <faure@kde.org>
-
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) {
// 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);
}
};
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);
/**
*
* @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:
/**
<label>Expandable folders</label>
<default>true</default>
</entry>
+ <entry name="DirectorySizeCount" type="Bool">
+ <label>Whether or not content count is use as directory size</label>
+ <default>true</default>
+ </entry>
+ <entry name="RecursiveDirectorySizeLimit" type="UInt">
+ <label>Recursive directory size limit</label>
+ <default>10</default>
+ </entry>
</group>
</kcfg>
#include <QComboBox>
#include <QHelpEvent>
#include <QFormLayout>
+#include <QSpinBox>
+#include <QRadioButton>
+#include <QButtonGroup>
+#include <QLabel>
ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) :
QWidget(parent),
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();
m_fontRequester = new DolphinFontRequester(this);
topLayout->addRow(i18nc("@label:listbox", "Label font:"), m_fontRequester);
-
switch (m_mode) {
case IconsMode: {
m_widthBox = new QComboBox();
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<int>::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;
}
break;
case DetailsMode:
connect(m_expandableFolders, &QCheckBox::toggled, this, &ViewSettingsTab::changed);
+ connect(m_recursiveDirectorySizeLimit, QOverload<int>::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;
break;
case DetailsMode:
DetailsModeSettings::setExpandableFolders(m_expandableFolders->isChecked());
+ DetailsModeSettings::setDirectorySizeCount(m_numberOfItems->isChecked());
+ DetailsModeSettings::setRecursiveDirectorySizeLimit(m_recursiveDirectorySizeLimit->value());
break;
default:
break;
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;
class QComboBox;
class QCheckBox;
class QSlider;
+class QSpinBox;
+class QRadioButton;
/**
* @brief Represents one tab of the view-settings page.
QComboBox* m_widthBox;
QComboBox* m_maxLinesBox;
QCheckBox* m_expandableFolders;
+ QRadioButton* m_numberOfItems;
+ QRadioButton* m_sizeOfContents;
+ QSpinBox* m_recursiveDirectorySizeLimit;
};
#endif