refactor: move sorters in separate files

This commit is contained in:
Grecko 2018-04-30 01:10:36 +02:00
parent df41e94e45
commit 2ee1936545
16 changed files with 764 additions and 680 deletions

View File

@ -3,7 +3,6 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/qqmlsortfilterproxymodel.h \
$$PWD/sorter.h \
$$PWD/proxyrole.h \
$$PWD/filters/filter.h \
$$PWD/filters/filtercontainer.h \
@ -15,10 +14,14 @@ HEADERS += $$PWD/qqmlsortfilterproxymodel.h \
$$PWD/filters/expressionfilter.h \
$$PWD/filters/filtercontainerfilter.h \
$$PWD/filters/anyoffilter.h \
$$PWD/filters/alloffilter.h
$$PWD/filters/alloffilter.h \
$$PWD/sorters/sorter.h \
$$PWD/sorters/sortercontainer.h \
$$PWD/sorters/rolesorter.h \
$$PWD/sorters/stringsorter.h \
$$PWD/sorters/expressionsorter.h
SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/sorter.cpp \
$$PWD/proxyrole.cpp \
$$PWD/filters/filter.cpp \
$$PWD/filters/filtercontainer.cpp \
@ -31,4 +34,10 @@ SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/filters/filtercontainerfilter.cpp \
$$PWD/filters/anyoffilter.cpp \
$$PWD/filters/alloffilter.cpp \
$$PWD/filters/filtersqmltypes.cpp
$$PWD/filters/filtersqmltypes.cpp \
$$PWD/sorters/sorter.cpp \
$$PWD/sorters/sortercontainer.cpp \
$$PWD/sorters/rolesorter.cpp \
$$PWD/sorters/stringsorter.cpp \
$$PWD/sorters/expressionsorter.cpp \
$$PWD/sorters/sortersqmltypes.cpp

View File

