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