};
DolphinSortFilterProxyModel::DolphinSortFilterProxyModel(QObject* parent) :
- KDirSortFilterProxyModel(parent),
+ KSortFilterProxyModel(parent),
m_sorting(DolphinView::SortByName),
m_sortOrder(Qt::AscendingOrder)
{
+ setDynamicSortFilter(true);
+
+ // sort by the user visible string for now
+ setSortRole(DolphinView::SortByName);
+ setSortCaseSensitivity(Qt::CaseInsensitive);
+ sort(KDirModel::Name, Qt::AscendingOrder);
}
DolphinSortFilterProxyModel::~DolphinSortFilterProxyModel()
m_sorting = sortingForColumn(column);
m_sortOrder = sortOrder;
setSortRole(m_sorting);
- KDirSortFilterProxyModel::sort(column, sortOrder);
+ KSortFilterProxyModel::sort(column, sortOrder);
+}
+
+bool DolphinSortFilterProxyModel::hasChildren(const QModelIndex& parent) const
+{
+ const QModelIndex sourceParent = mapToSource(parent);
+ return sourceModel()->hasChildren(sourceParent);
+}
+
+bool DolphinSortFilterProxyModel::canFetchMore(const QModelIndex& parent) const
+{
+ const QModelIndex sourceParent = mapToSource(parent);
+ return sourceModel()->canFetchMore(sourceParent);
}
DolphinView::Sorting DolphinSortFilterProxyModel::sortingForColumn(int column)
bool DolphinSortFilterProxyModel::lessThan(const QModelIndex& left,
const QModelIndex& right) const
{
-#ifdef HAVE_NEPOMUK
KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
const KFileItem* leftFileItem = dirModel->itemForIndex(left);
const KFileItem* rightFileItem = dirModel->itemForIndex(right);
+ // If we are sorting by rating, folders and files are citizens of the same
+ // class. Same if we are sorting by tags.
+#ifdef HAVE_NEPOMUK
+ if ((sortRole() != DolphinView::SortByRating) &&
+ (sortRole() != DolphinView::SortByTags))
+ {
+#endif
+ // On our priority, folders go above regular files.
+ if (leftFileItem->isDir() && !rightFileItem->isDir()) {
+ return true;
+ } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
+ return false;
+ }
+#ifdef HAVE_NEPOMUK
+ }
+#endif
// Hidden elements go before visible ones, if they both are
// folders or files.
}
switch (sortRole()) {
- case DolphinView::SortByRating: {
- const quint32 leftRating = ratingForIndex(left);
- const quint32 rightRating = ratingForIndex(right);
-
- if (leftRating == rightRating) {
- // On our priority, folders go above regular files.
- // This checks are needed (don't think it's the same doing it here
- // than above). Here we make dirs citizens of first class because
- // we know we are on the same category. On the check we do on the
- // top of the method we don't know, so we remove that check when we
- // are sorting by rating. (ereslibre)
- if (leftFileItem->isDir() && !rightFileItem->isDir()) {
- return true;
- } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
- return false;
- }
+ case DolphinView::SortByName: {
+ // So we are in the same priority, what counts now is their names.
+ const QVariant leftData = dirModel->data(left, KDirModel::Name);
+ const QVariant rightData = dirModel->data(right, KDirModel::Name);
+ const QString leftValueString(leftData.toString());
+ const QString rightValueString(rightData.toString());
+
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftValueString, rightValueString) < 0) :
+ (naturalCompare(leftValueString.toLower(), rightValueString.toLower()) < 0);
+ }
- return sortCaseSensitivity() ?
- (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
- (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ case DolphinView::SortBySize: {
+ // If we have two folders, what we have to measure is the number of
+ // items that contains each other
+ if (leftFileItem->isDir() && rightFileItem->isDir()) {
+ QVariant leftValue = dirModel->data(left, KDirModel::ChildCountRole);
+ int leftCount = leftValue.type() == QVariant::Int ? leftValue.toInt() : KDirModel::ChildCountUnknown;
+
+ QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
+ int rightCount = rightValue.type() == QVariant::Int ? rightValue.toInt() : KDirModel::ChildCountUnknown;
+
+ // In the case they two have the same child items, we sort them by
+ // their names. So we have always everything ordered. We also check
+ // if we are taking in count their cases.
+ if (leftCount == rightCount) {
+ return sortCaseSensitivity() ? (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
}
- return leftRating > rightRating;
+ // If they had different number of items, we sort them depending
+ // on how many items had each other.
+ return leftCount < rightCount;
}
- case DolphinView::SortByTags: {
- const QString leftTags = tagsForIndex(left);
- const QString rightTags = tagsForIndex(right);
-
- if (leftTags == rightTags) {
- // On our priority, folders go above regular files.
- // This checks are needed (don't think it's the same doing it here
- // than above). Here we make dirs citizens of first class because
- // we know we are on the same category. On the check we do on the
- // top of the method we don't know, so we remove that check when we
- // are sorting by tags. (ereslibre)
- if (leftFileItem->isDir() && !rightFileItem->isDir()) {
- return true;
- } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
- return false;
- }
+ // If what we are measuring is two files and they have the same size,
+ // sort them by their file names.
+ if (leftFileItem->size() == rightFileItem->size()) {
+ return sortCaseSensitivity() ? (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
- return sortCaseSensitivity() ?
- (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ // If their sizes are different, sort them by their sizes, as expected.
+ return leftFileItem->size() < rightFileItem->size();
+ }
+
+ case DolphinView::SortByDate: {
+ KDateTime leftTime, rightTime;
+ leftTime.setTime_t(leftFileItem->time(KIO::UDS_MODIFICATION_TIME));
+ rightTime.setTime_t(rightFileItem->time(KIO::UDS_MODIFICATION_TIME));
+
+ if (leftTime == rightTime) {
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
+
+ return leftTime > rightTime;
+ }
+
+ case DolphinView::SortByPermissions: {
+ if (leftFileItem->permissionsString() == rightFileItem->permissionsString()) {
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
+
+ return naturalCompare(leftFileItem->permissionsString(),
+ rightFileItem->permissionsString()) < 0;
+ }
+
+ case DolphinView::SortByOwner: {
+ if (leftFileItem->user() == rightFileItem->user()) {
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
+
+ return naturalCompare(leftFileItem->user(), rightFileItem->user()) < 0;
+ }
+
+ case DolphinView::SortByGroup: {
+ if (leftFileItem->group() == rightFileItem->group()) {
+ return sortCaseSensitivity() ? (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
(naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
+
+ return naturalCompare(leftFileItem->group(),
+ rightFileItem->group()) < 0;
+ }
+
+ case DolphinView::SortByType: {
+ if (leftFileItem->mimetype() == rightFileItem->mimetype()) {
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
+
+ return naturalCompare(leftFileItem->mimeComment(),
+ rightFileItem->mimeComment()) < 0;
+ }
+
+#ifdef HAVE_NEPOMUK
+ case DolphinView::SortByRating: {
+ const quint32 leftRating = ratingForIndex(left);
+ const quint32 rightRating = ratingForIndex(right);
+
+ if (leftRating == rightRating) {
+ // On our priority, folders go above regular files.
+ // This checks are needed (don't think it's the same doing it here
+ // than above). Here we make dirs citizens of first class because
+ // we know we are on the same category. On the check we do on the
+ // top of the method we don't know, so we remove that check when we
+ // are sorting by rating. (ereslibre)
+ if (leftFileItem->isDir() && !rightFileItem->isDir()) {
+ return true;
+ } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
+ return false;
}
- return naturalCompare(leftTags, rightTags) < 0;
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
}
+
+ return leftRating > rightRating;
+ }
+
+ case DolphinView::SortByTags: {
+ const QString leftTags = tagsForIndex(left);
+ const QString rightTags = tagsForIndex(right);
+
+ if (leftTags == rightTags) {
+ // On our priority, folders go above regular files.
+ // This checks are needed (don't think it's the same doing it here
+ // than above). Here we make dirs citizens of first class because
+ // we know we are on the same category. On the check we do on the
+ // top of the method we don't know, so we remove that check when we
+ // are sorting by tags. (ereslibre)
+ if (leftFileItem->isDir() && !rightFileItem->isDir()) {
+ return true;
+ } else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
+ return false;
+ }
+
+ return sortCaseSensitivity() ?
+ (naturalCompare(leftFileItem->name(), rightFileItem->name()) < 0) :
+ (naturalCompare(leftFileItem->name().toLower(), rightFileItem->name().toLower()) < 0);
+ }
+
+ return naturalCompare(leftTags, rightTags) < 0;
}
#endif
+ }
+
// We have set a SortRole and trust the ProxyModel to do
// the right thing for now.
- return KDirSortFilterProxyModel::lessThan(left, right);
+ return QSortFilterProxyModel::lessThan(left, right);
}
quint32 DolphinSortFilterProxyModel::ratingForIndex(const QModelIndex& index)
return tagsString;
#else
- Q_UNUSED(index);
return QString();
#endif
}
+int DolphinSortFilterProxyModel::naturalCompare(const QString& a,
+ const QString& b)
+{
+ // This method chops the input a and b into pieces of
+ // digits and non-digits (a1.05 becomes a | 1 | . | 05)
+ // and compares these pieces of a and b to each other
+ // (first with first, second with second, ...).
+ //
+ // This is based on the natural sort order code code by Martin Pool
+ // http://sourcefrog.net/projects/natsort/
+ // Martin Pool agreed to license this under LGPL or GPL.
+
+ const QChar* currA = a.unicode(); // iterator over a
+ const QChar* currB = b.unicode(); // iterator over b
+
+ if (currA == currB) {
+ return 0;
+ }
+
+ const QChar* begSeqA = currA; // beginning of a new character sequence of a
+ const QChar* begSeqB = currB;
+
+ while (!currA->isNull() && !currB->isNull()) {
+ // find sequence of characters ending at the first non-character
+ while (!currA->isNull() && !currA->isDigit()) {
+ ++currA;
+ }
+
+ while (!currB->isNull() && !currB->isDigit()) {
+ ++currB;
+ }
+
+ // compare these sequences
+ const QString subA(begSeqA, currA - begSeqA);
+ const QString subB(begSeqB, currB - begSeqB);
+ const int cmp = QString::localeAwareCompare(subA, subB);
+ if (cmp != 0) {
+ return cmp;
+ }
+
+ if (currA->isNull() || currB->isNull()) {
+ break;
+ }
+
+ // now some digits follow...
+ if ((*currA == '0') || (*currB == '0')) {
+ // one digit-sequence starts with 0 -> assume we are in a fraction part
+ // do left aligned comparison (numbers are considered left aligned)
+ while (1) {
+ if (!currA->isDigit() && !currB->isDigit()) {
+ break;
+ } else if (!currA->isDigit()) {
+ return -1;
+ } else if (!currB->isDigit()) {
+ return + 1;
+ } else if (*currA < *currB) {
+ return -1;
+ } else if (*currA > *currB) {
+ return + 1;
+ }
+ ++currA;
+ ++currB;
+ }
+ } else {
+ // No digit-sequence starts with 0 -> assume we are looking at some integer
+ // do right aligned comparison.
+ //
+ // The longest run of digits wins. That aside, the greatest
+ // value wins, but we can't know that it will until we've scanned
+ // both numbers to know that they have the same magnitude.
+
+ int weight = 0;
+ while (1) {
+ if (!currA->isDigit() && !currB->isDigit()) {
+ if (weight != 0) {
+ return weight;
+ }
+ break;
+ } else if (!currA->isDigit()) {
+ return -1;
+ } else if (!currB->isDigit()) {
+ return + 1;
+ } else if ((*currA < *currB) && (weight == 0)) {
+ weight = -1;
+ } else if ((*currA > *currB) && (weight == 0)) {
+ weight = + 1;
+ }
+ ++currA;
+ ++currB;
+ }
+ }
+
+ begSeqA = currA;
+ begSeqB = currB;
+ }
+
+ if (currA->isNull() && currB->isNull()) {
+ return 0;
+ }
+
+ return currA->isNull() ? -1 : + 1;
+}
#include "dolphinsortfilterproxymodel.moc"
#ifndef DOLPHINSORTFILTERPROXYMODEL_H
#define DOLPHINSORTFILTERPROXYMODEL_H
-#include <kdirsortfilterproxymodel.h>
+#include <ksortfilterproxymodel.h>
#include <dolphinview.h>
#include <libdolphin_export.h>
*
* It is assured that directories are always sorted before files.
*/
-class LIBDOLPHINPRIVATE_EXPORT DolphinSortFilterProxyModel : public KDirSortFilterProxyModel
+class LIBDOLPHINPRIVATE_EXPORT DolphinSortFilterProxyModel : public KSortFilterProxyModel
{
Q_OBJECT
virtual void sort(int column,
Qt::SortOrder order = Qt::AscendingOrder);
+ /** Reimplemented from QAbstractItemModel. Returns true for directories. */
+ virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const;
+
+ /** Reimplemented from QAbstractItemModel. Returns true for empty directories. */
+ virtual bool canFetchMore(const QModelIndex& parent) const;
+
/**
* Helper method to get the DolphinView::Sorting type for a given
* column \a column. If the column is smaller 0 or greater than the
*/
static DolphinView::Sorting sortingForColumn(int column);
- /**
- * This method is essential on the categorized view.
- * It will does a "basic" sorting, just for finding out categories,
- * and their order. Then over those elements DISORDERED on categories,
- * the lessThan method will be applied for each category.
- *
- * The easy explanation is that not always folders go first. That will depend.
- * Imagine we sort by Rating. Categories will be created by 10 stars,
- * 9 stars, 8 stars... but a category with only a file with rating 10
- * will go before a category with a folder with rating 8.
- * That's the main reason, and that's lessThanGeneralPurpose() method.
- * That will go category by category creating sets of elements...
- */
virtual bool lessThanGeneralPurpose(const QModelIndex &left,
const QModelIndex &right) const;
- /**
- * Then for each set of elements lessThanCategoryPurpose() will be applied,
- * because for each category we wan't first folders and bla bla bla...
- * That's the main reason of that method existence.
- *
- * For that reason, is not that clear that we want ALWAYS folders first.
- * On each category, yes, that's true. But that's not true always,
- * as I have pointed out on the example before.
- */
- bool lessThanCategoryPurpose(const QModelIndex &left,
- const QModelIndex &right) const
- {
- //when we sort inside 1 category its the usual lessThan()
- //from KDirSortFilterProxyModel(+nepomuk)
- return lessThan(left,right);
- }
-
protected:
virtual bool lessThan(const QModelIndex& left,
const QModelIndex& right) const;
*/
static QString tagsForIndex(const QModelIndex& index);
+ static int naturalCompare(const QString& a, const QString& b);
+
private:
DolphinView::Sorting m_sorting;
Qt::SortOrder m_sortOrder;