@ -2,7 +2,7 @@
#include <QtQml>
#include <algorithm>
#include "filters/filter.h"
#include "sorter.h"
#include "sorters/sorter.h"
#include "proxyrole.h"
namespace qqsfpm {

View File

@ -1,502 +0,0 @@
#include "sorter.h"
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
namespace qqsfpm {
SorterContainer::~SorterContainer()
{
}
QList<Sorter*> SorterContainer::sorters() const
{
return m_sorters;
}
void SorterContainer::appendSorter(Sorter* sorter)
{
m_sorters.append(sorter);
onSorterAppended(sorter);
}
void SorterContainer::removeSorter(Sorter *sorter)
{
m_sorters.removeOne(sorter);
onSorterRemoved(sorter);
}
void SorterContainer::clearSorters()
{
m_sorters.clear();
onSortersCleared();
}
QQmlListProperty<Sorter> SorterContainer::sortersListProperty()
{
return QQmlListProperty<Sorter>(reinterpret_cast<QObject*>(this), &m_sorters,
&SorterContainer::append_sorter,
&SorterContainer::count_sorter,
&SorterContainer::at_sorter,
&SorterContainer::clear_sorters);
}
void SorterContainer::append_sorter(QQmlListProperty<Sorter>* list, Sorter* sorter)
{
if (!sorter)
return;
SorterContainer* that = reinterpret_cast<SorterContainer*>(list->object);
that->appendSorter(sorter);
}
int SorterContainer::count_sorter(QQmlListProperty<Sorter>* list)
{
QList<Sorter*>* sorters = static_cast<QList<Sorter*>*>(list->data);
return sorters->count();
}
Sorter* SorterContainer::at_sorter(QQmlListProperty<Sorter>* list, int index)
{
QList<Sorter*>* sorters = static_cast<QList<Sorter*>*>(list->data);
return sorters->at(index);
}
void SorterContainer::clear_sorters(QQmlListProperty<Sorter> *list)
{
SorterContainer* that = reinterpret_cast<SorterContainer*>(list->object);
that->clearSorters();
}
/*!
\qmltype Sorter
\inqmlmodule SortFilterProxyModel
\brief Base type for the \l SortFilterProxyModel sorters
The Sorter type cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other sorters types that inherit from it.
Attempting to use the Sorter type directly will result in an error.
*/
Sorter::Sorter(QObject *parent) : QObject(parent)
{
}
Sorter::~Sorter() = default;
/*!
\qmlproperty bool Sorter::enabled
This property holds whether the sorter is enabled.
A disabled sorter will not change the order of the rows.
By default, sorters are enabled.
*/
bool Sorter::enabled() const
{
return m_enabled;
}
void Sorter::setEnabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
Q_EMIT enabledChanged();
Q_EMIT invalidated();
}
bool Sorter::ascendingOrder() const
{
return sortOrder() == Qt::AscendingOrder;
}
void Sorter::setAscendingOrder(bool ascendingOrder)
{
setSortOrder(ascendingOrder ? Qt::AscendingOrder : Qt::DescendingOrder);
}
/*!
\qmlproperty Qt::SortOrder Sorter::sortOrder
This property holds the sort order of this sorter.
\value Qt.AscendingOrder The items are sorted ascending e.g. starts with 'AAA' ends with 'ZZZ' in Latin-1 locales
\value Qt.DescendingOrder The items are sorted descending e.g. starts with 'ZZZ' ends with 'AAA' in Latin-1 locales
By default, sorting is in ascending order.
*/
Qt::SortOrder Sorter::sortOrder() const
{
return m_sortOrder;
}
void Sorter::setSortOrder(Qt::SortOrder sortOrder)
{
if (m_sortOrder == sortOrder)
return;
m_sortOrder = sortOrder;
Q_EMIT sortOrderChanged();
invalidate();
}
int Sorter::compareRows(const QModelIndex &source_left, const QModelIndex &source_right, const QQmlSortFilterProxyModel& proxyModel) const
{
int comparison = compare(source_left, source_right, proxyModel);
return (m_sortOrder == Qt::AscendingOrder) ? comparison : -comparison;
}
int Sorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
if (lessThan(sourceLeft, sourceRight, proxyModel))
return -1;
if (lessThan(sourceRight, sourceLeft, proxyModel))
return 1;
return 0;
}
void Sorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
Q_UNUSED(proxyModel)
}
bool Sorter::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
Q_UNUSED(sourceLeft)
Q_UNUSED(sourceRight)
Q_UNUSED(proxyModel)
return false;
}
void Sorter::invalidate()
{
if (m_enabled)
Q_EMIT invalidated();
}
const QString& RoleSorter::roleName() const
{
return m_roleName;
}
/*!
\qmltype RoleSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\brief Sorts rows based on a source model role
A RoleSorter is a simple \l Sorter that sorts rows based on a source model role.
In the following example, rows with be sorted by their \c lastName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: RoleSorter { roleName: "lastName" }
}
\endcode
*/
/*!
\qmlproperty string RoleSorter::roleName
This property holds the role name that the sorter is using to query the source model's data when sorting items.
*/
void RoleSorter::setRoleName(const QString& roleName)
{
if (m_roleName == roleName)
return;
m_roleName = roleName;
Q_EMIT roleNameChanged();
invalidate();
}
QPair<QVariant, QVariant> RoleSorter::sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair;
int role = proxyModel.roleForName(m_roleName);
if (role == -1)
return pair;
pair.first = proxyModel.sourceData(sourceLeft, role);
pair.second = proxyModel.sourceData(sourceRight, role);
return pair;
}
int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair = sourceData(sourceLeft, sourceRight, proxyModel);
QVariant leftValue = pair.first;
QVariant rightValue = pair.second;
if (leftValue < rightValue)
return -1;
if (leftValue > rightValue)
return 1;
return 0;
}
/*!
\qmltype StringSorter
\inherits RoleSorter
\inqmlmodule SortFilterProxyModel
\brief Sorts rows based on a source model string role
\l StringSorter is a specialized \l RoleSorter that sorts rows based on a source model string role.
\l StringSorter compares strings according to a localized collation algorithm.
In the following example, rows with be sorted by their \c lastName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: StringSorter { roleName: "lastName" }
}
\endcode
*/
/*!
\qmlproperty Qt.CaseSensitivity StringSorter::caseSensitivity
This property holds the case sensitivity of the sorter.
*/
Qt::CaseSensitivity StringSorter::caseSensitivity() const
{
return m_collator.caseSensitivity();
}
void StringSorter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{
if (m_collator.caseSensitivity() == caseSensitivity)
return;
m_collator.setCaseSensitivity(caseSensitivity);
Q_EMIT caseSensitivityChanged();
invalidate();
}
/*!
\qmlproperty bool StringSorter::ignorePunctation
This property holds whether the sorter ignores punctation.
if \c ignorePunctuation is \c true, punctuation characters and symbols are ignored when determining sort order.
\note This property is not currently supported on Apple platforms or if Qt is configured to not use ICU on Linux.
*/
bool StringSorter::ignorePunctation() const
{
return m_collator.ignorePunctuation();
}
void StringSorter::setIgnorePunctation(bool ignorePunctation)
{
if (m_collator.ignorePunctuation() == ignorePunctation)
return;
m_collator.setIgnorePunctuation(ignorePunctation);
Q_EMIT ignorePunctationChanged();
invalidate();
}
/*!
\qmlproperty Locale StringSorter::locale
This property holds the locale of the sorter.
*/
QLocale StringSorter::locale() const
{
return m_collator.locale();
}
void StringSorter::setLocale(const QLocale &locale)
{
if (m_collator.locale() == locale)
return;
m_collator.setLocale(locale);
Q_EMIT localeChanged();
invalidate();
}
/*!
\qmlproperty bool StringSorter::numericMode
This property holds whether the numeric mode of the sorter is enabled.
This will enable proper sorting of numeric digits, so that e.g. 100 sorts after 99.
By default this mode is off.
*/
bool StringSorter::numericMode() const
{
return m_collator.numericMode();
}
void StringSorter::setNumericMode(bool numericMode)
{
if (m_collator.numericMode() == numericMode)
return;
m_collator.setNumericMode(numericMode);
Q_EMIT numericModeChanged();
invalidate();
}
int StringSorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair = sourceData(sourceLeft, sourceRight, proxyModel);
QString leftValue = pair.first.toString();
QString rightValue = pair.second.toString();
return m_collator.compare(leftValue, rightValue);
}
/*!
\qmltype ExpressionSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\brief Sorts row with a custom sorting
An ExpressionSorter is a \l Sorter allowing to implement custom sorting based on a javascript expression.
*/
/*!
\qmlproperty expression ExpressionSorter::expression
An expression to implement custom sorting. It must evaluate to a bool.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding}, except that it will be evaluated for each of the source model's rows.
Model data is accessible for both rows with the \c modelLeft, and \c modelRight properties:
\code
sorters: ExpressionSorter {
expression: {
return modelLeft.someRole < modelRight.someRole;
}
}
\endcode
The \c index of the row is also available through \c modelLeft and \c modelRight.
The expression should return \c true if the value of the left item is less than the value of the right item, otherwise returns false.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index* or in \c model*) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionSorter::expression() const
{
return m_scriptString;
}
void ExpressionSorter::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionSorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
bool evaluateBoolExpression(QQmlExpression& expression)
{
QVariant variantResult = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return false;
}
if (variantResult.canConvert<bool>()) {
return variantResult.toBool();
} else {
qWarning("%s:%i:%i : Can't convert result to bool",
expression.sourceFile().toUtf8().data(),
expression.lineNumber(),
expression.columnNumber());
return false;
}
}
int ExpressionSorter::compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelLeftMap, modelRightMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
for (auto it = roles.cbegin(); it != roles.cend(); ++it) {
modelLeftMap.insert(it.value(), proxyModel.sourceData(sourceLeft, it.key()));
modelRightMap.insert(it.value(), proxyModel.sourceData(sourceRight, it.key()));
}
modelLeftMap.insert("index", sourceLeft.row());
modelRightMap.insert("index", sourceRight.row());
QQmlExpression expression(m_scriptString, &context);
context.setContextProperty("modelLeft", modelLeftMap);
context.setContextProperty("modelRight", modelRightMap);
if (evaluateBoolExpression(expression))
return -1;
context.setContextProperty("modelLeft", modelRightMap);
context.setContextProperty("modelRight", modelLeftMap);
if (evaluateBoolExpression(expression))
return 1;
}
return 0;
}
void ExpressionSorter::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
QVariantMap modelLeftMap, modelRightMap;
// what about roles changes ?
for (const QByteArray& roleName : proxyModel.roleNames().values()) {
modelLeftMap.insert(roleName, QVariant());
modelRightMap.insert(roleName, QVariant());
}
modelLeftMap.insert("index", -1);
modelRightMap.insert("index", -1);
m_context->setContextProperty("modelLeft", modelLeftMap);
m_context->setContextProperty("modelRight", modelRightMap);
updateExpression();
}
void ExpressionSorter::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionSorter::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
void registerSorterTypes() {
qmlRegisterUncreatableType<Sorter>("SortFilterProxyModel", 0, 2, "Sorter", "Sorter is an abstract class");
qmlRegisterType<RoleSorter>("SortFilterProxyModel", 0, 2, "RoleSorter");
qmlRegisterType<StringSorter>("SortFilterProxyModel", 0, 2, "StringSorter");
qmlRegisterType<ExpressionSorter>("SortFilterProxyModel", 0, 2, "ExpressionSorter");
}
Q_COREAPP_STARTUP_FUNCTION(registerSorterTypes)
}

172
sorter.h
View File

@ -1,172 +0,0 @@
#ifndef SORTER_H
#define SORTER_H
#include <QObject>
#include <QQmlListProperty>
#include <QQmlExpression>
#include <QCollator>
namespace qqsfpm {
class Sorter;
class QQmlSortFilterProxyModel;
class SorterContainer {
public:
virtual ~SorterContainer();
QList<Sorter*> sorters() const;
void appendSorter(Sorter* sorter);
void removeSorter(Sorter* sorter);
void clearSorters();
QQmlListProperty<Sorter> sortersListProperty();
protected:
QList<Sorter*> m_sorters;
private:
virtual void onSorterAppended(Sorter* sorter) = 0;
virtual void onSorterRemoved(Sorter* sorter) = 0;
virtual void onSortersCleared() = 0;
static void append_sorter(QQmlListProperty<Sorter>* list, Sorter* sorter);
static int count_sorter(QQmlListProperty<Sorter>* list);
static Sorter* at_sorter(QQmlListProperty<Sorter>* list, int index);
static void clear_sorters(QQmlListProperty<Sorter>* list);
};
}
#define SorterContainer_iid "fr.grecko.SortFilterProxyModel.SorterContainer"
Q_DECLARE_INTERFACE(qqsfpm::SorterContainer, SorterContainer_iid)
namespace qqsfpm {
class Sorter : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool ascendingOrder READ ascendingOrder WRITE setAscendingOrder NOTIFY sortOrderChanged)
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
public:
Sorter(QObject* parent = nullptr);
virtual ~Sorter() = 0;
bool enabled() const;
void setEnabled(bool enabled);
bool ascendingOrder() const;
void setAscendingOrder(bool ascendingOrder);
Qt::SortOrder sortOrder() const;
void setSortOrder(Qt::SortOrder sortOrder);
int compareRows(const QModelIndex& source_left, const QModelIndex& source_right, const QQmlSortFilterProxyModel& proxyModel) const;
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
Q_SIGNALS:
void enabledChanged();
void sortOrderChanged();
void invalidated();
protected:
virtual int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
virtual bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
void invalidate();
private:
bool m_enabled = true;
Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
};
class RoleSorter : public Sorter
{
Q_OBJECT
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
public:
using Sorter::Sorter;
const QString& roleName() const;
void setRoleName(const QString& roleName);
Q_SIGNALS:
void roleNameChanged();
protected:
QPair<QVariant, QVariant> sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
QString m_roleName;
};
class StringSorter : public RoleSorter
{
Q_OBJECT
Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
Q_PROPERTY(bool ignorePunctation READ ignorePunctation WRITE setIgnorePunctation NOTIFY ignorePunctationChanged)
Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged)
Q_PROPERTY(bool numericMode READ numericMode WRITE setNumericMode NOTIFY numericModeChanged)
public:
using RoleSorter::RoleSorter;
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
bool ignorePunctation() const;
void setIgnorePunctation(bool ignorePunctation);
QLocale locale() const;
void setLocale(const QLocale& locale);
bool numericMode() const;
void setNumericMode(bool numericMode);
Q_SIGNALS:
void caseSensitivityChanged();
void ignorePunctationChanged();
void localeChanged();
void numericModeChanged();
protected:
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
QCollator m_collator;
};
class ExpressionSorter : public Sorter
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using Sorter::Sorter;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void expressionChanged();
protected:
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
#endif // SORTER_H

View File

@ -0,0 +1,145 @@
#include "expressionsorter.h"
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype ExpressionSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\brief Sorts row with a custom sorting
An ExpressionSorter is a \l Sorter allowing to implement custom sorting based on a javascript expression.
*/
/*!
\qmlproperty expression ExpressionSorter::expression
An expression to implement custom sorting. It must evaluate to a bool.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding}, except that it will be evaluated for each of the source model's rows.
Model data is accessible for both rows with the \c modelLeft, and \c modelRight properties:
\code
sorters: ExpressionSorter {
expression: {
return modelLeft.someRole < modelRight.someRole;
}
}
\endcode
The \c index of the row is also available through \c modelLeft and \c modelRight.
The expression should return \c true if the value of the left item is less than the value of the right item, otherwise returns false.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index* or in \c model*) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionSorter::expression() const
{
return m_scriptString;
}
void ExpressionSorter::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionSorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
bool evaluateBoolExpression(QQmlExpression& expression)
{
QVariant variantResult = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return false;
}
if (variantResult.canConvert<bool>()) {
return variantResult.toBool();
} else {
qWarning("%s:%i:%i : Can't convert result to bool",
expression.sourceFile().toUtf8().data(),
expression.lineNumber(),
expression.columnNumber());
return false;
}
}
int ExpressionSorter::compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelLeftMap, modelRightMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
for (auto it = roles.cbegin(); it != roles.cend(); ++it) {
modelLeftMap.insert(it.value(), proxyModel.sourceData(sourceLeft, it.key()));
modelRightMap.insert(it.value(), proxyModel.sourceData(sourceRight, it.key()));
}
modelLeftMap.insert("index", sourceLeft.row());
modelRightMap.insert("index", sourceRight.row());
QQmlExpression expression(m_scriptString, &context);
context.setContextProperty("modelLeft", modelLeftMap);
context.setContextProperty("modelRight", modelRightMap);
if (evaluateBoolExpression(expression))
return -1;
context.setContextProperty("modelLeft", modelRightMap);
context.setContextProperty("modelRight", modelLeftMap);
if (evaluateBoolExpression(expression))
return 1;
}
return 0;
}
void ExpressionSorter::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
QVariantMap modelLeftMap, modelRightMap;
// what about roles changes ?
for (const QByteArray& roleName : proxyModel.roleNames().values()) {
modelLeftMap.insert(roleName, QVariant());
modelRightMap.insert(roleName, QVariant());
}
modelLeftMap.insert("index", -1);
modelRightMap.insert("index", -1);
m_context->setContextProperty("modelLeft", modelLeftMap);
m_context->setContextProperty("modelRight", modelRightMap);
updateExpression();
}
void ExpressionSorter::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionSorter::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
}

View File

@ -0,0 +1,43 @@
#ifndef EXPRESSIONSORTER_H
#define EXPRESSIONSORTER_H
#include "sorter.h"
#include <QQmlScriptString>
class QQmlExpression;
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class ExpressionSorter : public Sorter
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using Sorter::Sorter;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void expressionChanged();
protected:
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
#endif // EXPRESSIONSORTER_H

68
sorters/rolesorter.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "rolesorter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype RoleSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\brief Sorts rows based on a source model role
A RoleSorter is a simple \l Sorter that sorts rows based on a source model role.
In the following example, rows with be sorted by their \c lastName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: RoleSorter { roleName: "lastName" }
}
\endcode
*/
/*!
\qmlproperty string RoleSorter::roleName
This property holds the role name that the sorter is using to query the source model's data when sorting items.
*/
const QString& RoleSorter::roleName() const
{
return m_roleName;
}
void RoleSorter::setRoleName(const QString& roleName)
{
if (m_roleName == roleName)
return;
m_roleName = roleName;
Q_EMIT roleNameChanged();
invalidate();
}
QPair<QVariant, QVariant> RoleSorter::sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair;
int role = proxyModel.roleForName(m_roleName);
if (role == -1)
return pair;
pair.first = proxyModel.sourceData(sourceLeft, role);
pair.second = proxyModel.sourceData(sourceRight, role);
return pair;
}
int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair = sourceData(sourceLeft, sourceRight, proxyModel);
QVariant leftValue = pair.first;
QVariant rightValue = pair.second;
if (leftValue < rightValue)
return -1;
if (leftValue > rightValue)
return 1;
return 0;
}
}

