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 "LegendModel.h" |
9 | |
10 | #include "Chart.h" |
11 | #include "datasource/ChartDataSource.h" |
12 | |
13 | LegendModel::LegendModel(QObject *parent) |
14 | : QAbstractListModel(parent) |
15 | { |
16 | } |
17 | |
18 | QHash<int, QByteArray> LegendModel::roleNames() const |
19 | { |
20 | static QHash<int, QByteArray> names = { |
21 | {NameRole, "name" }, |
22 | {ShortNameRole, "shortName" }, |
23 | {ColorRole, "color" }, |
24 | {ValueRole, "value" }, |
25 | }; |
26 | |
27 | return names; |
28 | } |
29 | |
30 | int LegendModel::rowCount(const QModelIndex &parent) const |
31 | { |
32 | if (parent.isValid()) { |
33 | return 0; |
34 | } |
35 | |
36 | return m_items.size(); |
37 | } |
38 | |
39 | QVariant LegendModel::data(const QModelIndex &index, int role) const |
40 | { |
41 | if (!checkIndex(index, options: CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) { |
42 | return {}; |
43 | } |
44 | |
45 | switch (role) { |
46 | case NameRole: |
47 | return m_items.at(n: index.row()).name; |
48 | case ShortNameRole: |
49 | return m_items.at(n: index.row()).shortName; |
50 | case ColorRole: |
51 | return m_items.at(n: index.row()).color; |
52 | case ValueRole: |
53 | return m_items.at(n: index.row()).value; |
54 | } |
55 | |
56 | return QVariant{}; |
57 | } |
58 | |
59 | Chart *LegendModel::chart() const |
60 | { |
61 | return m_chart; |
62 | } |
63 | |
64 | void LegendModel::setChart(Chart *newChart) |
65 | { |
66 | if (newChart == m_chart) { |
67 | return; |
68 | } |
69 | |
70 | if (m_chart) { |
71 | for (const auto &connection : std::as_const(t&: m_connections)) { |
72 | disconnect(connection); |
73 | } |
74 | m_connections.clear(); |
75 | } |
76 | |
77 | m_chart = newChart; |
78 | queueUpdate(); |
79 | Q_EMIT chartChanged(); |
80 | } |
81 | |
82 | int LegendModel::sourceIndex() const |
83 | { |
84 | return m_sourceIndex; |
85 | } |
86 | |
87 | void LegendModel::setSourceIndex(int index) |
88 | { |
89 | if (index == m_sourceIndex) { |
90 | return; |
91 | } |
92 | |
93 | m_sourceIndex = index; |
94 | queueUpdate(); |
95 | Q_EMIT sourceIndexChanged(); |
96 | } |
97 | |
98 | void LegendModel::queueUpdate() |
99 | { |
100 | if (!m_updateQueued) { |
101 | m_updateQueued = true; |
102 | QMetaObject::invokeMethod(object: this, function: &LegendModel::update, type: Qt::QueuedConnection); |
103 | } |
104 | } |
105 | |
106 | void LegendModel::queueDataChange() |
107 | { |
108 | if (!m_dataChangeQueued) { |
109 | m_dataChangeQueued = true; |
110 | QMetaObject::invokeMethod(object: this, function: &LegendModel::updateData, type: Qt::QueuedConnection); |
111 | } |
112 | } |
113 | |
114 | void LegendModel::update() |
115 | { |
116 | m_updateQueued = false; |
117 | |
118 | if (!m_chart) { |
119 | return; |
120 | } |
121 | |
122 | beginResetModel(); |
123 | m_items.clear(); |
124 | |
125 | ChartDataSource *colorSource = m_chart->colorSource(); |
126 | ChartDataSource *nameSource = m_chart->nameSource(); |
127 | ChartDataSource *shortNameSource = m_chart->shortNameSource(); |
128 | |
129 | m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::colorSourceChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection)); |
130 | m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::nameSourceChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection)); |
131 | m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::destroyed, context: this, slot: &LegendModel::onChartDestroyed, type: Qt::UniqueConnection)); |
132 | |
133 | auto sources = m_chart->valueSources(); |
134 | int itemCount = countItems(); |
135 | |
136 | std::transform(first: sources.cbegin(), last: sources.cend(), result: std::back_inserter(x&: m_connections), unary_op: [this](ChartDataSource *source) { |
137 | return connect(sender: source, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection); |
138 | }); |
139 | |
140 | m_connections.push_back(x: connect(sender: m_chart, signal: &Chart::valueSourcesChanged, context: this, slot: &LegendModel::queueUpdate, type: Qt::UniqueConnection)); |
141 | |
142 | if ((!colorSource && !(nameSource || shortNameSource)) || itemCount <= 0) { |
143 | endResetModel(); |
144 | return; |
145 | } |
146 | |
147 | if (colorSource) { |
148 | m_connections.push_back(x: connect(sender: colorSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection)); |
149 | } |
150 | |
151 | if (nameSource) { |
152 | m_connections.push_back(x: connect(sender: nameSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection)); |
153 | } |
154 | |
155 | if (shortNameSource) { |
156 | m_connections.push_back(x: connect(sender: shortNameSource, signal: &ChartDataSource::dataChanged, context: this, slot: &LegendModel::queueDataChange, type: Qt::UniqueConnection)); |
157 | } |
158 | |
159 | for (int i = 0; i < itemCount; ++i) { |
160 | LegendItem item; |
161 | item.name = nameSource ? nameSource->item(index: i).toString() : QString(); |
162 | item.shortName = shortNameSource ? shortNameSource->item(index: i).toString() : QString(); |
163 | item.color = colorSource ? colorSource->item(index: i).value<QColor>() : QColor(); |
164 | item.value = getValueForItem(item: i); |
165 | m_items.push_back(x: item); |
166 | } |
167 | |
168 | endResetModel(); |
169 | } |
170 | |
171 | void LegendModel::updateData() |
172 | { |
173 | m_dataChangeQueued = false; |
174 | |
175 | if (!m_chart) { |
176 | return; |
177 | } |
178 | |
179 | ChartDataSource *colorSource = m_chart->colorSource(); |
180 | ChartDataSource *nameSource = m_chart->nameSource(); |
181 | ChartDataSource *shortNameSource = m_chart->shortNameSource(); |
182 | |
183 | auto itemCount = countItems(); |
184 | |
185 | if (itemCount != int(m_items.size())) { |
186 | // Number of items changed, so trigger a full update |
187 | queueUpdate(); |
188 | return; |
189 | } |
190 | |
191 | QList<QList<int>> changedRows(itemCount); |
192 | |
193 | std::for_each(first: m_items.begin(), last: m_items.end(), f: [&, i = 0](LegendItem &item) mutable { |
194 | auto name = nameSource ? nameSource->item(index: i).toString() : QString{}; |
195 | if (item.name != name) { |
196 | item.name = name; |
197 | changedRows[i] << NameRole; |
198 | } |
199 | |
200 | auto shortName = shortNameSource ? shortNameSource->item(index: i).toString() : QString{}; |
201 | if (item.shortName != shortName) { |
202 | item.shortName = shortName; |
203 | changedRows[i] << ShortNameRole; |
204 | } |
205 | |
206 | auto color = colorSource ? colorSource->item(index: i).toString() : QColor{}; |
207 | if (item.color != color) { |
208 | item.color = color; |
209 | changedRows[i] << ColorRole; |
210 | } |
211 | |
212 | auto value = getValueForItem(item: i); |
213 | if (item.value != value) { |
214 | item.value = value; |
215 | changedRows[i] << ValueRole; |
216 | } |
217 | |
218 | i++; |
219 | }); |
220 | |
221 | for (auto i = 0; i < changedRows.size(); ++i) { |
222 | auto changedRoles = changedRows.at(i); |
223 | if (!changedRoles.isEmpty()) { |
224 | Q_EMIT dataChanged(topLeft: index(row: i, column: 0), bottomRight: index(row: i, column: 0), roles: changedRoles); |
225 | } |
226 | } |
227 | } |
228 | |
229 | int LegendModel::countItems() |
230 | { |
231 | auto sources = m_chart->valueSources(); |
232 | int itemCount = 0; |
233 | |
234 | switch (m_chart->indexingMode()) { |
235 | case Chart::IndexSourceValues: |
236 | if (sources.count() > 0) { |
237 | itemCount = sources.at(i: 0)->itemCount(); |
238 | } |
239 | break; |
240 | case Chart::IndexEachSource: |
241 | itemCount = sources.count(); |
242 | break; |
243 | case Chart::IndexAllValues: |
244 | itemCount = std::accumulate(first: sources.cbegin(), last: sources.cend(), init: 0, binary_op: [](int current, ChartDataSource *source) -> int { |
245 | return current + source->itemCount(); |
246 | }); |
247 | break; |
248 | } |
249 | |
250 | return itemCount; |
251 | } |
252 | |
253 | QVariant LegendModel::getValueForItem(int item) |
254 | { |
255 | const auto sources = m_chart->valueSources(); |
256 | auto value = QVariant{}; |
257 | |
258 | switch (m_chart->indexingMode()) { |
259 | case Chart::IndexSourceValues: |
260 | value = sources.at(i: 0)->item(index: item); |
261 | break; |
262 | case Chart::IndexEachSource: |
263 | value = sources.at(i: item)->first(); |
264 | break; |
265 | case Chart::IndexAllValues: |
266 | for (auto source : sources) { |
267 | if (item >= source->itemCount()) { |
268 | item -= source->itemCount(); |
269 | } else { |
270 | value = source->item(index: item); |
271 | break; |
272 | } |
273 | } |
274 | break; |
275 | } |
276 | |
277 | return value; |
278 | } |
279 | |
280 | void LegendModel::onChartDestroyed() |
281 | { |
282 | beginResetModel(); |
283 | m_items.clear(); |
284 | m_chart = nullptr; |
285 | endResetModel(); |
286 | } |
287 | |
288 | #include "moc_LegendModel.cpp" |
289 | |