diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -24,6 +24,7 @@ kmodelindexproxymappertest.cpp krecursivefilterproxymodeltest.cpp krearrangecolumnsproxymodeltest.cpp + knumbermodeltest.cpp LINK_LIBRARIES KF5::ItemModels Qt5::Test Qt5::Widgets proxymodeltestsuite ) diff --git a/autotests/knumbermodeltest.cpp b/autotests/knumbermodeltest.cpp new file mode 100644 --- /dev/null +++ b/autotests/knumbermodeltest.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 David Edmundson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include +#include + +#include +#include "test_model_helpers.h" +using namespace TestModelHelpers; + +Q_DECLARE_METATYPE(QModelIndex) + +class tst_KNumberModel: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void init() + { + QLocale::setDefault(QLocale::c()); + } + + void basicTest() + { + KNumberModel m; + m.setMinimumValue(3); + m.setMaximumValue(5); + QCOMPARE(m.rowCount(), 3); + QCOMPARE(m.data(m.index(0, 0), Qt::DisplayRole), QVariant("3")); + QCOMPARE(m.data(m.index(1, 0), Qt::DisplayRole), QVariant("4")); + QCOMPARE(m.data(m.index(2, 0), Qt::DisplayRole), QVariant("5")); + } + + void testUpdates() + { + KNumberModel m; + m.setMinimumValue(3); + m.setMaximumValue(5); + QSignalSpy resetSpy(&m, &QAbstractItemModel::modelReset); + m.setMaximumValue(7); + QVERIFY(resetSpy.count() == 1); + } + + void testStep() + { + KNumberModel m; + m.setMinimumValue(3); + m.setMaximumValue(4); + m.setStepSize(0.4); + QCOMPARE(m.rowCount(), 3); + QCOMPARE(m.data(m.index(2, 0), Qt::DisplayRole), QVariant("3.8")); + } + + void testLocale() + { + KNumberModel m; + m.setMinimumValue(1000); + m.setMaximumValue(1000); + QCOMPARE(m.rowCount(), 1); + QCOMPARE(m.data(m.index(0, 0), Qt::DisplayRole), QVariant("1,000")); + + m.setFormattingOptions(QLocale::OmitGroupSeparator); + QCOMPARE(m.data(m.index(0, 0), Qt::DisplayRole), QVariant("1000")); + + } + +private: +}; + +QTEST_MAIN(tst_KNumberModel) + +#include "knumbermodeltest.moc" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -7,6 +7,7 @@ kextracolumnsproxymodel.cpp klinkitemselectionmodel.cpp kmodelindexproxymapper.cpp + knumbermodel.cpp krearrangecolumnsproxymodel.cpp krecursivefilterproxymodel.cpp kselectionproxymodel.cpp diff --git a/src/core/knumbermodel.h b/src/core/knumbermodel.h new file mode 100644 --- /dev/null +++ b/src/core/knumbermodel.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 David Edmundson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KNUMBERMODEL_H +#define KNUMBERMODEL_H + +#include +#include +#include + +#include "kitemmodels_export.h" + +class KNumberModelPrivate; + +/** + * Creates a model of entries from N to M with rows at a given interval + * + * The model contains two Roles + * display, the number represented as a string + * value, the actual value as a number + * + * @since 5.NUMBERMODEL_VERSION + */ +class KITEMMODELS_EXPORT KNumberModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY (qreal minimumValue READ minimumValue WRITE setMinimumValue NOTIFY minimumValueChanged) + Q_PROPERTY (qreal maximumValue READ maximumValue WRITE setMaximumValue NOTIFY maximumValueChanged) + Q_PROPERTY (qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged) + Q_PROPERTY (QLocale::NumberOptions formattingOptions READ formattingOptions WRITE setFormattingOptions NOTIFY formattingOptionsChanged) + +public: + KNumberModel(QObject *parent = nullptr); + ~KNumberModel() override; + + enum Roles { + DisplayRole = Qt::DisplayRole, + ValueRole = Qt::UserRole + }; + + /** + * The minimum value for the model. + * The default value is 1. + */ + void setMinimumValue(qreal minimumValue); + qreal minimumValue() const; + + /** + * The maximum value in this model. + * The default value is 1. + * + * @note If max is a multiple of @arg step it will be included. Otherwise it will not be reached + * i.e in a model with a min of 0 with a maximum of 1 with a step size of 0.3, the final row will be 0.9. + * + */ + void setMaximumValue(qreal maximumValue); + qreal maximumValue() const; + + /* + * Step between listed entries + * The default value is 1.0 + */ + void setStepSize(qreal stepSize); + qreal stepSize() const; + + /** + * Define how the string representation of the number should be presented + * i.e "1,000" or "1000" + * Default is QLocale::Default + */ + void setFormattingOptions(QLocale::NumberOptions options); + QLocale::NumberOptions formattingOptions() const; + + /** + * Returns the value represnted at the given index. + */ + qreal value(const QModelIndex &index) const; + + int rowCount(const QModelIndex &index = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + +Q_SIGNALS: + void minimumValueChanged(); + void maximumValueChanged(); + void stepSizeChanged(); + void formattingOptionsChanged(); + +private: + QScopedPointer d; +}; + +#endif diff --git a/src/core/knumbermodel.cpp b/src/core/knumbermodel.cpp new file mode 100644 --- /dev/null +++ b/src/core/knumbermodel.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 David Edmundson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "knumbermodel.h" + +#include +#include + +#include + +class KNumberModelPrivate +{ +public: + qreal minimumValue = 0.0; + qreal maximumValue = 0.0; + qreal stepSize = 1.0; + QLocale::NumberOptions formattingOptions = QLocale::DefaultNumberOptions; +}; + +KNumberModel::KNumberModel(QObject *parent): + QAbstractListModel(parent), + d(new KNumberModelPrivate) +{} + +KNumberModel::~KNumberModel() +{} + +void KNumberModel::setMinimumValue(qreal minimumValue) +{ + if (minimumValue == d->minimumValue) { + return; + } + beginResetModel(); + d->minimumValue = minimumValue; + endResetModel(); + emit minimumValueChanged(); +} + +qreal KNumberModel::minimumValue() const +{ + return d->minimumValue; +} + +void KNumberModel::setMaximumValue(qreal maximumValue) +{ + if (maximumValue == d->maximumValue) { + return; + } + beginResetModel(); + d->maximumValue = maximumValue; + endResetModel(); + emit maximumValueChanged(); +} + +qreal KNumberModel::maximumValue() const +{ + return d->maximumValue; +} + +void KNumberModel::setStepSize(qreal stepSize) +{ + Q_ASSERT(stepSize != 0); + if (stepSize == d->stepSize) { + return; + } + beginResetModel(); + d->stepSize = stepSize; + endResetModel(); + emit stepSizeChanged(); +} + +qreal KNumberModel::stepSize() const +{ + return d->stepSize; +} + +void KNumberModel::setFormattingOptions(QLocale::NumberOptions formattingOptions) +{ + if (d->formattingOptions == formattingOptions) { + return; + } + d->formattingOptions = formattingOptions; + + if (rowCount() == 0) { + return; + } + dataChanged(index(0, 0, QModelIndex()), index(rowCount(), 0, QModelIndex()), QVector{DisplayRole}); + emit formattingOptionsChanged(); +} + +QLocale::NumberOptions KNumberModel::formattingOptions() const +{ + return d->formattingOptions; +} + +qreal KNumberModel::value(const QModelIndex &index) const +{ + if (!index.isValid()) { + return 0.0; + } + return d->minimumValue + d->stepSize * index.row(); +} + +int KNumberModel::rowCount(const QModelIndex &index) const +{ + if (index.parent().isValid()) { + return 0; + } + //1 initial entry (the minimumValue) + the number of valid steps afterwards + return 1 + std::max(0, qFloor((d->maximumValue - d->minimumValue) / d->stepSize)); +} + +QVariant KNumberModel::data(const QModelIndex &index, int role) const +{ + switch(role) { + case KNumberModel::DisplayRole: { + auto locale = QLocale::system(); + locale.setNumberOptions(d->formattingOptions); + return QVariant(locale.toString(value(index))); + } + case KNumberModel::ValueRole: + return QVariant(value(index)); + } + return QVariant(); +} + +QHash KNumberModel::roleNames() const +{ + return {{KNumberModel::DisplayRole, QByteArrayLiteral("display")}, + {KNumberModel::ValueRole, QByteArrayLiteral("value")}}; +}