32
sorters/rolesorter.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef ROLESORTER_H
#define ROLESORTER_H
#include "sorter.h"
namespace qqsfpm {
class RoleSorter : public Sorter
{
Q_OBJECT
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
public:
using Sorter::Sorter;
const QString& roleName() const;
void setRoleName(const QString& roleName);
Q_SIGNALS:
void roleNameChanged();
protected:
QPair<QVariant, QVariant> sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
QString m_roleName;
};
}
#endif // ROLESORTER_H

116
sorters/sorter.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "sorter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype Sorter
\inqmlmodule SortFilterProxyModel
\brief Base type for the \l SortFilterProxyModel sorters
The Sorter type cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other sorters types that inherit from it.
Attempting to use the Sorter type directly will result in an error.
*/
Sorter::Sorter(QObject *parent) : QObject(parent)
{
}
Sorter::~Sorter() = default;
/*!
\qmlproperty bool Sorter::enabled
This property holds whether the sorter is enabled.
A disabled sorter will not change the order of the rows.
By default, sorters are enabled.
*/
bool Sorter::enabled() const
{
return m_enabled;
}
void Sorter::setEnabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
Q_EMIT enabledChanged();
Q_EMIT invalidated();
}
bool Sorter::ascendingOrder() const
{
return sortOrder() == Qt::AscendingOrder;
}
void Sorter::setAscendingOrder(bool ascendingOrder)
{
setSortOrder(ascendingOrder ? Qt::AscendingOrder : Qt::DescendingOrder);
}
/*!
\qmlproperty Qt::SortOrder Sorter::sortOrder
This property holds the sort order of this sorter.
\value Qt.AscendingOrder The items are sorted ascending e.g. starts with 'AAA' ends with 'ZZZ' in Latin-1 locales
\value Qt.DescendingOrder The items are sorted descending e.g. starts with 'ZZZ' ends with 'AAA' in Latin-1 locales
By default, sorting is in ascending order.
*/
Qt::SortOrder Sorter::sortOrder() const
{
return m_sortOrder;
}
void Sorter::setSortOrder(Qt::SortOrder sortOrder)
{
if (m_sortOrder == sortOrder)
return;
m_sortOrder = sortOrder;
Q_EMIT sortOrderChanged();
invalidate();
}
int Sorter::compareRows(const QModelIndex &source_left, const QModelIndex &source_right, const QQmlSortFilterProxyModel& proxyModel) const
{
int comparison = compare(source_left, source_right, proxyModel);
return (m_sortOrder == Qt::AscendingOrder) ? comparison : -comparison;
}
int Sorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
if (lessThan(sourceLeft, sourceRight, proxyModel))
return -1;
if (lessThan(sourceRight, sourceLeft, proxyModel))
return 1;
return 0;
}
void Sorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
Q_UNUSED(proxyModel)
}
bool Sorter::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
Q_UNUSED(sourceLeft)
Q_UNUSED(sourceRight)
Q_UNUSED(proxyModel)
return false;
}
void Sorter::invalidate()
{
if (m_enabled)
Q_EMIT invalidated();
}
}

