|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 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 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
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 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
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 |
|
51 PieView::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 |
|
65 void 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 |
|
86 bool 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 |
|
98 QModelIndex 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 |
|
161 bool 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 |
|
171 QRect 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 |
|
210 QRegion 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 |
|
247 int PieView::horizontalOffset() const |
|
248 { |
|
249 return horizontalScrollBar()->value(); |
|
250 } |
|
251 |
|
252 void 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 |
|
262 void PieView::mouseMoveEvent(QMouseEvent *event) |
|
263 { |
|
264 if (rubberBand) |
|
265 rubberBand->setGeometry(QRect(origin, event->pos()).normalized()); |
|
266 QAbstractItemView::mouseMoveEvent(event); |
|
267 } |
|
268 |
|
269 void PieView::mouseReleaseEvent(QMouseEvent *event) |
|
270 { |
|
271 QAbstractItemView::mouseReleaseEvent(event); |
|
272 if (rubberBand) |
|
273 rubberBand->hide(); |
|
274 viewport()->update(); |
|
275 } |
|
276 |
|
277 QModelIndex 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 |
|
308 void 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 |
|
390 void PieView::resizeEvent(QResizeEvent * /* event */) |
|
391 { |
|
392 updateGeometries(); |
|
393 } |
|
394 |
|
395 int PieView::rows(const QModelIndex &index) const |
|
396 { |
|
397 return model()->rowCount(model()->parent(index)); |
|
398 } |
|
399 |
|
400 void 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 |
|
416 void 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 |
|
431 void PieView::scrollContentsBy(int dx, int dy) |
|
432 { |
|
433 viewport()->scroll(dx, dy); |
|
434 } |
|
435 |
|
436 void 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 |
|
464 void 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 |
|
512 void 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 |
|
520 int PieView::verticalOffset() const |
|
521 { |
|
522 return verticalScrollBar()->value(); |
|
523 } |
|
524 |
|
525 /* |
|
526 Returns the position of the item in viewport coordinates. |
|
527 */ |
|
528 |
|
529 QRect 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 |
|
544 QRegion 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 } |