source: trunk/examples/itemviews/chart/pieview.cpp@ 651

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 17.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the examples of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include <math.h>
43#include <QtGui>
44
45#ifndef M_PI
46#define M_PI 3.1415927
47#endif
48
49#include "pieview.h"
50
51PieView::PieView(QWidget *parent)
52 : QAbstractItemView(parent)
53{
54 horizontalScrollBar()->setRange(0, 0);
55 verticalScrollBar()->setRange(0, 0);
56
57 margin = 8;
58 totalSize = 300;
59 pieSize = totalSize - 2*margin;
60 validItems = 0;
61 totalValue = 0.0;
62 rubberBand = 0;
63}
64
65void PieView::dataChanged(const QModelIndex &topLeft,
66 const QModelIndex &bottomRight)
67{
68 QAbstractItemView::dataChanged(topLeft, bottomRight);
69
70 validItems = 0;
71 totalValue = 0.0;
72
73 for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
74
75 QModelIndex index = model()->index(row, 1, rootIndex());
76 double value = model()->data(index).toDouble();
77
78 if (value > 0.0) {
79 totalValue += value;
80 validItems++;
81 }
82 }
83 viewport()->update();
84}
85
86bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
87{
88 if (index.column() == 0)
89 return QAbstractItemView::edit(index, trigger, event);
90 else
91 return false;
92}
93
94/*
95 Returns the item that covers the coordinate given in the view.
96*/
97
98QModelIndex PieView::indexAt(const QPoint &point) const
99{
100 if (validItems == 0)
101 return QModelIndex();
102
103 // Transform the view coordinates into contents widget coordinates.
104 int wx = point.x() + horizontalScrollBar()->value();
105 int wy = point.y() + verticalScrollBar()->value();
106
107 if (wx < totalSize) {
108 double cx = wx - totalSize/2;
109 double cy = totalSize/2 - wy; // positive cy for items above the center
110
111 // Determine the distance from the center point of the pie chart.
112 double d = pow(pow(cx, 2) + pow(cy, 2), 0.5);
113
114 if (d == 0 || d > pieSize/2)
115 return QModelIndex();
116
117 // Determine the angle of the point.
118 double angle = (180 / M_PI) * acos(cx/d);
119 if (cy < 0)
120 angle = 360 - angle;
121
122 // Find the relevant slice of the pie.
123 double startAngle = 0.0;
124
125 for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
126
127 QModelIndex index = model()->index(row, 1, rootIndex());
128 double value = model()->data(index).toDouble();
129
130 if (value > 0.0) {
131 double sliceAngle = 360*value/totalValue;
132
133 if (angle >= startAngle && angle < (startAngle + sliceAngle))
134 return model()->index(row, 1, rootIndex());
135
136 startAngle += sliceAngle;
137 }
138 }
139 } else {
140 double itemHeight = QFontMetrics(viewOptions().font).height();
141 int listItem = int((wy - margin) / itemHeight);
142 int validRow = 0;
143
144 for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
145
146 QModelIndex index = model()->index(row, 1, rootIndex());
147 if (model()->data(index).toDouble() > 0.0) {
148
149 if (listItem == validRow)
150 return model()->index(row, 0, rootIndex());
151
152 // Update the list index that corresponds to the next valid row.
153 validRow++;
154 }
155 }
156 }
157
158 return QModelIndex();
159}
160
161bool PieView::isIndexHidden(const QModelIndex & /*index*/) const
162{
163 return false;
164}
165
166/*
167 Returns the rectangle of the item at position \a index in the
168 model. The rectangle is in contents coordinates.
169*/
170
171QRect PieView::itemRect(const QModelIndex &index) const
172{
173 if (!index.isValid())
174 return QRect();
175
176 // Check whether the index's row is in the list of rows represented
177 // by slices.
178 QModelIndex valueIndex;
179
180 if (index.column() != 1)
181 valueIndex = model()->index(index.row(), 1, rootIndex());
182 else
183 valueIndex = index;
184
185 if (model()->data(valueIndex).toDouble() > 0.0) {
186
187 int listItem = 0;
188 for (int row = index.row()-1; row >= 0; --row) {
189 if (model()->data(model()->index(row, 1, rootIndex())).toDouble() > 0.0)
190 listItem++;
191 }
192
193 double itemHeight;
194
195 switch (index.column()) {
196 case 0:
197 itemHeight = QFontMetrics(viewOptions().font).height();
198
199 return QRect(totalSize,
200 int(margin + listItem*itemHeight),
201 totalSize - margin, int(itemHeight));
202 case 1:
203 return viewport()->rect();
204 }
205
206 }
207 return QRect();
208}
209
210QRegion PieView::itemRegion(const QModelIndex &index) const
211{
212 if (!index.isValid())
213 return QRegion();
214
215 if (index.column() != 1)
216 return itemRect(index);
217
218 if (model()->data(index).toDouble() <= 0.0)
219 return QRegion();
220
221 double startAngle = 0.0;
222 for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
223
224 QModelIndex sliceIndex = model()->index(row, 1, rootIndex());
225 double value = model()->data(sliceIndex).toDouble();
226
227 if (value > 0.0) {
228 double angle = 360*value/totalValue;
229
230 if (sliceIndex == index) {
231 QPainterPath slicePath;
232 slicePath.moveTo(totalSize/2, totalSize/2);
233 slicePath.arcTo(margin, margin, margin+pieSize, margin+pieSize,
234 startAngle, angle);
235 slicePath.closeSubpath();
236
237 return QRegion(slicePath.toFillPolygon().toPolygon());
238 }
239
240 startAngle += angle;
241 }
242 }
243
244 return QRegion();
245}
246
247int PieView::horizontalOffset() const
248{
249 return horizontalScrollBar()->value();
250}
251
252void PieView::mousePressEvent(QMouseEvent *event)
253{
254 QAbstractItemView::mousePressEvent(event);
255 origin = event->pos();
256 if (!rubberBand)
257 rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
258 rubberBand->setGeometry(QRect(origin, QSize()));
259 rubberBand->show();
260}
261
262void PieView::mouseMoveEvent(QMouseEvent *event)
263{
264 if (rubberBand)
265 rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
266 QAbstractItemView::mouseMoveEvent(event);
267}
268
269void PieView::mouseReleaseEvent(QMouseEvent *event)
270{
271 QAbstractItemView::mouseReleaseEvent(event);
272 if (rubberBand)
273 rubberBand->hide();
274 viewport()->update();
275}
276
277QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction,
278 Qt::KeyboardModifiers /*modifiers*/)
279{
280 QModelIndex current = currentIndex();
281
282 switch (cursorAction) {
283 case MoveLeft:
284 case MoveUp:
285 if (current.row() > 0)
286 current = model()->index(current.row() - 1, current.column(),
287 rootIndex());
288 else
289 current = model()->index(0, current.column(), rootIndex());
290 break;
291 case MoveRight:
292 case MoveDown:
293 if (current.row() < rows(current) - 1)
294 current = model()->index(current.row() + 1, current.column(),
295 rootIndex());
296 else
297 current = model()->index(rows(current) - 1, current.column(),
298 rootIndex());
299 break;
300 default:
301 break;
302 }
303
304 viewport()->update();
305 return current;
306}
307
308void PieView::paintEvent(QPaintEvent *event)
309{
310 QItemSelectionModel *selections = selectionModel();
311 QStyleOptionViewItem option = viewOptions();
312 QStyle::State state = option.state;
313
314 QBrush background = option.palette.base();
315 QPen foreground(option.palette.color(QPalette::WindowText));
316 QPen textPen(option.palette.color(QPalette::Text));
317 QPen highlightedPen(option.palette.color(QPalette::HighlightedText));
318
319 QPainter painter(viewport());
320 painter.setRenderHint(QPainter::Antialiasing);
321
322 painter.fillRect(event->rect(), background);
323 painter.setPen(foreground);
324
325 // Viewport rectangles
326 QRect pieRect = QRect(margin, margin, pieSize, pieSize);
327 QPoint keyPoint = QPoint(totalSize - horizontalScrollBar()->value(),
328 margin - verticalScrollBar()->value());
329
330 if (validItems > 0) {
331
332 painter.save();
333 painter.translate(pieRect.x() - horizontalScrollBar()->value(),
334 pieRect.y() - verticalScrollBar()->value());
335 painter.drawEllipse(0, 0, pieSize, pieSize);
336 double startAngle = 0.0;
337 int row;
338
339 for (row = 0; row < model()->rowCount(rootIndex()); ++row) {
340
341 QModelIndex index = model()->index(row, 1, rootIndex());
342 double value = model()->data(index).toDouble();
343
344 if (value > 0.0) {
345 double angle = 360*value/totalValue;
346
347 QModelIndex colorIndex = model()->index(row, 0, rootIndex());
348 QColor color = QColor(model()->data(colorIndex,
349 Qt::DecorationRole).toString());
350
351 if (currentIndex() == index)
352 painter.setBrush(QBrush(color, Qt::Dense4Pattern));
353 else if (selections->isSelected(index))
354 painter.setBrush(QBrush(color, Qt::Dense3Pattern));
355 else
356 painter.setBrush(QBrush(color));
357
358 painter.drawPie(0, 0, pieSize, pieSize, int(startAngle*16),
359 int(angle*16));
360
361 startAngle += angle;
362 }
363 }
364 painter.restore();
365
366 int keyNumber = 0;
367
368 for (row = 0; row < model()->rowCount(rootIndex()); ++row) {
369
370 QModelIndex index = model()->index(row, 1, rootIndex());
371 double value = model()->data(index).toDouble();
372
373 if (value > 0.0) {
374 QModelIndex labelIndex = model()->index(row, 0, rootIndex());
375
376 QStyleOptionViewItem option = viewOptions();
377 option.rect = visualRect(labelIndex);
378 if (selections->isSelected(labelIndex))
379 option.state |= QStyle::State_Selected;
380 if (currentIndex() == labelIndex)
381 option.state |= QStyle::State_HasFocus;
382 itemDelegate()->paint(&painter, option, labelIndex);
383
384 keyNumber++;
385 }
386 }
387 }
388}
389
390void PieView::resizeEvent(QResizeEvent * /* event */)
391{
392 updateGeometries();
393}
394
395int PieView::rows(const QModelIndex &index) const
396{
397 return model()->rowCount(model()->parent(index));
398}
399
400void PieView::rowsInserted(const QModelIndex &parent, int start, int end)
401{
402 for (int row = start; row <= end; ++row) {
403
404 QModelIndex index = model()->index(row, 1, rootIndex());
405 double value = model()->data(index).toDouble();
406
407 if (value > 0.0) {
408 totalValue += value;
409 validItems++;
410 }
411 }
412
413 QAbstractItemView::rowsInserted(parent, start, end);
414}
415
416void PieView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
417{
418 for (int row = start; row <= end; ++row) {
419
420 QModelIndex index = model()->index(row, 1, rootIndex());
421 double value = model()->data(index).toDouble();
422 if (value > 0.0) {
423 totalValue -= value;
424 validItems--;
425 }
426 }
427
428 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
429}
430
431void PieView::scrollContentsBy(int dx, int dy)
432{
433 viewport()->scroll(dx, dy);
434}
435
436void PieView::scrollTo(const QModelIndex &index, ScrollHint)
437{
438 QRect area = viewport()->rect();
439 QRect rect = visualRect(index);
440
441 if (rect.left() < area.left())
442 horizontalScrollBar()->setValue(
443 horizontalScrollBar()->value() + rect.left() - area.left());
444 else if (rect.right() > area.right())
445 horizontalScrollBar()->setValue(
446 horizontalScrollBar()->value() + qMin(
447 rect.right() - area.right(), rect.left() - area.left()));
448
449 if (rect.top() < area.top())
450 verticalScrollBar()->setValue(
451 verticalScrollBar()->value() + rect.top() - area.top());
452 else if (rect.bottom() > area.bottom())
453 verticalScrollBar()->setValue(
454 verticalScrollBar()->value() + qMin(
455 rect.bottom() - area.bottom(), rect.top() - area.top()));
456
457 update();
458}
459
460/*
461 Find the indices corresponding to the extent of the selection.
462*/
463
464void PieView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
465{
466 // Use content widget coordinates because we will use the itemRegion()
467 // function to check for intersections.
468
469 QRect contentsRect = rect.translated(
470 horizontalScrollBar()->value(),
471 verticalScrollBar()->value()).normalized();
472
473 int rows = model()->rowCount(rootIndex());
474 int columns = model()->columnCount(rootIndex());
475 QModelIndexList indexes;
476
477 for (int row = 0; row < rows; ++row) {
478 for (int column = 0; column < columns; ++column) {
479 QModelIndex index = model()->index(row, column, rootIndex());
480 QRegion region = itemRegion(index);
481 if (!region.intersect(contentsRect).isEmpty())
482 indexes.append(index);
483 }
484 }
485
486 if (indexes.size() > 0) {
487 int firstRow = indexes[0].row();
488 int lastRow = indexes[0].row();
489 int firstColumn = indexes[0].column();
490 int lastColumn = indexes[0].column();
491
492 for (int i = 1; i < indexes.size(); ++i) {
493 firstRow = qMin(firstRow, indexes[i].row());
494 lastRow = qMax(lastRow, indexes[i].row());
495 firstColumn = qMin(firstColumn, indexes[i].column());
496 lastColumn = qMax(lastColumn, indexes[i].column());
497 }
498
499 QItemSelection selection(
500 model()->index(firstRow, firstColumn, rootIndex()),
501 model()->index(lastRow, lastColumn, rootIndex()));
502 selectionModel()->select(selection, command);
503 } else {
504 QModelIndex noIndex;
505 QItemSelection selection(noIndex, noIndex);
506 selectionModel()->select(selection, command);
507 }
508
509 update();
510}
511
512void PieView::updateGeometries()
513{
514 horizontalScrollBar()->setPageStep(viewport()->width());
515 horizontalScrollBar()->setRange(0, qMax(0, 2*totalSize - viewport()->width()));
516 verticalScrollBar()->setPageStep(viewport()->height());
517 verticalScrollBar()->setRange(0, qMax(0, totalSize - viewport()->height()));
518}
519
520int PieView::verticalOffset() const
521{
522 return verticalScrollBar()->value();
523}
524
525/*
526 Returns the position of the item in viewport coordinates.
527*/
528
529QRect PieView::visualRect(const QModelIndex &index) const
530{
531 QRect rect = itemRect(index);
532 if (rect.isValid())
533 return QRect(rect.left() - horizontalScrollBar()->value(),
534 rect.top() - verticalScrollBar()->value(),
535 rect.width(), rect.height());
536 else
537 return rect;
538}
539
540/*
541 Returns a region corresponding to the selection in viewport coordinates.
542*/
543
544QRegion PieView::visualRegionForSelection(const QItemSelection &selection) const
545{
546 int ranges = selection.count();
547
548 if (ranges == 0)
549 return QRect();
550
551 QRegion region;
552 for (int i = 0; i < ranges; ++i) {
553 QItemSelectionRange range = selection.at(i);
554 for (int row = range.top(); row <= range.bottom(); ++row) {
555 for (int col = range.left(); col <= range.right(); ++col) {
556 QModelIndex index = model()->index(row, col, rootIndex());
557 region += visualRect(index);
558 }
559 }
560 }
561 return region;
562}
Note: See TracBrowser for help on using the repository browser.