52
sorters/sorter.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef SORTER_H
#define SORTER_H
#include <QObject>
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class Sorter : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool ascendingOrder READ ascendingOrder WRITE setAscendingOrder NOTIFY sortOrderChanged)
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
public:
Sorter(QObject* parent = nullptr);
virtual ~Sorter() = 0;
bool enabled() const;
void setEnabled(bool enabled);
bool ascendingOrder() const;
void setAscendingOrder(bool ascendingOrder);
Qt::SortOrder sortOrder() const;
void setSortOrder(Qt::SortOrder sortOrder);
int compareRows(const QModelIndex& source_left, const QModelIndex& source_right, const QQmlSortFilterProxyModel& proxyModel) const;
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
Q_SIGNALS:
void enabledChanged();
void sortOrderChanged();
void invalidated();
protected:
virtual int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
virtual bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
void invalidate();
private:
bool m_enabled = true;
Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
};
}
#endif // SORTER_H

View File

@ -0,0 +1,69 @@
#include "sortercontainer.h"
namespace qqsfpm {
SorterContainer::~SorterContainer()
{
}
QList<Sorter*> SorterContainer::sorters() const
{
return m_sorters;
}
void SorterContainer::appendSorter(Sorter* sorter)
{
m_sorters.append(sorter);
onSorterAppended(sorter);
}
void SorterContainer::removeSorter(Sorter *sorter)
{
m_sorters.removeOne(sorter);
onSorterRemoved(sorter);
}
void SorterContainer::clearSorters()
{
m_sorters.clear();
onSortersCleared();
}
QQmlListProperty<Sorter> SorterContainer::sortersListProperty()
{
return QQmlListProperty<Sorter>(reinterpret_cast<QObject*>(this), &m_sorters,
&SorterContainer::append_sorter,
&SorterContainer::count_sorter,
&SorterContainer::at_sorter,
&SorterContainer::clear_sorters);
}
void SorterContainer::append_sorter(QQmlListProperty<Sorter>* list, Sorter* sorter)
{
if (!sorter)
return;
SorterContainer* that = reinterpret_cast<SorterContainer*>(list->object);
that->appendSorter(sorter);
}
int SorterContainer::count_sorter(QQmlListProperty<Sorter>* list)
{
QList<Sorter*>* sorters = static_cast<QList<Sorter*>*>(list->data);
return sorters->count();
}
Sorter* SorterContainer::at_sorter(QQmlListProperty<Sorter>* list, int index)
{
QList<Sorter*>* sorters = static_cast<QList<Sorter*>*>(list->data);
return sorters->at(index);
}
void SorterContainer::clear_sorters(QQmlListProperty<Sorter> *list)
{
SorterContainer* that = reinterpret_cast<SorterContainer*>(list->object);
that->clearSorters();
}
}

