1 | // Copyright (C) 2024 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | #include <QtCore/qabstractitemmodel.h> |
4 | #include <QtGraphs/qpiemodelmapper.h> |
5 | #include <QtGraphs/qpieseries.h> |
6 | #include <private/qpiemodelmapper_p.h> |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | /*! |
11 | \class QPieModelMapper |
12 | \inmodule QtGraphs |
13 | \ingroup graphs_2D |
14 | \brief The QPieModelMapper is a model mapper for pie series. |
15 | |
16 | Model mappers enable using a data model derived from the QAbstractItemModel |
17 | class as a data source for a graph. A model mapper is used to create |
18 | a connection between a data model and QPieSeries. |
19 | |
20 | Both model and pie series properties can be used to manipulate the data. The |
21 | model mapper keeps the pie series and the data model in sync. |
22 | */ |
23 | /*! |
24 | \qmltype PieModelMapper |
25 | \nativetype QPieModelMapper |
26 | \inqmlmodule QtGraphs |
27 | \ingroup graphs_qml_2D |
28 | |
29 | \brief Model mapper for pie series. |
30 | |
31 | Model mappers enable using a data model derived from the QAbstractItemModel |
32 | class as a data source for a graph. A model mapper is used to create |
33 | a connection between a data model and PieSeries. |
34 | |
35 | Both model and pie series properties can be used to manipulate the data. The |
36 | model mapper keeps the pie series and the data model in sync. |
37 | |
38 | The following QML example creates a pie series with four slices (assuming |
39 | the model has at least five rows). Each slice gets a label from column 1 and |
40 | a value from column 2. |
41 | \code |
42 | PieModelMapper { |
43 | series: pieSeries |
44 | model: customModel |
45 | labelsSection: 1 |
46 | valuesSection: 2 |
47 | firstRow: 1 |
48 | rowCount: 4 |
49 | orientation: Qt.Vertical |
50 | } |
51 | \endcode |
52 | */ |
53 | |
54 | /*! |
55 | \property QPieModelMapper::series |
56 | \brief The pie series that is used by the mapper. |
57 | |
58 | All the data in the series is discarded when it is set to the mapper. |
59 | When a new series is specified, the old series is disconnected (but it preserves its data). |
60 | */ |
61 | /*! |
62 | \qmlproperty PieSeries PieModelMapper::series |
63 | The pie series that is used by the mapper. If you define the mapper element as a child for a |
64 | PieSeries, leave this property undefined. All the data in the series is discarded when it is set to the mapper. |
65 | When new series is specified the old series is disconnected (but it preserves its data). |
66 | */ |
67 | |
68 | /*! |
69 | \property QPieModelMapper::model |
70 | \brief The model that is used by the mapper. |
71 | */ |
72 | /*! |
73 | \qmlproperty model PieModelMapper::model |
74 | The QAbstractItemModel based model that is used by the mapper. You need to implement the model |
75 | and expose it to QML. |
76 | |
77 | \note The model has to support adding and removing rows or columns and modifying |
78 | the data in the cells. |
79 | */ |
80 | |
81 | /*! |
82 | \property QPieModelMapper::valuesSection |
83 | \brief The column or row of the model that is kept in sync with the values of the pie's slices. |
84 | |
85 | The default value is -1 (invalid mapping). |
86 | |
87 | \sa QPieModelMapper::orientation |
88 | */ |
89 | /*! |
90 | \qmlproperty qsizetype PieModelMapper::valuesSection |
91 | The column or row of the model that is kept in sync with the values of the pie's slices. |
92 | The default value is -1 (invalid mapping). |
93 | |
94 | \sa orientation |
95 | */ |
96 | |
97 | /*! |
98 | \property QPieModelMapper::labelsSection |
99 | \brief The column or row of the model that is kept in sync with the labels of the pie's slices. |
100 | |
101 | The default value is -1 (invalid mapping). |
102 | |
103 | \sa QPieModelMapper::orientation |
104 | */ |
105 | /*! |
106 | \qmlproperty qsizetype PieModelMapper::labelsSection |
107 | The column or row of the model that is kept in sync with the labels of the pie's slices. |
108 | The default value is -1 (invalid mapping). |
109 | |
110 | \sa orientation |
111 | */ |
112 | |
113 | /*! |
114 | \property QPieModelMapper::first |
115 | \brief The column or row of the model that contains the first slice value. |
116 | |
117 | The minimum and default value is 0. |
118 | |
119 | \sa QPieModelMapper::orientation |
120 | */ |
121 | /*! |
122 | \qmlproperty qsizetype PieModelMapper::first |
123 | The column or row of the model that contains the first slice value. |
124 | The default value is 0. |
125 | |
126 | \sa orientation |
127 | */ |
128 | |
129 | /*! |
130 | \property QPieModelMapper::count |
131 | \brief The number of columns or rows of the model that are mapped as the data for a pie series. |
132 | |
133 | The minimum and default value is -1 (number limited by the number of rows in the model). |
134 | |
135 | \sa QPieModelMapper::orientation |
136 | */ |
137 | /*! |
138 | \qmlproperty qsizetype PieModelMapper::count |
139 | The number of columns or rows of the model that are mapped as the data for a pie series. |
140 | The default value is -1 (number limited by the number of rows in the model). |
141 | |
142 | \sa orientation |
143 | */ |
144 | |
145 | /*! |
146 | \property QPieModelMapper::orientation |
147 | \brief Tells the modelmapper how to map data from a model. If |
148 | \c{Qt::Vertical} is used, each of the model's rows defines a pie slice, and the |
149 | model's columns define the label or the value of the pie slice. When the orientation is set to |
150 | \c{Qt::Horizontal}, each of the model's columns defines a pie slice, and the model's |
151 | rows define the label or the value of the pie slice. |
152 | |
153 | The default value is \c{Qt::Vertical} |
154 | */ |
155 | /*! |
156 | \qmlproperty orientation PieModelMapper::orientation |
157 | Tells the modelmapper how to map data from a model. If |
158 | \c{Qt.Vertical} is used, each of the model's rows defines a pie slice, and the |
159 | model's columns define the label or the value of the pie slice. When the orientation is set to |
160 | \c{Qt.Horizontal}, each of the model's columns defines a pie slice, and the model's |
161 | rows define the label or the value of the pie slice. |
162 | |
163 | The default value is \c{Qt.Vertical} |
164 | */ |
165 | |
166 | /*! |
167 | \qmlsignal PieModelMapper::seriesChanged() |
168 | |
169 | This signal is emitted when the series that the mapper is connected to changes. |
170 | */ |
171 | |
172 | /*! |
173 | \qmlsignal PieModelMapper::modelChanged() |
174 | |
175 | This signal is emitted when the model that the mapper is connected to changes. |
176 | */ |
177 | |
178 | /*! |
179 | \qmlsignal PieModelMapper::valuesSectionChanged() |
180 | |
181 | This signal is emitted when the values section changes. |
182 | */ |
183 | |
184 | /*! |
185 | \qmlsignal PieModelMapper::labelsSectionChanged() |
186 | |
187 | This signal is emitted when the labels section changes. |
188 | */ |
189 | |
190 | /*! |
191 | \qmlsignal PieModelMapper::firstChanged() |
192 | This signal is emitted when the first slice changes. |
193 | */ |
194 | |
195 | /*! |
196 | \qmlsignal PieModelMapper::countChanged() |
197 | This signal is emitted when the count changes. |
198 | */ |
199 | |
200 | /*! |
201 | \qmlsignal PieModelMapper::orientationChanged() |
202 | This signal is emitted when the orientation changes. |
203 | */ |
204 | |
205 | QPieModelMapper::~QPieModelMapper() {} |
206 | |
207 | QPieModelMapper::QPieModelMapper(QObject *parent) |
208 | : QObject{*(new QPieModelMapperPrivate), parent} {} |
209 | |
210 | QPieModelMapper::QPieModelMapper(QPieModelMapperPrivate &dd, QObject *parent) |
211 | : QObject(dd, parent) {} |
212 | |
213 | void QPieModelMapper::onSliceLabelChanged() { |
214 | Q_D(QPieModelMapper); |
215 | d->onSliceLabelChanged(slice: qobject_cast<QPieSlice *>(object: sender())); |
216 | } |
217 | |
218 | void QPieModelMapper::onSliceValueChanged() { |
219 | Q_D(QPieModelMapper); |
220 | d->onSliceValueChanged(slice: qobject_cast<QPieSlice *>(object: sender())); |
221 | } |
222 | |
223 | QAbstractItemModel *QPieModelMapper::model() const { |
224 | Q_D(const QPieModelMapper); |
225 | return d->m_model; |
226 | } |
227 | |
228 | void QPieModelMapper::setModel(QAbstractItemModel *model) { |
229 | if (model == nullptr) |
230 | return; |
231 | Q_D(QPieModelMapper); |
232 | if (d->m_model) { |
233 | QObjectPrivate::disconnect(sender: d->m_model, |
234 | signal: &QAbstractItemModel::modelReset, |
235 | receiverPrivate: d, |
236 | slot: &QPieModelMapperPrivate::initializePieFromModel); |
237 | QObjectPrivate::disconnect(sender: d->m_model, |
238 | signal: &QAbstractItemModel::dataChanged, |
239 | receiverPrivate: d, |
240 | slot: &QPieModelMapperPrivate::onModelUpdated); |
241 | QObjectPrivate::disconnect(sender: d->m_model, |
242 | signal: &QAbstractItemModel::rowsInserted, |
243 | receiverPrivate: d, |
244 | slot: &QPieModelMapperPrivate::onModelRowsAdded); |
245 | QObjectPrivate::disconnect(sender: d->m_model, |
246 | signal: &QAbstractItemModel::rowsRemoved, |
247 | receiverPrivate: d, |
248 | slot: &QPieModelMapperPrivate::onModelRowsRemoved); |
249 | QObjectPrivate::disconnect(sender: d->m_model, |
250 | signal: &QAbstractItemModel::columnsInserted, |
251 | receiverPrivate: d, |
252 | slot: &QPieModelMapperPrivate::onModelColumnsAdded); |
253 | QObjectPrivate::disconnect(sender: d->m_model, |
254 | signal: &QAbstractItemModel::columnsRemoved, |
255 | receiverPrivate: d, |
256 | slot: &QPieModelMapperPrivate::onModelColumnsRemoved); |
257 | QObjectPrivate::disconnect(sender: d->m_model, |
258 | signal: &QAbstractItemModel::destroyed, |
259 | receiverPrivate: d, |
260 | slot: &QPieModelMapperPrivate::handleModelDestroyed); |
261 | } |
262 | d->m_model = model; |
263 | d->initializePieFromModel(); |
264 | |
265 | QObjectPrivate::connect(sender: d->m_model, |
266 | signal: &QAbstractItemModel::modelReset, |
267 | receiverPrivate: d, |
268 | slot: &QPieModelMapperPrivate::initializePieFromModel); |
269 | QObjectPrivate::connect(sender: d->m_model, |
270 | signal: &QAbstractItemModel::dataChanged, |
271 | receiverPrivate: d, |
272 | slot: &QPieModelMapperPrivate::onModelUpdated); |
273 | QObjectPrivate::connect(sender: d->m_model, |
274 | signal: &QAbstractItemModel::rowsInserted, |
275 | receiverPrivate: d, |
276 | slot: &QPieModelMapperPrivate::onModelRowsAdded); |
277 | QObjectPrivate::connect(sender: d->m_model, |
278 | signal: &QAbstractItemModel::rowsRemoved, |
279 | receiverPrivate: d, |
280 | slot: &QPieModelMapperPrivate::onModelRowsRemoved); |
281 | QObjectPrivate::connect(sender: d->m_model, |
282 | signal: &QAbstractItemModel::columnsInserted, |
283 | receiverPrivate: d, |
284 | slot: &QPieModelMapperPrivate::onModelColumnsAdded); |
285 | QObjectPrivate::connect(sender: d->m_model, |
286 | signal: &QAbstractItemModel::columnsRemoved, |
287 | receiverPrivate: d, |
288 | slot: &QPieModelMapperPrivate::onModelColumnsRemoved); |
289 | QObjectPrivate::connect(sender: d->m_model, |
290 | signal: &QAbstractItemModel::destroyed, |
291 | receiverPrivate: d, |
292 | slot: &QPieModelMapperPrivate::handleModelDestroyed); |
293 | Q_EMIT modelChanged(); |
294 | } |
295 | |
296 | QPieSeries *QPieModelMapper::series() const { |
297 | Q_D(const QPieModelMapper); |
298 | return d->m_series; |
299 | } |
300 | |
301 | void QPieModelMapper::setSeries(QPieSeries *series) { |
302 | Q_D(QPieModelMapper); |
303 | if (d->m_series) { |
304 | QObjectPrivate::disconnect(sender: d->m_series, |
305 | signal: &QPieSeries::added, |
306 | receiverPrivate: d, |
307 | slot: &QPieModelMapperPrivate::onSlicesAdded); |
308 | QObjectPrivate::disconnect(sender: d->m_series, |
309 | signal: &QPieSeries::removed, |
310 | receiverPrivate: d, |
311 | slot: &QPieModelMapperPrivate::onSlicesRemoved); |
312 | QObjectPrivate::disconnect(sender: d->m_series, |
313 | signal: &QPieSeries::destroyed, |
314 | receiverPrivate: d, |
315 | slot: &QPieModelMapperPrivate::handleSeriesDestroyed); |
316 | } |
317 | |
318 | if (series == 0) |
319 | return; |
320 | |
321 | d->m_series = series; |
322 | d->initializePieFromModel(); |
323 | // connect the signals from the series |
324 | QObjectPrivate::connect(sender: d->m_series, |
325 | signal: &QPieSeries::added, |
326 | receiverPrivate: d, |
327 | slot: &QPieModelMapperPrivate::onSlicesAdded); |
328 | QObjectPrivate::connect(sender: d->m_series, |
329 | signal: &QPieSeries::removed, |
330 | receiverPrivate: d, |
331 | slot: &QPieModelMapperPrivate::onSlicesRemoved); |
332 | QObjectPrivate::connect(sender: d->m_series, |
333 | signal: &QPieSeries::destroyed, |
334 | receiverPrivate: d, |
335 | slot: &QPieModelMapperPrivate::handleSeriesDestroyed); |
336 | Q_EMIT seriesChanged(); |
337 | } |
338 | |
339 | qsizetype QPieModelMapper::first() const |
340 | { |
341 | Q_D(const QPieModelMapper); |
342 | return d->m_first; |
343 | } |
344 | |
345 | void QPieModelMapper::setFirst(qsizetype first) |
346 | { |
347 | Q_D(QPieModelMapper); |
348 | d->m_first = qMax(a: first, b: 0); |
349 | d->initializePieFromModel(); |
350 | Q_EMIT firstChanged(); |
351 | } |
352 | |
353 | qsizetype QPieModelMapper::count() const |
354 | { |
355 | Q_D(const QPieModelMapper); |
356 | return d->m_count; |
357 | } |
358 | |
359 | void QPieModelMapper::setCount(qsizetype count) |
360 | { |
361 | Q_D(QPieModelMapper); |
362 | d->m_count = qMax(a: count, b: -1); |
363 | d->initializePieFromModel(); |
364 | Q_EMIT countChanged(); |
365 | } |
366 | |
367 | Qt::Orientation QPieModelMapper::orientation() const { |
368 | Q_D(const QPieModelMapper); |
369 | return d->m_orientation; |
370 | } |
371 | |
372 | void QPieModelMapper::setOrientation(Qt::Orientation orientation) { |
373 | Q_D(QPieModelMapper); |
374 | d->m_orientation = orientation; |
375 | d->initializePieFromModel(); |
376 | Q_EMIT orientationChanged(); |
377 | } |
378 | |
379 | qsizetype QPieModelMapper::valuesSection() const |
380 | { |
381 | Q_D(const QPieModelMapper); |
382 | return d->m_valuesSection; |
383 | } |
384 | |
385 | void QPieModelMapper::setValuesSection(qsizetype valuesSection) |
386 | { |
387 | Q_D(QPieModelMapper); |
388 | d->m_valuesSection = qMax(a: -1, b: valuesSection); |
389 | d->initializePieFromModel(); |
390 | Q_EMIT valuesSectionChanged(); |
391 | } |
392 | |
393 | qsizetype QPieModelMapper::labelsSection() const |
394 | { |
395 | Q_D(const QPieModelMapper); |
396 | return d->m_labelsSection; |
397 | } |
398 | |
399 | void QPieModelMapper::setLabelsSection(qsizetype labelsSection) |
400 | { |
401 | Q_D(QPieModelMapper); |
402 | d->m_labelsSection = qMax(a: -1, b: labelsSection); |
403 | d->initializePieFromModel(); |
404 | Q_EMIT labelsSectionChanged(); |
405 | } |
406 | |
407 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
408 | |
409 | QPieModelMapperPrivate::QPieModelMapperPrivate() {} |
410 | |
411 | QPieModelMapperPrivate::~QPieModelMapperPrivate() {} |
412 | |
413 | void QPieModelMapperPrivate::blockModelSignals(bool block) { |
414 | m_modelSignalsBlock = block; |
415 | } |
416 | |
417 | void QPieModelMapperPrivate::blockSeriesSignals(bool block) { |
418 | m_seriesSignalsBlock = block; |
419 | } |
420 | |
421 | QPieSlice *QPieModelMapperPrivate::pieSlice(QModelIndex index) const { |
422 | if (!index.isValid()) |
423 | return 0; // index is invalid |
424 | |
425 | if (m_orientation == Qt::Vertical |
426 | && (index.column() == m_valuesSection || index.column() == m_labelsSection)) { |
427 | if (index.row() >= m_first && (m_count == -1 || index.row() < m_first + m_count)) { |
428 | if (m_model->index(row: index.row(), column: m_valuesSection).isValid() |
429 | && m_model->index(row: index.row(), column: m_labelsSection).isValid()) |
430 | return m_series->slices().at(i: index.row() - m_first); |
431 | else |
432 | return 0; |
433 | } |
434 | } else if (m_orientation == Qt::Horizontal |
435 | && (index.row() == m_valuesSection || index.row() == m_labelsSection)) { |
436 | if (index.column() >= m_first && (m_count == -1 || index.column() < m_first + m_count)) { |
437 | if (m_model->index(row: m_valuesSection, column: index.column()).isValid() |
438 | && m_model->index(row: m_labelsSection, column: index.column()).isValid()) |
439 | return m_series->slices().at(i: index.column() - m_first); |
440 | else |
441 | return 0; |
442 | } |
443 | } |
444 | return 0; // This part of model has not been mapped to any slice |
445 | } |
446 | |
447 | QModelIndex QPieModelMapperPrivate::valueModelIndex(qsizetype sliceIndex) { |
448 | if (m_count != -1 && sliceIndex >= m_count) |
449 | return QModelIndex(); // invalid |
450 | |
451 | if (m_orientation == Qt::Vertical) |
452 | return m_model->index(row: int(sliceIndex) + m_first, column: m_valuesSection); |
453 | else |
454 | return m_model->index(row: m_valuesSection, column: int(sliceIndex) + m_first); |
455 | } |
456 | |
457 | QModelIndex QPieModelMapperPrivate::labelModelIndex(qsizetype sliceIndex) { |
458 | if (m_count != -1 && sliceIndex >= m_count) |
459 | return QModelIndex(); // invalid |
460 | |
461 | if (m_orientation == Qt::Vertical) |
462 | return m_model->index(row: int(sliceIndex) + m_first, column: m_labelsSection); |
463 | else |
464 | return m_model->index(row: m_labelsSection, column: int(sliceIndex) + m_first); |
465 | } |
466 | |
467 | bool QPieModelMapperPrivate::isLabelIndex(QModelIndex index) const { |
468 | if (m_orientation == Qt::Vertical && index.column() == m_labelsSection) |
469 | return true; |
470 | else if (m_orientation == Qt::Horizontal && index.row() == m_labelsSection) |
471 | return true; |
472 | |
473 | return false; |
474 | } |
475 | |
476 | bool QPieModelMapperPrivate::isValueIndex(QModelIndex index) const { |
477 | if (m_orientation == Qt::Vertical && index.column() == m_valuesSection) |
478 | return true; |
479 | else if (m_orientation == Qt::Horizontal && index.row() == m_valuesSection) |
480 | return true; |
481 | |
482 | return false; |
483 | } |
484 | |
485 | void QPieModelMapperPrivate::onSlicesAdded(const QList<QPieSlice *> &slices) { |
486 | if (m_seriesSignalsBlock) |
487 | return; |
488 | |
489 | if (slices.size() == 0) |
490 | return; |
491 | |
492 | int firstIndex = int(m_series->slices().indexOf(t: slices.at(i: 0))); |
493 | if (firstIndex == -1) |
494 | return; |
495 | |
496 | if (m_count != -1) |
497 | m_count += slices.size(); |
498 | Q_Q(QPieModelMapper); |
499 | for (int i = firstIndex; i < firstIndex + slices.size(); i++) { |
500 | m_slices.insert(i, t: slices.at(i: i - firstIndex)); |
501 | QObject::connect(sender: slices.at(i: i - firstIndex), |
502 | signal: &QPieSlice::labelChanged, |
503 | context: q, |
504 | slot: &QPieModelMapper::onSliceLabelChanged); |
505 | QObject::connect(sender: slices.at(i: i - firstIndex), |
506 | signal: &QPieSlice::valueChanged, |
507 | context: q, |
508 | slot: &QPieModelMapper::onSliceValueChanged); |
509 | } |
510 | |
511 | blockModelSignals(); |
512 | if (m_orientation == Qt::Vertical) |
513 | m_model->insertRows(row: firstIndex + m_first, count: int(slices.size())); |
514 | else |
515 | m_model->insertColumns(column: firstIndex + m_first, count: int(slices.size())); |
516 | |
517 | for (int i = firstIndex; i < firstIndex + slices.size(); i++) { |
518 | m_model->setData(index: valueModelIndex(sliceIndex: i), value: slices.at(i: i - firstIndex)->value()); |
519 | m_model->setData(index: labelModelIndex(sliceIndex: i), value: slices.at(i: i - firstIndex)->label()); |
520 | } |
521 | blockModelSignals(block: false); |
522 | } |
523 | |
524 | void QPieModelMapperPrivate::onSlicesRemoved(const QList<QPieSlice *> &slices) { |
525 | if (m_seriesSignalsBlock) |
526 | return; |
527 | |
528 | if (slices.size() == 0) |
529 | return; |
530 | |
531 | int firstIndex = int(m_slices.indexOf(t: slices.at(i: 0))); |
532 | if (firstIndex == -1) |
533 | return; |
534 | |
535 | if (m_count != -1) |
536 | m_count -= slices.size(); |
537 | |
538 | for (int i = firstIndex + int(slices.size()) - 1; i >= firstIndex; i--) |
539 | m_slices.removeAt(i); |
540 | |
541 | blockModelSignals(); |
542 | if (m_orientation == Qt::Vertical) |
543 | m_model->removeRows(row: firstIndex + m_first, count: int(slices.size())); |
544 | else |
545 | m_model->removeColumns(column: firstIndex + m_first, count: int(slices.size())); |
546 | blockModelSignals(block: false); |
547 | } |
548 | |
549 | void QPieModelMapperPrivate::onSliceLabelChanged(const QPieSlice *slice) { |
550 | if (m_seriesSignalsBlock) |
551 | return; |
552 | |
553 | blockModelSignals(); |
554 | m_model->setData(index: labelModelIndex(sliceIndex: m_series->slices().indexOf(t: slice)), value: slice->label()); |
555 | blockModelSignals(block: false); |
556 | } |
557 | |
558 | void QPieModelMapperPrivate::onSliceValueChanged(const QPieSlice *slice) { |
559 | if (m_seriesSignalsBlock) |
560 | return; |
561 | |
562 | blockModelSignals(); |
563 | m_model->setData(index: valueModelIndex(sliceIndex: m_series->slices().indexOf(t: slice)), value: slice->value()); |
564 | blockModelSignals(block: false); |
565 | } |
566 | |
567 | void QPieModelMapperPrivate::handleSeriesDestroyed() { m_series = nullptr; } |
568 | |
569 | void QPieModelMapperPrivate::onModelUpdated(QModelIndex topLeft, QModelIndex bottomRight) |
570 | { |
571 | if (m_model == nullptr || m_series == nullptr) |
572 | return; |
573 | |
574 | if (m_modelSignalsBlock) |
575 | return; |
576 | |
577 | blockSeriesSignals(); |
578 | QModelIndex index; |
579 | QPieSlice *slice; |
580 | for (int row = topLeft.row(); row <= bottomRight.row(); row++) { |
581 | for (int column = topLeft.column(); column <= bottomRight.column(); column++) { |
582 | index = topLeft.sibling(arow: row, acolumn: column); |
583 | slice = pieSlice(index); |
584 | if (slice) { |
585 | if (isValueIndex(index)) |
586 | slice->setValue(m_model->data(index, role: Qt::DisplayRole).toReal()); |
587 | if (isLabelIndex(index)) |
588 | slice->setLabel(m_model->data(index, role: Qt::DisplayRole).toString()); |
589 | } |
590 | } |
591 | } |
592 | blockSeriesSignals(block: false); |
593 | } |
594 | |
595 | void QPieModelMapperPrivate::onModelRowsAdded(QModelIndex parent, qsizetype start, qsizetype end) |
596 | { |
597 | Q_UNUSED(parent); |
598 | if (m_modelSignalsBlock) |
599 | return; |
600 | |
601 | blockSeriesSignals(); |
602 | if (m_orientation == Qt::Vertical) { |
603 | insertData(start, end); |
604 | } else if (start <= m_valuesSection |
605 | || start <= m_labelsSection) { // if the changes affect the map - reinitialize the pie |
606 | initializePieFromModel(); |
607 | } |
608 | blockSeriesSignals(block: false); |
609 | } |
610 | |
611 | void QPieModelMapperPrivate::onModelRowsRemoved(QModelIndex parent, qsizetype start, qsizetype end) |
612 | { |
613 | Q_UNUSED(parent); |
614 | if (m_modelSignalsBlock) |
615 | return; |
616 | |
617 | blockSeriesSignals(); |
618 | if (m_orientation == Qt::Vertical) { |
619 | removeData(start, end); |
620 | } else if (start <= m_valuesSection || start <= m_labelsSection) { |
621 | // if the changes affect the map - reinitialize the pie |
622 | initializePieFromModel(); |
623 | } |
624 | blockSeriesSignals(block: false); |
625 | } |
626 | |
627 | void QPieModelMapperPrivate::onModelColumnsAdded(QModelIndex parent, qsizetype start, qsizetype end) |
628 | { |
629 | Q_UNUSED(parent); |
630 | if (m_modelSignalsBlock) |
631 | return; |
632 | |
633 | blockSeriesSignals(); |
634 | if (m_orientation == Qt::Horizontal) { |
635 | insertData(start, end); |
636 | } else if (start <= m_valuesSection || start <= m_labelsSection) { |
637 | // if the changes affect the map - reinitialize the pie |
638 | initializePieFromModel(); |
639 | } |
640 | blockSeriesSignals(block: false); |
641 | } |
642 | |
643 | void QPieModelMapperPrivate::onModelColumnsRemoved(QModelIndex parent, qsizetype start, qsizetype end) |
644 | { |
645 | Q_UNUSED(parent); |
646 | if (m_modelSignalsBlock) |
647 | return; |
648 | |
649 | blockSeriesSignals(); |
650 | if (m_orientation == Qt::Horizontal) { |
651 | removeData(start, end); |
652 | } else if (start <= m_valuesSection || start <= m_labelsSection) { |
653 | // if the changes affect the map - reinitialize the pie |
654 | initializePieFromModel(); |
655 | } |
656 | blockSeriesSignals(block: false); |
657 | } |
658 | |
659 | void QPieModelMapperPrivate::handleModelDestroyed() { m_model = nullptr; } |
660 | |
661 | void QPieModelMapperPrivate::insertData(qsizetype start, qsizetype end) { |
662 | if (m_model == nullptr || m_series == nullptr) |
663 | return; |
664 | |
665 | if (m_count != -1 && start >= m_first + m_count) { |
666 | return; |
667 | } else { |
668 | qsizetype addedCount = end - start + 1; |
669 | if (m_count != -1 && addedCount > m_count) |
670 | addedCount = m_count; |
671 | qsizetype first = qMax(a: start, b: m_first); |
672 | qsizetype last = qMin(a: first + addedCount - 1, |
673 | b: m_orientation == Qt::Vertical ? m_model->rowCount() - 1 |
674 | : m_model->columnCount() - 1); |
675 | Q_Q(QPieModelMapper); |
676 | |
677 | for (qsizetype i = first; i <= last; i++) { |
678 | QModelIndex valueIndex = valueModelIndex(sliceIndex: i - m_first); |
679 | QModelIndex labelIndex = labelModelIndex(sliceIndex: i - m_first); |
680 | if (valueIndex.isValid() && labelIndex.isValid()) { |
681 | QPieSlice *slice = new QPieSlice; |
682 | slice->setValue(m_model->data(index: valueIndex, role: Qt::DisplayRole).toDouble()); |
683 | slice->setLabel(m_model->data(index: labelIndex, role: Qt::DisplayRole).toString()); |
684 | QObject::connect(sender: slice, |
685 | signal: &QPieSlice::labelChanged, |
686 | context: q, |
687 | slot: &QPieModelMapper::onSliceLabelChanged); |
688 | QObject::connect(sender: slice, |
689 | signal: &QPieSlice::valueChanged, |
690 | context: q, |
691 | slot: &QPieModelMapper::onSliceValueChanged); |
692 | m_series->insert(index: i - m_first, slice); |
693 | m_slices.insert(i: i - m_first, t: slice); |
694 | } |
695 | } |
696 | |
697 | // remove excess of slices (abouve m_count) |
698 | if (m_count != -1 && m_series->slices().size() > m_count) { |
699 | for (qsizetype i = m_series->slices().size() - 1; i >= m_count; i--) { |
700 | m_series->remove(slice: m_series->slices().at(i)); |
701 | m_slices.removeAt(i); |
702 | } |
703 | } |
704 | } |
705 | } |
706 | |
707 | void QPieModelMapperPrivate::removeData(qsizetype start, qsizetype end) { |
708 | if (m_model == 0 || m_series == 0) |
709 | return; |
710 | |
711 | qsizetype removedCount = end - start + 1; |
712 | if (m_count != -1 && start >= m_first + m_count) { |
713 | return; |
714 | } else { |
715 | qsizetype toRemove = qMin(a: m_series->slices().size(), |
716 | b: removedCount); // first find how many items can actually be removed |
717 | qsizetype first = qMax(a: start, |
718 | b: m_first); // get the index of the first item that will be removed. |
719 | qsizetype last = qMin(a: first + toRemove - 1, |
720 | b: m_series->slices().size() + m_first |
721 | - 1); // get the index of the last item that will be removed. |
722 | for (qsizetype i = last; i >= first; i--) { |
723 | m_series->remove(slice: m_series->slices().at(i: i - m_first)); |
724 | m_slices.removeAt(i: i - m_first); |
725 | } |
726 | |
727 | if (m_count != -1) { |
728 | qsizetype itemsAvailable; // check how many are available to be added |
729 | if (m_orientation == Qt::Vertical) |
730 | itemsAvailable = m_model->rowCount() - m_first - m_series->slices().size(); |
731 | else |
732 | itemsAvailable = m_model->columnCount() - m_first - m_series->slices().size(); |
733 | qsizetype toBeAdded = qMin(a: itemsAvailable, |
734 | b: m_count |
735 | - m_series->slices().size()); // add not more items than there |
736 | // is space left to be filled. |
737 | qsizetype currentSize = m_series->slices().size(); |
738 | if (toBeAdded > 0) { |
739 | for (qsizetype i = m_series->slices().size(); i < currentSize + toBeAdded; i++) { |
740 | QModelIndex valueIndex = valueModelIndex(sliceIndex: i - m_first); |
741 | QModelIndex labelIndex = labelModelIndex(sliceIndex: i - m_first); |
742 | if (valueIndex.isValid() && labelIndex.isValid()) { |
743 | QPieSlice *slice = new QPieSlice; |
744 | slice->setValue(m_model->data(index: valueIndex, role: Qt::DisplayRole).toDouble()); |
745 | slice->setLabel(m_model->data(index: labelIndex, role: Qt::DisplayRole).toString()); |
746 | m_series->insert(index: i, slice); |
747 | m_slices.insert(i, t: slice); |
748 | } |
749 | } |
750 | } |
751 | } |
752 | } |
753 | } |
754 | |
755 | void QPieModelMapperPrivate::initializePieFromModel() { |
756 | if (m_model == nullptr || m_series == nullptr) |
757 | return; |
758 | |
759 | blockSeriesSignals(); |
760 | // clear current content |
761 | m_series->clear(); |
762 | m_slices.clear(); |
763 | |
764 | // create the initial slices set |
765 | int slicePos = 0; |
766 | QModelIndex valueIndex = valueModelIndex(sliceIndex: slicePos); |
767 | QModelIndex labelIndex = labelModelIndex(sliceIndex: slicePos); |
768 | Q_Q(QPieModelMapper); |
769 | while (valueIndex.isValid() && labelIndex.isValid()) { |
770 | QPieSlice *slice = new QPieSlice; |
771 | slice->setLabel(m_model->data(index: labelIndex, role: Qt::DisplayRole).toString()); |
772 | slice->setValue(m_model->data(index: valueIndex, role: Qt::DisplayRole).toDouble()); |
773 | QObject::connect(sender: slice, signal: &QPieSlice::labelChanged, context: q, slot: &QPieModelMapper::onSliceLabelChanged); |
774 | QObject::connect(sender: slice, signal: &QPieSlice::valueChanged, context: q, slot: &QPieModelMapper::onSliceValueChanged); |
775 | m_series->append(slice); |
776 | m_slices.append(t: slice); |
777 | slicePos++; |
778 | valueIndex = valueModelIndex(sliceIndex: slicePos); |
779 | labelIndex = labelModelIndex(sliceIndex: slicePos); |
780 | } |
781 | blockSeriesSignals(block: false); |
782 | } |
783 | |
784 | QT_END_NAMESPACE |
785 |
Definitions
- ~QPieModelMapper
- QPieModelMapper
- QPieModelMapper
- onSliceLabelChanged
- onSliceValueChanged
- model
- setModel
- series
- setSeries
- first
- setFirst
- count
- setCount
- orientation
- setOrientation
- valuesSection
- setValuesSection
- labelsSection
- setLabelsSection
- QPieModelMapperPrivate
- ~QPieModelMapperPrivate
- blockModelSignals
- blockSeriesSignals
- pieSlice
- valueModelIndex
- labelModelIndex
- isLabelIndex
- isValueIndex
- onSlicesAdded
- onSlicesRemoved
- onSliceLabelChanged
- onSliceValueChanged
- handleSeriesDestroyed
- onModelUpdated
- onModelRowsAdded
- onModelRowsRemoved
- onModelColumnsAdded
- onModelColumnsRemoved
- handleModelDestroyed
- insertData
- removeData
Start learning QML with our Intro Training
Find out more