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