42
sorters/sortercontainer.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef SORTERSSORTERCONTAINER_H
#define SORTERSSORTERCONTAINER_H
#include <QList>
#include <QQmlListProperty>
namespace qqsfpm {
class Sorter;
class QQmlSortFilterProxyModel;
class SorterContainer {
public:
virtual ~SorterContainer();
QList<Sorter*> sorters() const;
void appendSorter(Sorter* sorter);
void removeSorter(Sorter* sorter);
void clearSorters();
QQmlListProperty<Sorter> sortersListProperty();
protected:
QList<Sorter*> m_sorters;
private:
virtual void onSorterAppended(Sorter* sorter) = 0;
virtual void onSorterRemoved(Sorter* sorter) = 0;
virtual void onSortersCleared() = 0;
static void append_sorter(QQmlListProperty<Sorter>* list, Sorter* sorter);
static int count_sorter(QQmlListProperty<Sorter>* list);
static Sorter* at_sorter(QQmlListProperty<Sorter>* list, int index);
static void clear_sorters(QQmlListProperty<Sorter>* list);
};
}
#define SorterContainer_iid "fr.grecko.SortFilterProxyModel.SorterContainer"
Q_DECLARE_INTERFACE(qqsfpm::SorterContainer, SorterContainer_iid)
#endif // SORTERSSORTERCONTAINER_H

