##// END OF EJS Templates
Fix zooming in crash...
Fix zooming in crash The bounding rect has to fit inside int limits when zooming in, meaning that the height and width both have to be within the int limits (INT_MAX). QWidget::update() uses a region (based on bounding rect) that has to be compatible with QRect. The geometry change is only done if the bounding rect fulfills this requirement. Task-number: QTRD-1907 Change-Id: I4e874de355390c5fc983ac1e0976bf0d21e2d10c Reviewed-by: Miikka Heikkinen <miikka.heikkinen@digia.com>

File last commit:

r2592:621c955c10d2
r2603:1e4c29e111d4
Show More
polarchartaxisangular.cpp
422 lines | 16.4 KiB | text/x-c | CppLexer
/ src / axis / polarchartaxisangular.cpp
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc
** All rights reserved.
** For any questions to Digia, please use contact form at http://qt.digia.com
**
** This file is part of the Qt Enterprise Charts Add-on.
**
** $QT_BEGIN_LICENSE$
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.digia.com
** $QT_END_LICENSE$
**
****************************************************************************/
#include "polarchartaxisangular_p.h"
#include "chartpresenter_p.h"
#include "abstractchartlayout_p.h"
#include "qabstractaxis.h"
#include "qabstractaxis_p.h"
#include <QDebug>
#include <qmath.h>
#include <QTextDocument>
QTCOMMERCIALCHART_BEGIN_NAMESPACE
PolarChartAxisAngular::PolarChartAxisAngular(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis)
: PolarChartAxis(axis, item, intervalAxis)
{
}
PolarChartAxisAngular::~PolarChartAxisAngular()
{
}
void PolarChartAxisAngular::updateGeometry()
{
QGraphicsLayoutItem::updateGeometry();
const QVector<qreal> &layout = this->layout();
if (layout.isEmpty())
return;
createAxisLabels(layout);
QStringList labelList = labels();
QPointF center = axisGeometry().center();
QList<QGraphicsItem *> arrowItemList = arrowItems();
QList<QGraphicsItem *> gridItemList = gridItems();
QList<QGraphicsItem *> labelItemList = labelItems();
QList<QGraphicsItem *> shadeItemList = shadeItems();
QGraphicsTextItem *title = titleItem();
QGraphicsEllipseItem *axisLine = static_cast<QGraphicsEllipseItem *>(arrowItemList.at(0));
axisLine->setRect(axisGeometry());
qreal radius = axisGeometry().height() / 2.0;
QRectF previousLabelRect;
QRectF firstLabelRect;
qreal labelHeight = 0;
bool firstShade = true;
bool nextTickVisible = false;
if (layout.size())
nextTickVisible = !(layout.at(0) < 0.0 || layout.at(0) > 360.0);
for (int i = 0; i < layout.size(); ++i) {
qreal angularCoordinate = layout.at(i);
QGraphicsLineItem *gridLineItem = static_cast<QGraphicsLineItem *>(gridItemList.at(i));
QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i + 1));
QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labelItemList.at(i));
QGraphicsPathItem *shadeItem = 0;
if (i == 0)
shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0));
else if (i % 2)
shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at((i / 2) + 1));
// Ignore ticks outside valid range
bool currentTickVisible = nextTickVisible;
if ((i == layout.size() - 1)
|| layout.at(i + 1) < 0.0
|| layout.at(i + 1) > 360.0) {
nextTickVisible = false;
} else {
nextTickVisible = true;
}
qreal labelCoordinate = angularCoordinate;
qreal labelVisible = currentTickVisible;
if (intervalAxis()) {
qreal farEdge;
if (i == (layout.size() - 1))
farEdge = 360.0;
else
farEdge = qMin(qreal(360.0), layout.at(i + 1));
// Adjust the labelCoordinate to show it if next tick is visible
if (nextTickVisible)
labelCoordinate = qMax(qreal(0.0), labelCoordinate);
labelCoordinate = (labelCoordinate + farEdge) / 2.0;
// Don't display label once the category gets too small near the axis
if (labelCoordinate < 5.0 || labelCoordinate > 355.0)
labelVisible = false;
else
labelVisible = true;
}
// Need this also in label calculations, so determine it first
QLineF tickLine(QLineF::fromPolar(radius - tickWidth(), 90.0 - angularCoordinate).p2(),
QLineF::fromPolar(radius + tickWidth(), 90.0 - angularCoordinate).p2());
tickLine.translate(center);
// Angular axis label
if (axis()->labelsVisible() && labelVisible) {
labelItem->setHtml(labelList.at(i));
const QRectF &rect = labelItem->boundingRect();
QPointF labelCenter = rect.center();
labelItem->setTransformOriginPoint(labelCenter.x(), labelCenter.y());
QRectF boundingRect = ChartPresenter::textBoundingRect(axis()->labelsFont(), labelList.at(i), axis()->labelsAngle());
boundingRect.moveCenter(labelCenter);
QPointF positionDiff(rect.topLeft() - boundingRect.topLeft());
QPointF labelPoint;
if (intervalAxis()) {
QLineF labelLine = QLineF::fromPolar(radius + tickWidth(), 90.0 - labelCoordinate);
labelLine.translate(center);
labelPoint = labelLine.p2();
} else {
labelPoint = tickLine.p2();
}
QRectF labelRect = moveLabelToPosition(labelCoordinate, labelPoint, boundingRect);
labelItem->setPos(labelRect.topLeft() + positionDiff);
// Store height for title calculations
qreal labelClearance = axisGeometry().top() - labelRect.top();
labelHeight = qMax(labelHeight, labelClearance);
// Label overlap detection
if (i && (previousLabelRect.intersects(labelRect) || firstLabelRect.intersects(labelRect))) {
labelVisible = false;
} else {
// Store labelRect for future comparison. Some area is deducted to make things look
// little nicer, as usually intersection happens at label corner with angular labels.
labelRect.adjust(-2.0, -4.0, -2.0, -4.0);
if (firstLabelRect.isEmpty())
firstLabelRect = labelRect;
previousLabelRect = labelRect;
labelVisible = true;
}
}
labelItem->setVisible(labelVisible);
if (!currentTickVisible) {
gridLineItem->setVisible(false);
tickItem->setVisible(false);
if (shadeItem)
shadeItem->setVisible(false);
continue;
}
// Angular grid line
QLineF gridLine = QLineF::fromPolar(radius, 90.0 - angularCoordinate);
gridLine.translate(center);
gridLineItem->setLine(gridLine);
gridLineItem->setVisible(true);
// Tick
tickItem->setLine(tickLine);
tickItem->setVisible(true);
// Shades
if (i % 2 || (i == 0 && !nextTickVisible)) {
QPainterPath path;
path.moveTo(center);
if (i == 0) {
// If first tick is also the last, we need to custom fill the first partial arc
// or it won't get filled.
path.arcTo(axisGeometry(), 90.0 - layout.at(0), layout.at(0));
path.closeSubpath();
} else {
qreal nextCoordinate;
if (!nextTickVisible) // Last visible tick
nextCoordinate = 360.0;
else
nextCoordinate = layout.at(i + 1);
qreal arcSpan = angularCoordinate - nextCoordinate;
path.arcTo(axisGeometry(), 90.0 - angularCoordinate, arcSpan);
path.closeSubpath();
// Add additional arc for first shade item if there is a partial arc to be filled
if (firstShade) {
QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0));
if (layout.at(i - 1) > 0.0) {
QPainterPath specialPath;
specialPath.moveTo(center);
specialPath.arcTo(axisGeometry(), 90.0 - layout.at(i - 1), layout.at(i - 1));
specialPath.closeSubpath();
specialShadeItem->setPath(specialPath);
specialShadeItem->setVisible(true);
} else {
specialShadeItem->setVisible(false);
}
}
}
shadeItem->setPath(path);
shadeItem->setVisible(true);
firstShade = false;
}
}
// Title, centered above the chart
QString titleText = axis()->titleText();
if (!titleText.isEmpty() && axis()->isTitleVisible()) {
QRectF dummyRect;
title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0), axisGeometry().width(), Qt::Horizontal, dummyRect));
QRectF titleBoundingRect = title->boundingRect();
QPointF titleCenter = center - titleBoundingRect.center();
title->setPos(titleCenter.x(), axisGeometry().top() - titlePadding() * 2.0 - titleBoundingRect.height() - labelHeight);
}
}
Qt::Orientation PolarChartAxisAngular::orientation() const
{
return Qt::Horizontal;
}
void PolarChartAxisAngular::createItems(int count)
{
if (arrowItems().count() == 0) {
// angular axis line
QGraphicsEllipseItem *arrow = new QGraphicsEllipseItem(presenter()->rootItem());
arrow->setPen(axis()->linePen());
arrowGroup()->addToGroup(arrow);
}
for (int i = 0; i < count; ++i) {
QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem());
QGraphicsLineItem *grid = new QGraphicsLineItem(presenter()->rootItem());
QGraphicsTextItem *label = new QGraphicsTextItem(presenter()->rootItem());
label->document()->setDocumentMargin(ChartPresenter::textMargin());
QGraphicsTextItem *title = titleItem();
arrow->setPen(axis()->linePen());
grid->setPen(axis()->gridLinePen());
label->setFont(axis()->labelsFont());
label->setDefaultTextColor(axis()->labelsBrush().color());
label->setRotation(axis()->labelsAngle());
title->setFont(axis()->titleFont());
title->setDefaultTextColor(axis()->titleBrush().color());
title->setHtml(axis()->titleText());
arrowGroup()->addToGroup(arrow);
gridGroup()->addToGroup(grid);
labelGroup()->addToGroup(label);
if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) {
QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem());
shade->setPen(axis()->shadesPen());
shade->setBrush(axis()->shadesBrush());
shadeGroup()->addToGroup(shade);
}
}
}
void PolarChartAxisAngular::handleArrowPenChanged(const QPen &pen)
{
bool first = true;
foreach (QGraphicsItem *item, arrowItems()) {
if (first) {
first = false;
// First arrow item is the outer circle of axis
static_cast<QGraphicsEllipseItem *>(item)->setPen(pen);
} else {
static_cast<QGraphicsLineItem *>(item)->setPen(pen);
}
}
}
void PolarChartAxisAngular::handleGridPenChanged(const QPen &pen)
{
foreach (QGraphicsItem *item, gridItems())
static_cast<QGraphicsLineItem *>(item)->setPen(pen);
}
QSizeF PolarChartAxisAngular::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
Q_UNUSED(which);
Q_UNUSED(constraint);
return QSizeF(-1, -1);
}
qreal PolarChartAxisAngular::preferredAxisRadius(const QSizeF &maxSize)
{
qreal radius = maxSize.height() / 2.0;
if (maxSize.width() < maxSize.height())
radius = maxSize.width() / 2.0;
if (axis()->labelsVisible()) {
QVector<qreal> layout = calculateLayout();
if (layout.isEmpty())
return radius;
createAxisLabels(layout);
QStringList labelList = labels();
QFont font = axis()->labelsFont();
QRectF maxRect;
maxRect.setSize(maxSize);
maxRect.moveCenter(QPointF(0.0, 0.0));
// This is a horrible way to find out the maximum radius for angular axis and its labels.
// It just increments the radius down until everyhing fits the constraint size.
// Proper way would be to actually calculate it but this seems to work reasonably fast as it is.
bool nextTickVisible = false;
for (int i = 0; i < layout.size(); ) {
if ((i == layout.size() - 1)
|| layout.at(i + 1) < 0.0
|| layout.at(i + 1) > 360.0) {
nextTickVisible = false;
} else {
nextTickVisible = true;
}
qreal labelCoordinate = layout.at(i);
qreal labelVisible;
if (intervalAxis()) {
qreal farEdge;
if (i == (layout.size() - 1))
farEdge = 360.0;
else
farEdge = qMin(qreal(360.0), layout.at(i + 1));
// Adjust the labelCoordinate to show it if next tick is visible
if (nextTickVisible)
labelCoordinate = qMax(qreal(0.0), labelCoordinate);
labelCoordinate = (labelCoordinate + farEdge) / 2.0;
}
if (labelCoordinate < 0.0 || labelCoordinate > 360.0)
labelVisible = false;
else
labelVisible = true;
if (!labelVisible) {
i++;
continue;
}
QRectF boundingRect = ChartPresenter::textBoundingRect(axis()->labelsFont(), labelList.at(i), axis()->labelsAngle());
QPointF labelPoint = QLineF::fromPolar(radius + tickWidth(), 90.0 - labelCoordinate).p2();
boundingRect = moveLabelToPosition(labelCoordinate, labelPoint, boundingRect);
QRectF intersectRect = maxRect.intersected(boundingRect);
if (boundingRect.isEmpty() || intersectRect == boundingRect) {
i++;
} else {
qreal reduction(0.0);
// If there is no intersection, reduce by smallest dimension of label rect to be on the safe side
if (intersectRect.isEmpty()) {
reduction = qMin(boundingRect.height(), boundingRect.width());
} else {
// Approximate needed radius reduction is the amount label rect exceeds max rect in either dimension.
// Could be further optimized by figuring out the proper math how to calculate exact needed reduction.
reduction = qMax(boundingRect.height() - intersectRect.height(),
boundingRect.width() - intersectRect.width());
}
// Typically the approximated reduction is little low, so add one
radius -= (reduction + 1.0);
if (radius < 1.0) // safeguard
return 1.0;
}
}
}
if (!axis()->titleText().isEmpty() && axis()->isTitleVisible()) {
QRectF titleRect = ChartPresenter::textBoundingRect(axis()->titleFont(), axis()->titleText());
radius -= titlePadding() + (titleRect.height() / 2.0);
if (radius < 1.0) // safeguard
return 1.0;
}
return radius;
}
QRectF PolarChartAxisAngular::moveLabelToPosition(qreal angularCoordinate, QPointF labelPoint, QRectF labelRect) const
{
if (angularCoordinate == 0.0)
labelRect.moveCenter(labelPoint + QPointF(0, -labelRect.height() / 2.0));
else if (angularCoordinate < 90.0)
labelRect.moveBottomLeft(labelPoint);
else if (angularCoordinate == 90.0)
labelRect.moveCenter(labelPoint + QPointF(labelRect.width() / 2.0 + 2.0, 0)); // +2 so that it does not hit the radial axis
else if (angularCoordinate < 180.0)
labelRect.moveTopLeft(labelPoint);
else if (angularCoordinate == 180.0)
labelRect.moveCenter(labelPoint + QPointF(0, labelRect.height() / 2.0));
else if (angularCoordinate < 270.0)
labelRect.moveTopRight(labelPoint);
else if (angularCoordinate == 270.0)
labelRect.moveCenter(labelPoint + QPointF(-labelRect.width() / 2.0 - 2.0, 0)); // -2 so that it does not hit the radial axis
else if (angularCoordinate < 360.0)
labelRect.moveBottomRight(labelPoint);
else
labelRect.moveCenter(labelPoint + QPointF(0, -labelRect.height() / 2.0));
return labelRect;
}
#include "moc_polarchartaxisangular_p.cpp"
QTCOMMERCIALCHART_END_NAMESPACE