/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtWidgets>#include <cmath>#ifndef M_PI#define M_PI 3.1415927#endif#include "pieview.h"
PieView::PieView(QWidget*parent)
: QAbstractItemView(parent)
{
horizontalScrollBar()->setRange(0,0);
verticalScrollBar()->setRange(0,0);
margin =8;
totalSize =300;
pieSize = totalSize -2* margin;
validItems =0;
totalValue =0.0;
rubberBand =0;
}
void PieView::dataChanged(constQModelIndex&topLeft,constQModelIndex&bottomRight,constQVector<int>&)
{
QAbstractItemView::dataChanged(topLeft, bottomRight);
validItems =0;
totalValue =0.0;
for (int row =0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
double value = model()->data(index).toDouble();
if (value >0.0) {
totalValue += value;
validItems++;
}
}
viewport()->update();
}
bool PieView::edit(constQModelIndex&index, EditTrigger trigger,QEvent*event)
{
if (index.column() ==0)
returnQAbstractItemView::edit(index, trigger, event);
elsereturnfalse;
}
/*
Returns the item that covers the coordinate given in the view.
*/QModelIndex PieView::indexAt(constQPoint&point) const
{
if (validItems ==0)
returnQModelIndex();
// Transform the view coordinates into contents widget coordinates.int wx = point.x() + horizontalScrollBar()->value();
int wy = point.y() + verticalScrollBar()->value();
if (wx < totalSize) {
double cx = wx - totalSize /2;
double cy = totalSize /2- wy; // positive cy for items above the center// Determine the distance from the center point of the pie chart.double d = std::sqrt(std::pow(cx,2) + std::pow(cy,2));
if (d ==0|| d > pieSize /2)
returnQModelIndex();
// Determine the angle of the point.double angle = (180/ M_PI) * std::acos(cx / d);
if (cy <0)
angle =360- angle;
// Find the relevant slice of the pie.double startAngle =0.0;
for (int row =0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
double value = model()->data(index).toDouble();
if (value >0.0) {
double sliceAngle =360* value / totalValue;
if (angle >= startAngle && angle < (startAngle + sliceAngle))
return model()->index(row,1, rootIndex());
startAngle += sliceAngle;
}
}
} else {
double itemHeight =QFontMetrics(viewOptions().font).height();
int listItem =int((wy - margin) / itemHeight);
int validRow =0;
for (int row =0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
if (model()->data(index).toDouble() >0.0) {
if (listItem == validRow)
return model()->index(row,0, rootIndex());
// Update the list index that corresponds to the next valid row.++validRow;
}
}
}
returnQModelIndex();
}
bool PieView::isIndexHidden(constQModelIndex&/*index*/) const
{
returnfalse;
}
/*
Returns the rectangle of the item at position \a index in the
model. The rectangle is in contents coordinates.
*/QRect PieView::itemRect(constQModelIndex&index) const
{
if (!index.isValid())
returnQRect();
// Check whether the index's row is in the list of rows represented// by slices.QModelIndex valueIndex;
if (index.column() !=1)
valueIndex = model()->index(index.row(),1, rootIndex());
else
valueIndex = index;
if (model()->data(valueIndex).toDouble() <=0.0)
returnQRect();
int listItem =0;
for (int row = index.row()-1; row >=0; --row) {
if (model()->data(model()->index(row,1, rootIndex())).toDouble() >0.0)
listItem++;
}
double itemHeight;
switch (index.column()) {
case0:
itemHeight =QFontMetrics(viewOptions().font).height();
returnQRect(totalSize,int(margin + listItem*itemHeight),
totalSize - margin,int(itemHeight));
case1:
return viewport()->rect();
}
returnQRect();
}
QRegion PieView::itemRegion(constQModelIndex&index) const
{
if (!index.isValid())
returnQRegion();
if (index.column() !=1)
return itemRect(index);
if (model()->data(index).toDouble() <=0.0)
returnQRegion();
double startAngle =0.0;
for (int row =0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex sliceIndex = model()->index(row,1, rootIndex());
double value = model()->data(sliceIndex).toDouble();
if (value >0.0) {
double angle =360* value / totalValue;
if (sliceIndex == index) {
QPainterPath slicePath;
slicePath.moveTo(totalSize /2, totalSize /2);
slicePath.arcTo(margin, margin, margin+pieSize, margin+pieSize,
startAngle, angle);
slicePath.closeSubpath();
returnQRegion(slicePath.toFillPolygon().toPolygon());
}
startAngle += angle;
}
}
returnQRegion();
}
int PieView::horizontalOffset() const
{
return horizontalScrollBar()->value();
}
void PieView::mousePressEvent(QMouseEvent*event)
{
QAbstractItemView::mousePressEvent(event);
origin = event->pos();
if (!rubberBand)
rubberBand =newQRubberBand(QRubberBand::Rectangle, viewport());
rubberBand->setGeometry(QRect(origin,QSize()));
rubberBand->show();
}
void PieView::mouseMoveEvent(QMouseEvent*event)
{
if (rubberBand)
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
QAbstractItemView::mouseMoveEvent(event);
}
void PieView::mouseReleaseEvent(QMouseEvent*event)
{
QAbstractItemView::mouseReleaseEvent(event);
if (rubberBand)
rubberBand->hide();
viewport()->update();
}
QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction,Qt::KeyboardModifiers /*modifiers*/)
{
QModelIndex current = currentIndex();
switch (cursorAction) {
case MoveLeft:
case MoveUp:
if (current.row() >0)
current = model()->index(current.row() -1, current.column(),
rootIndex());
else
current = model()->index(0, current.column(), rootIndex());
break;
case MoveRight:
case MoveDown:
if (current.row() < rows(current) -1)
current = model()->index(current.row() +1, current.column(),
rootIndex());
else
current = model()->index(rows(current) -1, current.column(),
rootIndex());
break;
default:
break;
}
viewport()->update();
return current;
}
void PieView::paintEvent(QPaintEvent*event)
{
QItemSelectionModel*selections = selectionModel();
QStyleOptionViewItem option = viewOptions();
QBrush background = option.palette.base();
QPen foreground(option.palette.color(QPalette::WindowText));
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(event->rect(), background);
painter.setPen(foreground);
// Viewport rectanglesQRect pieRect =QRect(margin, margin, pieSize, pieSize);
if (validItems <=0)
return;
painter.save();
painter.translate(pieRect.x() - horizontalScrollBar()->value(),
pieRect.y() - verticalScrollBar()->value());
painter.drawEllipse(0,0, pieSize, pieSize);
double startAngle =0.0;
int row;
for (row =0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
double value = model()->data(index).toDouble();
if (value >0.0) {
double angle =360*value/totalValue;
QModelIndex colorIndex = model()->index(row,0, rootIndex());
QColor color =QColor(model()->data(colorIndex,Qt::DecorationRole).toString());
if (currentIndex() == index)
painter.setBrush(QBrush(color,Qt::Dense4Pattern));
elseif (selections->isSelected(index))
painter.setBrush(QBrush(color,Qt::Dense3Pattern));
else
painter.setBrush(QBrush(color));
painter.drawPie(0,0, pieSize, pieSize,int(startAngle*16),int(angle*16));
startAngle += angle;
}
}
painter.restore();
int keyNumber =0;
for (row =0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
double value = model()->data(index).toDouble();
if (value >0.0) {
QModelIndex labelIndex = model()->index(row,0, rootIndex());
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(labelIndex);
if (selections->isSelected(labelIndex))
option.state |=QStyle::State_Selected;
if (currentIndex() == labelIndex)
option.state |=QStyle::State_HasFocus;
itemDelegate()->paint(&painter, option, labelIndex);
++keyNumber;
}
}
}
void PieView::resizeEvent(QResizeEvent*/* event */)
{
updateGeometries();
}
int PieView::rows(constQModelIndex&index) const
{
return model()->rowCount(model()->parent(index));
}
void PieView::rowsInserted(constQModelIndex&parent,int start,int end)
{
for (int row = start; row <= end; ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
double value = model()->data(index).toDouble();
if (value >0.0) {
totalValue += value;
++validItems;
}
}
QAbstractItemView::rowsInserted(parent, start, end);
}
void PieView::rowsAboutToBeRemoved(constQModelIndex&parent,int start,int end)
{
for (int row = start; row <= end; ++row) {
QModelIndex index = model()->index(row,1, rootIndex());
double value = model()->data(index).toDouble();
if (value >0.0) {
totalValue -= value;
--validItems;
}
}
QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
}
void PieView::scrollContentsBy(int dx,int dy)
{
viewport()->scroll(dx, dy);
}
void PieView::scrollTo(constQModelIndex&index, ScrollHint)
{
QRect area = viewport()->rect();
QRect rect = visualRect(index);
if (rect.left() < area.left()) {
horizontalScrollBar()->setValue(
horizontalScrollBar()->value() + rect.left() - area.left());
} elseif (rect.right() > area.right()) {
horizontalScrollBar()->setValue(
horizontalScrollBar()->value() +qMin(
rect.right() - area.right(), rect.left() - area.left()));
}
if (rect.top() < area.top()) {
verticalScrollBar()->setValue(
verticalScrollBar()->value() + rect.top() - area.top());
} elseif (rect.bottom() > area.bottom()) {
verticalScrollBar()->setValue(
verticalScrollBar()->value() +qMin(
rect.bottom() - area.bottom(), rect.top() - area.top()));
}
update();
}
/*
Find the indices corresponding to the extent of the selection.
*/void PieView::setSelection(constQRect&rect,QItemSelectionModel::SelectionFlags command)
{
// Use content widget coordinates because we will use the itemRegion()// function to check for intersections.QRect contentsRect = rect.translated(
horizontalScrollBar()->value(),
verticalScrollBar()->value()).normalized();
int rows = model()->rowCount(rootIndex());
int columns = model()->columnCount(rootIndex());
QModelIndexList indexes;
for (int row =0; row < rows; ++row) {
for (int column =0; column < columns; ++column) {
QModelIndex index = model()->index(row, column, rootIndex());
QRegion region = itemRegion(index);
if (region.intersects(contentsRect))
indexes.append(index);
}
}
if (indexes.size() >0) {
int firstRow = indexes[0].row();
int lastRow = indexes[0].row();
int firstColumn = indexes[0].column();
int lastColumn = indexes[0].column();
for (int i =1; i < indexes.size(); ++i) {
firstRow =qMin(firstRow, indexes[i].row());
lastRow =qMax(lastRow, indexes[i].row());
firstColumn =qMin(firstColumn, indexes[i].column());
lastColumn =qMax(lastColumn, indexes[i].column());
}
QItemSelection selection(
model()->index(firstRow, firstColumn, rootIndex()),
model()->index(lastRow, lastColumn, rootIndex()));
selectionModel()->select(selection, command);
} else {
QModelIndex noIndex;
QItemSelection selection(noIndex, noIndex);
selectionModel()->select(selection, command);
}
update();
}
void PieView::updateGeometries()
{
horizontalScrollBar()->setPageStep(viewport()->width());
horizontalScrollBar()->setRange(0,qMax(0,2*totalSize - viewport()->width()));
verticalScrollBar()->setPageStep(viewport()->height());
verticalScrollBar()->setRange(0,qMax(0, totalSize - viewport()->height()));
}
int PieView::verticalOffset() const
{
return verticalScrollBar()->value();
}
/*
Returns the position of the item in viewport coordinates.
*/QRect PieView::visualRect(constQModelIndex&index) const
{
QRect rect = itemRect(index);
if (!rect.isValid())
return rect;
returnQRect(rect.left() - horizontalScrollBar()->value(),
rect.top() - verticalScrollBar()->value(),
rect.width(), rect.height());
}
/*
Returns a region corresponding to the selection in viewport coordinates.
*/QRegion PieView::visualRegionForSelection(constQItemSelection&selection) const
{
int ranges = selection.count();
if (ranges ==0)
returnQRect();
QRegion region;
for (int i =0; i < ranges; ++i) {
QItemSelectionRange range = selection.at(i);
for (int row = range.top(); row <= range.bottom(); ++row) {
for (int col = range.left(); col <= range.right(); ++col) {
QModelIndex index = model()->index(row, col, rootIndex());
region += visualRect(index);
}
}
}
return region;
}