1/*
2 * This file is part of KQuickCharts
3 * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8#include "ModelSource.h"
9
10#include <QMetaProperty>
11
12#include "charts_datasource_logging.h"
13
14ModelSource::ModelSource(QObject *parent)
15 : ChartDataSource(parent)
16{
17 connect(sender: this, signal: &ModelSource::modelChanged, context: this, slot: &ModelSource::dataChanged);
18 connect(sender: this, signal: &ModelSource::columnChanged, context: this, slot: &ModelSource::dataChanged);
19 connect(sender: this, signal: &ModelSource::roleChanged, context: this, slot: &ModelSource::dataChanged);
20 connect(sender: this, signal: &ModelSource::indexColumnsChanged, context: this, slot: &ModelSource::dataChanged);
21}
22
23int ModelSource::role() const
24{
25 if (!m_model) {
26 return -1;
27 }
28
29 if (m_role < 0 && !m_roleName.isEmpty()) {
30 m_role = m_model->roleNames().key(value: m_roleName.toLatin1(), defaultKey: -1);
31 }
32
33 return m_role;
34}
35
36QString ModelSource::roleName() const
37{
38 return m_roleName;
39}
40
41int ModelSource::column() const
42{
43 return m_column;
44}
45
46QAbstractItemModel *ModelSource::model() const
47{
48 return m_model;
49}
50
51bool ModelSource::indexColumns() const
52{
53 return m_indexColumns;
54}
55
56int ModelSource::itemCount() const
57{
58 if (!m_model) {
59 return 0;
60 }
61
62 return m_indexColumns ? m_model->columnCount() : m_model->rowCount();
63}
64
65QVariant ModelSource::item(int index) const
66{
67 if (!m_model) {
68 return {};
69 }
70
71 // For certain model (QML ListModel for example), the roleNames() are more
72 // dynamic and may only be valid when this method gets called. So try and
73 // lookup the role first before anything else.
74 if (m_role < 0) {
75 if (m_roleName.isEmpty()) {
76 return QVariant{};
77 }
78
79 m_role = m_model->roleNames().key(value: m_roleName.toLatin1(), defaultKey: -1);
80 if (m_role < 0) {
81 qCWarning(DATASOURCE) << "ModelSource: Invalid role " << m_role << m_roleName;
82 return QVariant{};
83 }
84 }
85
86 if (!m_indexColumns && (m_column < 0 || m_column > m_model->columnCount())) {
87 qCDebug(DATASOURCE) << "ModelSource: Invalid column" << m_column;
88 return QVariant{};
89 }
90
91 auto modelIndex = m_indexColumns ? m_model->index(row: 0, column: index) : m_model->index(row: index, column: m_column);
92 if (modelIndex.isValid()) {
93 return m_model->data(index: modelIndex, role: m_role);
94 }
95
96 return QVariant{};
97}
98
99QVariant ModelSource::minimum() const
100{
101 if (!m_model || itemCount() <= 0) {
102 return {};
103 }
104
105 if (m_minimum.isValid()) {
106 return m_minimum;
107 }
108
109 auto minProperty = m_model->property(name: "minimum");
110 auto maxProperty = m_model->property(name: "maximum");
111 if (minProperty.isValid() && minProperty != maxProperty) {
112 return minProperty;
113 }
114
115 QVariant result = std::numeric_limits<float>::max();
116 for (int i = 0; i < itemCount(); ++i) {
117 result = std::min(a: result, b: item(index: i), comp: variantCompare);
118 }
119 return result;
120}
121
122QVariant ModelSource::maximum() const
123{
124 if (!m_model || itemCount() <= 0) {
125 return {};
126 }
127
128 if (m_maximum.isValid()) {
129 return m_maximum;
130 }
131
132 auto minProperty = m_model->property(name: "minimum");
133 auto maxProperty = m_model->property(name: "maximum");
134 if (maxProperty.isValid() && maxProperty != minProperty) {
135 return maxProperty;
136 }
137
138 QVariant result = std::numeric_limits<float>::min();
139 for (int i = 0; i < itemCount(); ++i) {
140 result = std::max(a: result, b: item(index: i), comp: variantCompare);
141 }
142 return result;
143}
144
145void ModelSource::setRole(int role)
146{
147 if (role == m_role) {
148 return;
149 }
150
151 m_role = role;
152 if (m_model) {
153 m_roleName = QString::fromLatin1(ba: m_model->roleNames().value(key: role));
154 Q_EMIT roleNameChanged();
155 }
156 Q_EMIT roleChanged();
157}
158
159void ModelSource::setRoleName(const QString &name)
160{
161 if (name == m_roleName) {
162 return;
163 }
164
165 m_roleName = name;
166 if (m_model) {
167 m_role = m_model->roleNames().key(value: m_roleName.toLatin1(), defaultKey: -1);
168 Q_EMIT roleChanged();
169 }
170 Q_EMIT roleNameChanged();
171}
172
173void ModelSource::setColumn(int column)
174{
175 if (column == m_column) {
176 return;
177 }
178
179 m_column = column;
180 Q_EMIT columnChanged();
181}
182
183void ModelSource::setIndexColumns(bool index)
184{
185 if (index == m_indexColumns) {
186 return;
187 }
188
189 m_indexColumns = index;
190 Q_EMIT indexColumnsChanged();
191}
192
193void ModelSource::setModel(QAbstractItemModel *model)
194{
195 if (m_model == model) {
196 return;
197 }
198
199 if (m_model) {
200 m_model->disconnect(receiver: this);
201 m_minimum = QVariant{};
202 m_maximum = QVariant{};
203 }
204
205 m_model = model;
206 if (m_model) {
207 connect(sender: m_model, signal: &QAbstractItemModel::rowsInserted, context: this, slot: &ModelSource::dataChanged);
208 connect(sender: m_model, signal: &QAbstractItemModel::rowsRemoved, context: this, slot: &ModelSource::dataChanged);
209 connect(sender: m_model, signal: &QAbstractItemModel::rowsMoved, context: this, slot: &ModelSource::dataChanged);
210 connect(sender: m_model, signal: &QAbstractItemModel::modelReset, context: this, slot: &ModelSource::dataChanged);
211 connect(sender: m_model, signal: &QAbstractItemModel::dataChanged, context: this, slot: &ModelSource::dataChanged);
212 connect(sender: m_model, signal: &QAbstractItemModel::layoutChanged, context: this, slot: &ModelSource::dataChanged);
213
214 connect(sender: m_model, signal: &QAbstractItemModel::destroyed, context: this, slot: [this]() {
215 m_minimum = QVariant{};
216 m_maximum = QVariant{};
217 m_model = nullptr;
218 });
219
220 auto minimumIndex = m_model->metaObject()->indexOfProperty(name: "minimum");
221 if (minimumIndex != -1) {
222 auto minimum = m_model->metaObject()->property(index: minimumIndex);
223 if (minimum.hasNotifySignal()) {
224 auto slot = metaObject()->method(index: metaObject()->indexOfSlot(slot: "onMinimumChanged()"));
225 connect(sender: m_model, signal: minimum.notifySignal(), receiver: this, method: slot);
226 m_minimum = minimum.read(obj: m_model);
227 }
228 }
229
230 auto maximumIndex = m_model->metaObject()->indexOfProperty(name: "maximum");
231 if (maximumIndex != -1) {
232 auto maximum = m_model->metaObject()->property(index: maximumIndex);
233 if (maximum.hasNotifySignal()) {
234 auto slot = metaObject()->method(index: metaObject()->indexOfSlot(slot: "onMaximumChanged()"));
235 connect(sender: m_model, signal: maximum.notifySignal(), receiver: this, method: slot);
236 m_maximum = maximum.read(obj: m_model);
237 }
238 }
239 }
240
241 Q_EMIT modelChanged();
242}
243
244void ModelSource::onMinimumChanged()
245{
246 auto newMinimum = m_model->property(name: "minimum");
247 if (newMinimum.isValid() && newMinimum != m_minimum) {
248 m_minimum = newMinimum;
249 }
250}
251
252void ModelSource::onMaximumChanged()
253{
254 auto newMaximum = m_model->property(name: "maximum");
255 if (newMaximum.isValid() && newMaximum != m_maximum) {
256 m_maximum = newMaximum;
257 }
258}
259
260#include "moc_ModelSource.cpp"
261

source code of kquickcharts/src/datasource/ModelSource.cpp