1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "pieview.h"
52
53#include <QtWidgets>
54
55PieView::PieView(QWidget *parent)
56 : QAbstractItemView(parent)
57{
58 horizontalScrollBar()->setRange(min: 0, max: 0);
59 verticalScrollBar()->setRange(min: 0, max: 0);
60}
61
62void PieView::dataChanged(const QModelIndex &topLeft,
63 const QModelIndex &bottomRight,
64 const QVector<int> &roles)
65{
66 QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
67
68 if (!roles.contains(t: Qt::DisplayRole))
69 return;
70
71 validItems = 0;
72 totalValue = 0.0;
73
74 for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) {
75
76 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
77 double value = model()->data(index, role: Qt::DisplayRole).toDouble();
78
79 if (value > 0.0) {
80 totalValue += value;
81 validItems++;
82 }
83 }
84 viewport()->update();
85}
86
87bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
88{
89 if (index.column() == 0)
90 return QAbstractItemView::edit(index, trigger, event);
91 else
92 return false;
93}
94
95/*
96 Returns the item that covers the coordinate given in the view.
97*/
98
99QModelIndex PieView::indexAt(const QPoint &point) const
100{
101 if (validItems == 0)
102 return QModelIndex();
103
104 // Transform the view coordinates into contents widget coordinates.
105 int wx = point.x() + horizontalScrollBar()->value();
106 int wy = point.y() + verticalScrollBar()->value();
107
108 if (wx < totalSize) {
109 double cx = wx - totalSize / 2;
110 double cy = totalSize / 2 - wy; // positive cy for items above the center
111
112 // Determine the distance from the center point of the pie chart.
113 double d = std::sqrt(x: std::pow(x: cx, y: 2) + std::pow(x: cy, y: 2));
114
115 if (d == 0 || d > pieSize / 2)
116 return QModelIndex();
117
118 // Determine the angle of the point.
119 double angle = qRadiansToDegrees(radians: std::atan2(y: cy, x: cx));
120 if (angle < 0)
121 angle = 360 + angle;
122
123 // Find the relevant slice of the pie.
124 double startAngle = 0.0;
125
126 for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) {
127
128 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
129 double value = model()->data(index).toDouble();
130
131 if (value > 0.0) {
132 double sliceAngle = 360 * value / totalValue;
133
134 if (angle >= startAngle && angle < (startAngle + sliceAngle))
135 return model()->index(row, column: 1, parent: rootIndex());
136
137 startAngle += sliceAngle;
138 }
139 }
140 } else {
141 double itemHeight = QFontMetrics(viewOptions().font).height();
142 int listItem = int((wy - margin) / itemHeight);
143 int validRow = 0;
144
145 for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) {
146
147 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
148 if (model()->data(index).toDouble() > 0.0) {
149
150 if (listItem == validRow)
151 return model()->index(row, column: 0, parent: rootIndex());
152
153 // Update the list index that corresponds to the next valid row.
154 ++validRow;
155 }
156 }
157 }
158
159 return QModelIndex();
160}
161
162bool PieView::isIndexHidden(const QModelIndex & /*index*/) const
163{
164 return false;
165}
166
167/*
168 Returns the rectangle of the item at position \a index in the
169 model. The rectangle is in contents coordinates.
170*/
171
172QRect PieView::itemRect(const QModelIndex &index) const
173{
174 if (!index.isValid())
175 return QRect();
176
177 // Check whether the index's row is in the list of rows represented
178 // by slices.
179 QModelIndex valueIndex;
180
181 if (index.column() != 1)
182 valueIndex = model()->index(row: index.row(), column: 1, parent: rootIndex());
183 else
184 valueIndex = index;
185
186 if (model()->data(index: valueIndex).toDouble() <= 0.0)
187 return QRect();
188
189 int listItem = 0;
190 for (int row = index.row()-1; row >= 0; --row) {
191 if (model()->data(index: model()->index(row, column: 1, parent: rootIndex())).toDouble() > 0.0)
192 listItem++;
193 }
194
195 switch (index.column()) {
196 case 0: {
197 const qreal itemHeight = QFontMetricsF(viewOptions().font).height();
198
199 return QRect(totalSize,
200 qRound(d: margin + listItem * itemHeight),
201 totalSize - margin, qRound(d: itemHeight));
202 }
203 case 1:
204 return viewport()->rect();
205 }
206 return QRect();
207}
208
209QRegion PieView::itemRegion(const QModelIndex &index) const
210{
211 if (!index.isValid())
212 return QRegion();
213
214 if (index.column() != 1)
215 return itemRect(index);
216
217 if (model()->data(index).toDouble() <= 0.0)
218 return QRegion();
219
220 double startAngle = 0.0;
221 for (int row = 0; row < model()->rowCount(parent: rootIndex()); ++row) {
222
223 QModelIndex sliceIndex = model()->index(row, column: 1, parent: rootIndex());
224 double value = model()->data(index: sliceIndex).toDouble();
225
226 if (value > 0.0) {
227 double angle = 360 * value / totalValue;
228
229 if (sliceIndex == index) {
230 QPainterPath slicePath;
231 slicePath.moveTo(x: totalSize / 2, y: totalSize / 2);
232 slicePath.arcTo(x: margin, y: margin, w: margin + pieSize, h: margin + pieSize,
233 startAngle, arcLength: angle);
234 slicePath.closeSubpath();
235
236 return QRegion(slicePath.toFillPolygon().toPolygon());
237 }
238
239 startAngle += angle;
240 }
241 }
242
243 return QRegion();
244}
245
246int PieView::horizontalOffset() const
247{
248 return horizontalScrollBar()->value();
249}
250
251void PieView::mousePressEvent(QMouseEvent *event)
252{
253 QAbstractItemView::mousePressEvent(event);
254 origin = event->pos();
255 if (!rubberBand)
256 rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
257 rubberBand->setGeometry(QRect(origin, QSize()));
258 rubberBand->show();
259}
260
261void PieView::mouseMoveEvent(QMouseEvent *event)
262{
263 if (rubberBand)
264 rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
265 QAbstractItemView::mouseMoveEvent(event);
266}
267
268void PieView::mouseReleaseEvent(QMouseEvent *event)
269{
270 QAbstractItemView::mouseReleaseEvent(event);
271 if (rubberBand)
272 rubberBand->hide();
273 viewport()->update();
274}
275
276QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction,
277 Qt::KeyboardModifiers /*modifiers*/)
278{
279 QModelIndex current = currentIndex();
280
281 switch (cursorAction) {
282 case MoveLeft:
283 case MoveUp:
284 if (current.row() > 0)
285 current = model()->index(row: current.row() - 1, column: current.column(),
286 parent: rootIndex());
287 else
288 current = model()->index(row: 0, column: current.column(), parent: rootIndex());
289 break;
290 case MoveRight:
291 case MoveDown:
292 if (current.row() < rows(index: current) - 1)
293 current = model()->index(row: current.row() + 1, column: current.column(),
294 parent: rootIndex());
295 else
296 current = model()->index(row: rows(index: current) - 1, column: current.column(),
297 parent: rootIndex());
298 break;
299 default:
300 break;
301 }
302
303 viewport()->update();
304 return current;
305}
306
307void PieView::paintEvent(QPaintEvent *event)
308{
309 QItemSelectionModel *selections = selectionModel();
310 QStyleOptionViewItem option = viewOptions();
311
312 QBrush background = option.palette.base();
313 QPen foreground(option.palette.color(cr: QPalette::WindowText));
314
315 QPainter painter(viewport());
316 painter.setRenderHint(hint: QPainter::Antialiasing);
317
318 painter.fillRect(event->rect(), background);
319 painter.setPen(foreground);
320
321 // Viewport rectangles
322 QRect pieRect = QRect(margin, margin, pieSize, pieSize);
323
324 if (validItems <= 0)
325 return;
326
327 painter.save();
328 painter.translate(dx: pieRect.x() - horizontalScrollBar()->value(),
329 dy: pieRect.y() - verticalScrollBar()->value());
330 painter.drawEllipse(x: 0, y: 0, w: pieSize, h: pieSize);
331 double startAngle = 0.0;
332 int row;
333
334 for (row = 0; row < model()->rowCount(parent: rootIndex()); ++row) {
335 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
336 double value = model()->data(index).toDouble();
337
338 if (value > 0.0) {
339 double angle = 360 * value / totalValue;
340
341 QModelIndex colorIndex = model()->index(row, column: 0, parent: rootIndex());
342 QColor color = QColor(model()->data(index: colorIndex, role: Qt::DecorationRole).toString());
343
344 if (currentIndex() == index)
345 painter.setBrush(QBrush(color, Qt::Dense4Pattern));
346 else if (selections->isSelected(index))
347 painter.setBrush(QBrush(color, Qt::Dense3Pattern));
348 else
349 painter.setBrush(QBrush(color));
350
351 painter.drawPie(x: 0, y: 0, w: pieSize, h: pieSize, a: int(startAngle*16), alen: int(angle*16));
352
353 startAngle += angle;
354 }
355 }
356 painter.restore();
357
358 int keyNumber = 0;
359
360 for (row = 0; row < model()->rowCount(parent: rootIndex()); ++row) {
361 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
362 double value = model()->data(index).toDouble();
363
364 if (value > 0.0) {
365 QModelIndex labelIndex = model()->index(row, column: 0, parent: rootIndex());
366
367 QStyleOptionViewItem option = viewOptions();
368 option.rect = visualRect(index: labelIndex);
369 if (selections->isSelected(index: labelIndex))
370 option.state |= QStyle::State_Selected;
371 if (currentIndex() == labelIndex)
372 option.state |= QStyle::State_HasFocus;
373 itemDelegate()->paint(painter: &painter, option, index: labelIndex);
374
375 ++keyNumber;
376 }
377 }
378}
379
380void PieView::resizeEvent(QResizeEvent * /* event */)
381{
382 updateGeometries();
383}
384
385int PieView::rows(const QModelIndex &index) const
386{
387 return model()->rowCount(parent: model()->parent(child: index));
388}
389
390void PieView::rowsInserted(const QModelIndex &parent, int start, int end)
391{
392 for (int row = start; row <= end; ++row) {
393 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
394 double value = model()->data(index).toDouble();
395
396 if (value > 0.0) {
397 totalValue += value;
398 ++validItems;
399 }
400 }
401
402 QAbstractItemView::rowsInserted(parent, start, end);
403}
404
405void PieView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
406{
407 for (int row = start; row <= end; ++row) {
408 QModelIndex index = model()->index(row, column: 1, parent: rootIndex());
409 double value = model()->data(index).toDouble();
410 if (value > 0.0) {
411 totalValue -= value;
412 --validItems;
413 }
414 }
415
416 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
417}
418
419void PieView::scrollContentsBy(int dx, int dy)
420{
421 viewport()->scroll(dx, dy);
422}
423
424void PieView::scrollTo(const QModelIndex &index, ScrollHint)
425{
426 QRect area = viewport()->rect();
427 QRect rect = visualRect(index);
428
429 if (rect.left() < area.left()) {
430 horizontalScrollBar()->setValue(
431 horizontalScrollBar()->value() + rect.left() - area.left());
432 } else if (rect.right() > area.right()) {
433 horizontalScrollBar()->setValue(
434 horizontalScrollBar()->value() + qMin(
435 a: rect.right() - area.right(), b: rect.left() - area.left()));
436 }
437
438 if (rect.top() < area.top()) {
439 verticalScrollBar()->setValue(
440 verticalScrollBar()->value() + rect.top() - area.top());
441 } else if (rect.bottom() > area.bottom()) {
442 verticalScrollBar()->setValue(
443 verticalScrollBar()->value() + qMin(
444 a: rect.bottom() - area.bottom(), b: rect.top() - area.top()));
445 }
446
447 update();
448}
449
450/*
451 Find the indices corresponding to the extent of the selection.
452*/
453
454void PieView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
455{
456 // Use content widget coordinates because we will use the itemRegion()
457 // function to check for intersections.
458
459 QRect contentsRect = rect.translated(
460 dx: horizontalScrollBar()->value(),
461 dy: verticalScrollBar()->value()).normalized();
462
463 int rows = model()->rowCount(parent: rootIndex());
464 int columns = model()->columnCount(parent: rootIndex());
465 QModelIndexList indexes;
466
467 for (int row = 0; row < rows; ++row) {
468 for (int column = 0; column < columns; ++column) {
469 QModelIndex index = model()->index(row, column, parent: rootIndex());
470 QRegion region = itemRegion(index);
471 if (region.intersects(r: contentsRect))
472 indexes.append(t: index);
473 }
474 }
475
476 if (indexes.size() > 0) {
477 int firstRow = indexes.at(i: 0).row();
478 int lastRow = firstRow;
479 int firstColumn = indexes.at(i: 0).column();
480 int lastColumn = firstColumn;
481
482 for (int i = 1; i < indexes.size(); ++i) {
483 firstRow = qMin(a: firstRow, b: indexes.at(i).row());
484 lastRow = qMax(a: lastRow, b: indexes.at(i).row());
485 firstColumn = qMin(a: firstColumn, b: indexes.at(i).column());
486 lastColumn = qMax(a: lastColumn, b: indexes.at(i).column());
487 }
488
489 QItemSelection selection(
490 model()->index(row: firstRow, column: firstColumn, parent: rootIndex()),
491 model()->index(row: lastRow, column: lastColumn, parent: rootIndex()));
492 selectionModel()->select(selection, command);
493 } else {
494 QModelIndex noIndex;
495 QItemSelection selection(noIndex, noIndex);
496 selectionModel()->select(selection, command);
497 }
498
499 update();
500}
501
502void PieView::updateGeometries()
503{
504 horizontalScrollBar()->setPageStep(viewport()->width());
505 horizontalScrollBar()->setRange(min: 0, max: qMax(a: 0, b: 2 * totalSize - viewport()->width()));
506 verticalScrollBar()->setPageStep(viewport()->height());
507 verticalScrollBar()->setRange(min: 0, max: qMax(a: 0, b: totalSize - viewport()->height()));
508}
509
510int PieView::verticalOffset() const
511{
512 return verticalScrollBar()->value();
513}
514
515/*
516 Returns the position of the item in viewport coordinates.
517*/
518
519QRect PieView::visualRect(const QModelIndex &index) const
520{
521 QRect rect = itemRect(index);
522 if (!rect.isValid())
523 return rect;
524
525 return QRect(rect.left() - horizontalScrollBar()->value(),
526 rect.top() - verticalScrollBar()->value(),
527 rect.width(), rect.height());
528}
529
530/*
531 Returns a region corresponding to the selection in viewport coordinates.
532*/
533
534QRegion PieView::visualRegionForSelection(const QItemSelection &selection) const
535{
536 int ranges = selection.count();
537
538 if (ranges == 0)
539 return QRect();
540
541 QRegion region;
542 for (int i = 0; i < ranges; ++i) {
543 const QItemSelectionRange &range = selection.at(i);
544 for (int row = range.top(); row <= range.bottom(); ++row) {
545 for (int col = range.left(); col <= range.right(); ++col) {
546 QModelIndex index = model()->index(row, column: col, parent: rootIndex());
547 region += visualRect(index);
548 }
549 }
550 }
551 return region;
552}
553

source code of qtbase/examples/widgets/itemviews/chart/pieview.cpp