View File

@ -0,0 +1,19 @@
#include "sorter.h"
#include "rolesorter.h"
#include "stringsorter.h"
#include "expressionsorter.h"
#include <QQmlEngine>
#include <QCoreApplication>
namespace qqsfpm {
void registerSorterTypes() {
qmlRegisterUncreatableType<Sorter>("SortFilterProxyModel", 0, 2, "Sorter", "Sorter is an abstract class");
qmlRegisterType<RoleSorter>("SortFilterProxyModel", 0, 2, "RoleSorter");
qmlRegisterType<StringSorter>("SortFilterProxyModel", 0, 2, "StringSorter");
qmlRegisterType<ExpressionSorter>("SortFilterProxyModel", 0, 2, "ExpressionSorter");
}
Q_COREAPP_STARTUP_FUNCTION(registerSorterTypes)
}

116
sorters/stringsorter.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "stringsorter.h"
namespace qqsfpm {
/*!
\qmltype StringSorter
\inherits RoleSorter
\inqmlmodule SortFilterProxyModel
\brief Sorts rows based on a source model string role
\l StringSorter is a specialized \l RoleSorter that sorts rows based on a source model string role.
\l StringSorter compares strings according to a localized collation algorithm.
In the following example, rows with be sorted by their \c lastName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: StringSorter { roleName: "lastName" }
}
\endcode
*/
/*!
\qmlproperty Qt.CaseSensitivity StringSorter::caseSensitivity
This property holds the case sensitivity of the sorter.
*/
Qt::CaseSensitivity StringSorter::caseSensitivity() const
{
return m_collator.caseSensitivity();
}
void StringSorter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{
if (m_collator.caseSensitivity() == caseSensitivity)
return;
m_collator.setCaseSensitivity(caseSensitivity);
Q_EMIT caseSensitivityChanged();
invalidate();
}
/*!
\qmlproperty bool StringSorter::ignorePunctation
This property holds whether the sorter ignores punctation.
if \c ignorePunctuation is \c true, punctuation characters and symbols are ignored when determining sort order.
\note This property is not currently supported on Apple platforms or if Qt is configured to not use ICU on Linux.
*/
bool StringSorter::ignorePunctation() const
{
return m_collator.ignorePunctuation();
}
void StringSorter::setIgnorePunctation(bool ignorePunctation)
{
if (m_collator.ignorePunctuation() == ignorePunctation)
return;
m_collator.setIgnorePunctuation(ignorePunctation);
Q_EMIT ignorePunctationChanged();
invalidate();
}
/*!
\qmlproperty Locale StringSorter::locale
This property holds the locale of the sorter.
*/
QLocale StringSorter::locale() const
{
return m_collator.locale();
}
void StringSorter::setLocale(const QLocale &locale)
{
if (m_collator.locale() == locale)
return;
m_collator.setLocale(locale);
Q_EMIT localeChanged();
invalidate();
}
/*!
\qmlproperty bool StringSorter::numericMode
This property holds whether the numeric mode of the sorter is enabled.
This will enable proper sorting of numeric digits, so that e.g. 100 sorts after 99.
By default this mode is off.
*/
bool StringSorter::numericMode() const
{
return m_collator.numericMode();
}
void StringSorter::setNumericMode(bool numericMode)
{
if (m_collator.numericMode() == numericMode)
return;
m_collator.setNumericMode(numericMode);
Q_EMIT numericModeChanged();
invalidate();
}
int StringSorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair = sourceData(sourceLeft, sourceRight, proxyModel);
QString leftValue = pair.first.toString();
QString rightValue = pair.second.toString();
return m_collator.compare(leftValue, rightValue);
}
}

47
sorters/stringsorter.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef STRINGSORTER_H
#define STRINGSORTER_H
#include "rolesorter.h"
#include <QCollator>
namespace qqsfpm {
class StringSorter : public RoleSorter
{
Q_OBJECT
Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
Q_PROPERTY(bool ignorePunctation READ ignorePunctation WRITE setIgnorePunctation NOTIFY ignorePunctationChanged)
Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged)
Q_PROPERTY(bool numericMode READ numericMode WRITE setNumericMode NOTIFY numericModeChanged)
public:
using RoleSorter::RoleSorter;
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
bool ignorePunctation() const;
void setIgnorePunctation(bool ignorePunctation);
QLocale locale() const;
void setLocale(const QLocale& locale);
bool numericMode() const;
void setNumericMode(bool numericMode);
Q_SIGNALS:
void caseSensitivityChanged();
void ignorePunctationChanged();
void localeChanged();
void numericModeChanged();
protected:
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
QCollator m_collator;
};
}
#endif // STRINGSORTER_H

View File

@ -1,7 +1,7 @@
#ifndef INDEXSORTER_H
#define INDEXSORTER_H
#include <sorter.h>
#include "sorters/sorter.h"
class IndexSorter : public qqsfpm::Sorter
{