File indexing completed on 2024-12-01 03:40:50

0001 /*
0002     SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
0003     SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
0004     SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
0005 
0006     Separated from Dolphin by Nick Shaforostoff <shafff@ukr.net>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include "kdirsortfilterproxymodel.h"
0012 
0013 #include "defaults-kfile.h"
0014 
0015 #include <KConfigGroup>
0016 #include <KLocalizedString>
0017 #include <KSharedConfig>
0018 #include <kdirmodel.h>
0019 #include <kfileitem.h>
0020 
0021 #include <QCollator>
0022 
0023 class Q_DECL_HIDDEN KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate
0024 {
0025 public:
0026     KDirSortFilterProxyModelPrivate();
0027 
0028     int compare(const QString &, const QString &, Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive);
0029     void slotNaturalSortingChanged();
0030 
0031     bool m_sortFoldersFirst;
0032     bool m_sortHiddenFilesLast;
0033     bool m_naturalSorting;
0034     QCollator m_collator;
0035 };
0036 
0037 KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::KDirSortFilterProxyModelPrivate()
0038     : m_sortFoldersFirst(true)
0039     , m_sortHiddenFilesLast(DefaultHiddenFilesLast)
0040 {
0041     slotNaturalSortingChanged();
0042 }
0043 
0044 int KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::compare(const QString &a, const QString &b, Qt::CaseSensitivity caseSensitivity)
0045 {
0046     int result;
0047 
0048     if (m_naturalSorting) {
0049         m_collator.setCaseSensitivity(caseSensitivity);
0050         result = m_collator.compare(a, b);
0051     } else {
0052         result = QString::compare(a, b, caseSensitivity);
0053     }
0054 
0055     if (caseSensitivity == Qt::CaseSensitive || result != 0) {
0056         // Only return the result, if the strings are not equal. If they are equal by a case insensitive
0057         // comparison, still a deterministic sort order is required. A case sensitive
0058         // comparison is done as fallback.
0059         return result;
0060     }
0061 
0062     return QString::compare(a, b, Qt::CaseSensitive);
0063 }
0064 
0065 void KDirSortFilterProxyModel::KDirSortFilterProxyModelPrivate::slotNaturalSortingChanged()
0066 {
0067     KConfigGroup g(KSharedConfig::openConfig(), QStringLiteral("KDE"));
0068     m_naturalSorting = g.readEntry("NaturalSorting", true);
0069     m_collator.setNumericMode(m_naturalSorting);
0070 }
0071 
0072 KDirSortFilterProxyModel::KDirSortFilterProxyModel(QObject *parent)
0073     : KCategorizedSortFilterProxyModel(parent)
0074     , d(new KDirSortFilterProxyModelPrivate)
0075 {
0076     setDynamicSortFilter(true);
0077 
0078     // sort by the user visible string for now
0079     setSortCaseSensitivity(Qt::CaseInsensitive);
0080     sort(KDirModel::Name, Qt::AscendingOrder);
0081 }
0082 
0083 Qt::DropActions KDirSortFilterProxyModel::supportedDragOptions() const
0084 {
0085     return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction;
0086 }
0087 
0088 KDirSortFilterProxyModel::~KDirSortFilterProxyModel() = default;
0089 
0090 bool KDirSortFilterProxyModel::hasChildren(const QModelIndex &parent) const
0091 {
0092     const QModelIndex sourceParent = mapToSource(parent);
0093     return sourceModel()->hasChildren(sourceParent);
0094 }
0095 
0096 bool KDirSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const
0097 {
0098     const QModelIndex sourceParent = mapToSource(parent);
0099     return sourceModel()->canFetchMore(sourceParent);
0100 }
0101 
0102 int KDirSortFilterProxyModel::pointsForPermissions(const QFileInfo &info)
0103 {
0104     int points = 0;
0105 
0106     const QFile::Permission permissionsCheck[] = {QFile::ReadUser,
0107                                                   QFile::WriteUser,
0108                                                   QFile::ExeUser,
0109                                                   QFile::ReadGroup,
0110                                                   QFile::WriteGroup,
0111                                                   QFile::ExeGroup,
0112                                                   QFile::ReadOther,
0113                                                   QFile::WriteOther,
0114                                                   QFile::ExeOther};
0115 
0116     for (QFile::Permission perm : permissionsCheck) {
0117         points += info.permission(perm) ? 1 : 0;
0118     }
0119 
0120     return points;
0121 }
0122 
0123 void KDirSortFilterProxyModel::setSortFoldersFirst(bool foldersFirst)
0124 {
0125     if (d->m_sortFoldersFirst == foldersFirst) {
0126         return;
0127     }
0128 
0129     d->m_sortFoldersFirst = foldersFirst;
0130     invalidate();
0131 }
0132 
0133 bool KDirSortFilterProxyModel::sortFoldersFirst() const
0134 {
0135     return d->m_sortFoldersFirst;
0136 }
0137 
0138 void KDirSortFilterProxyModel::setSortHiddenFilesLast(bool hiddenFilesLast)
0139 {
0140     if (d->m_sortHiddenFilesLast == hiddenFilesLast) {
0141         return;
0142     }
0143 
0144     d->m_sortHiddenFilesLast = hiddenFilesLast;
0145     invalidate();
0146 }
0147 
0148 bool KDirSortFilterProxyModel::sortHiddenFilesLast() const
0149 {
0150     return d->m_sortHiddenFilesLast;
0151 }
0152 
0153 bool KDirSortFilterProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
0154 {
0155     KDirModel *dirModel = static_cast<KDirModel *>(sourceModel());
0156 
0157     const KFileItem leftFileItem = dirModel->itemForIndex(left);
0158     const KFileItem rightFileItem = dirModel->itemForIndex(right);
0159 
0160     const bool isLessThan = (sortOrder() == Qt::AscendingOrder);
0161 
0162     // Show hidden files and folders last
0163     if (d->m_sortHiddenFilesLast) {
0164         const bool leftItemIsHidden = leftFileItem.isHidden();
0165         const bool rightItemIsHidden = rightFileItem.isHidden();
0166         if (leftItemIsHidden && !rightItemIsHidden) {
0167             return !isLessThan;
0168         } else if (!leftItemIsHidden && rightItemIsHidden) {
0169             return isLessThan;
0170         }
0171     }
0172 
0173     // Folders go before files if the corresponding setting is set.
0174     if (d->m_sortFoldersFirst) {
0175         const bool leftItemIsDir = leftFileItem.isDir();
0176         const bool rightItemIsDir = rightFileItem.isDir();
0177         if (leftItemIsDir && !rightItemIsDir) {
0178             return isLessThan;
0179         } else if (!leftItemIsDir && rightItemIsDir) {
0180             return !isLessThan;
0181         }
0182     }
0183 
0184     switch (left.column()) {
0185     case KDirModel::Name: {
0186         int result = d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity());
0187         if (result == 0) {
0188             // KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used
0189             result = d->compare(leftFileItem.name(sortCaseSensitivity() == Qt::CaseInsensitive),
0190                                 rightFileItem.name(sortCaseSensitivity() == Qt::CaseInsensitive),
0191                                 sortCaseSensitivity());
0192             if (result == 0) {
0193                 // If KFileItem::text() is also not unique most probably a search protocol is used
0194                 // that allows showing the same file names from different directories
0195                 result = d->compare(leftFileItem.url().toString(), rightFileItem.url().toString(), sortCaseSensitivity());
0196             }
0197         }
0198 
0199         return result < 0;
0200     }
0201 
0202     case KDirModel::Size: {
0203         // If we have two folders, what we have to measure is the number of
0204         // items that contains each other
0205         if (leftFileItem.isDir() && rightFileItem.isDir()) {
0206             QVariant leftValue = dirModel->data(left, KDirModel::ChildCountRole);
0207             int leftCount = (leftValue.typeId() == QMetaType::Int) ? leftValue.toInt() : KDirModel::ChildCountUnknown;
0208 
0209             QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
0210             int rightCount = (rightValue.typeId() == QMetaType::Int) ? rightValue.toInt() : KDirModel::ChildCountUnknown;
0211 
0212             // In the case they two have the same child items, we sort them by
0213             // their names. So we have always everything ordered. We also check
0214             // if we are taking in count their cases.
0215             if (leftCount == rightCount) {
0216                 return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0217             }
0218 
0219             // If one of them has unknown child items, place them on the end. If we
0220             // were comparing two unknown childed items, the previous comparison
0221             // sorted them by QCollator between them. This case is when we
0222             // have an unknown childed item, and another known.
0223             if (leftCount == KDirModel::ChildCountUnknown) {
0224                 return false;
0225             }
0226 
0227             if (rightCount == KDirModel::ChildCountUnknown) {
0228                 return true;
0229             }
0230 
0231             // If they had different number of items, we sort them depending
0232             // on how many items had each other.
0233             return leftCount < rightCount;
0234         }
0235 
0236         // If what we are measuring is two files and they have the same size,
0237         // sort them by their file names.
0238         if (leftFileItem.size() == rightFileItem.size()) {
0239             return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0240         }
0241 
0242         // If their sizes are different, sort them by their sizes, as expected.
0243         return leftFileItem.size() < rightFileItem.size();
0244     }
0245 
0246     case KDirModel::ModifiedTime: {
0247         QDateTime leftModifiedTime = leftFileItem.time(KFileItem::ModificationTime).toLocalTime();
0248         QDateTime rightModifiedTime = rightFileItem.time(KFileItem::ModificationTime).toLocalTime();
0249 
0250         if (leftModifiedTime == rightModifiedTime) {
0251             return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0252         }
0253 
0254         return leftModifiedTime < rightModifiedTime;
0255     }
0256 
0257     case KDirModel::Permissions: {
0258         const int leftPermissions = leftFileItem.permissions();
0259         const int rightPermissions = rightFileItem.permissions();
0260 
0261         if (leftPermissions == rightPermissions) {
0262             return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0263         }
0264 
0265         return leftPermissions > rightPermissions;
0266     }
0267 
0268     case KDirModel::Owner: {
0269         if (leftFileItem.user() == rightFileItem.user()) {
0270             return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0271         }
0272 
0273         return d->compare(leftFileItem.user(), rightFileItem.user()) < 0;
0274     }
0275 
0276     case KDirModel::Group: {
0277         if (leftFileItem.group() == rightFileItem.group()) {
0278             return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0279         }
0280 
0281         return d->compare(leftFileItem.group(), rightFileItem.group()) < 0;
0282     }
0283 
0284     case KDirModel::Type: {
0285         if (leftFileItem.mimetype() == rightFileItem.mimetype()) {
0286             return d->compare(leftFileItem.text(), rightFileItem.text(), sortCaseSensitivity()) < 0;
0287         }
0288 
0289         return d->compare(leftFileItem.mimeComment(), rightFileItem.mimeComment()) < 0;
0290     }
0291     }
0292 
0293     // We have set a SortRole and trust the ProxyModel to do
0294     // the right thing for now.
0295     return KCategorizedSortFilterProxyModel::subSortLessThan(left, right);
0296 }
0297 
0298 #include "moc_kdirsortfilterproxymodel.cpp"