地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

927 lines
22 KiB

15 years ago
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
* Qwt Widget Library
* Copyright (C) 1997 Josef Wilgen
* Copyright (C) 2002 Uwe Rathmann
*
15 years ago
* This library is free software; you can redistribute it and/or
* modify it under the terms of the Qwt License, Version 1.0
*****************************************************************************/
#include "qwt_scale_draw.h"
15 years ago
#include "qwt_scale_div.h"
#include "qwt_scale_map.h"
#include "qwt_math.h"
#include "qwt_painter.h"
#include <qpen.h>
#include <qpainter.h>
#include <qmath.h>
15 years ago
#if QT_VERSION < 0x040601
#define qFastSin(x) qSin(x)
#define qFastCos(x) qCos(x)
15 years ago
#endif
class QwtScaleDraw::PrivateData
{
public:
PrivateData():
len( 0 ),
alignment( QwtScaleDraw::BottomScale ),
labelAlignment( 0 ),
labelRotation( 0.0 )
{
15 years ago
}
QPointF pos;
double len;
15 years ago
Alignment alignment;
Qt::Alignment labelAlignment;
double labelRotation;
};
/*!
\brief Constructor
The range of the scale is initialized to [0, 100],
The position is at (0, 0) with a length of 100.
The orientation is QwtAbstractScaleDraw::Bottom.
*/
QwtScaleDraw::QwtScaleDraw()
{
d_data = new QwtScaleDraw::PrivateData;
setLength( 100 );
15 years ago
}
//! Destructor
QwtScaleDraw::~QwtScaleDraw()
{
delete d_data;
}
/*!
15 years ago
Return alignment of the scale
\sa setAlignment()
\return Alignment of the scale
15 years ago
*/
QwtScaleDraw::Alignment QwtScaleDraw::alignment() const
15 years ago
{
return d_data->alignment;
15 years ago
}
/*!
Set the alignment of the scale
\param align Alignment of the scale
15 years ago
The default alignment is QwtScaleDraw::BottomScale
\sa alignment()
*/
void QwtScaleDraw::setAlignment( Alignment align )
15 years ago
{
d_data->alignment = align;
}
/*!
Return the orientation
TopScale, BottomScale are horizontal (Qt::Horizontal) scales,
LeftScale, RightScale are vertical (Qt::Vertical) scales.
\return Orientation of the scale
15 years ago
\sa alignment()
*/
Qt::Orientation QwtScaleDraw::orientation() const
{
switch ( d_data->alignment )
{
case TopScale:
case BottomScale:
return Qt::Horizontal;
case LeftScale:
case RightScale:
default:
return Qt::Vertical;
15 years ago
}
}
/*!
\brief Determine the minimum border distance
This member function returns the minimum space
needed to draw the mark labels at the scale's endpoints.
\param font Font
\param start Start border distance
\param end End border distance
*/
void QwtScaleDraw::getBorderDistHint(
const QFont &font, int &start, int &end ) const
15 years ago
{
start = 0;
end = 0;
if ( !hasComponent( QwtAbstractScaleDraw::Labels ) )
15 years ago
return;
const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
if ( ticks.count() == 0 )
15 years ago
return;
// Find the ticks, that are mapped to the borders.
// minTick is the tick, that is mapped to the top/left-most position
// in widget coordinates.
double minTick = ticks[0];
double minPos = scaleMap().transform( minTick );
double maxTick = minTick;
double maxPos = minPos;
for ( int i = 1; i < ticks.count(); i++ )
{
const double tickPos = scaleMap().transform( ticks[i] );
if ( tickPos < minPos )
{
minTick = ticks[i];
minPos = tickPos;
}
if ( tickPos > scaleMap().transform( maxTick ) )
{
maxTick = ticks[i];
maxPos = tickPos;
}
}
15 years ago
double e = 0.0;
double s = 0.0;
15 years ago
if ( orientation() == Qt::Vertical )
{
s = -labelRect( font, minTick ).top();
s -= qAbs( minPos - qRound( scaleMap().p2() ) );
15 years ago
e = labelRect( font, maxTick ).bottom();
e -= qAbs( maxPos - scaleMap().p1() );
}
15 years ago
else
{
s = -labelRect( font, minTick ).left();
s -= qAbs( minPos - scaleMap().p1() );
15 years ago
e = labelRect( font, maxTick ).right();
e -= qAbs( maxPos - scaleMap().p2() );
}
15 years ago
if ( s < 0.0 )
s = 0.0;
if ( e < 0.0 )
e = 0.0;
start = qCeil( s );
end = qCeil( e );
15 years ago
}
/*!
Determine the minimum distance between two labels, that is necessary
that the texts don't overlap.
\param font Font
\return The maximum width of a label
\sa getBorderDistHint()
*/
int QwtScaleDraw::minLabelDist( const QFont &font ) const
15 years ago
{
if ( !hasComponent( QwtAbstractScaleDraw::Labels ) )
15 years ago
return 0;
const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
if ( ticks.isEmpty() )
15 years ago
return 0;
const QFontMetrics fm( font );
15 years ago
const bool vertical = ( orientation() == Qt::Vertical );
15 years ago
QRectF bRect1;
QRectF bRect2 = labelRect( font, ticks[0] );
if ( vertical )
{
bRect2.setRect( -bRect2.bottom(), 0.0, bRect2.height(), bRect2.width() );
15 years ago
}
double maxDist = 0.0;
for ( int i = 1; i < ticks.count(); i++ )
{
15 years ago
bRect1 = bRect2;
bRect2 = labelRect( font, ticks[i] );
if ( vertical )
{
bRect2.setRect( -bRect2.bottom(), 0.0,
bRect2.height(), bRect2.width() );
15 years ago
}
double dist = fm.leading(); // space between the labels
15 years ago
if ( bRect1.right() > 0 )
dist += bRect1.right();
if ( bRect2.left() < 0 )
dist += -bRect2.left();
if ( dist > maxDist )
maxDist = dist;
}
double angle = qwtRadians( labelRotation() );
15 years ago
if ( vertical )
angle += M_PI / 2;
const double sinA = qFastSin( angle ); // qreal -> double
if ( qFuzzyCompare( sinA + 1.0, 1.0 ) )
return qCeil( maxDist );
15 years ago
const int fmHeight = fm.ascent() - 2;
15 years ago
// The distance we need until there is
// the height of the label font. This height is needed
// for the neighbored label.
15 years ago
double labelDist = fmHeight / qFastSin( angle ) * qFastCos( angle );
15 years ago
if ( labelDist < 0 )
labelDist = -labelDist;
// For text orientations close to the scale orientation
15 years ago
if ( labelDist > maxDist )
labelDist = maxDist;
// For text orientations close to the opposite of the
15 years ago
// scale orientation
if ( labelDist < fmHeight )
labelDist = fmHeight;
return qCeil( labelDist );
15 years ago
}
/*!
Calculate the width/height that is needed for a
vertical/horizontal scale.
The extent is calculated from the pen width of the backbone,
the major tick length, the spacing and the maximum width/height
of the labels.
\param font Font used for painting the labels
\return Extent
15 years ago
\sa minLength()
*/
double QwtScaleDraw::extent( const QFont &font ) const
15 years ago
{
double d = 0;
15 years ago
if ( hasComponent( QwtAbstractScaleDraw::Labels ) )
{
15 years ago
if ( orientation() == Qt::Vertical )
d = maxLabelWidth( font );
15 years ago
else
d = maxLabelHeight( font );
15 years ago
if ( d > 0 )
d += spacing();
}
if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
{
d += maxTickLength();
15 years ago
}
if ( hasComponent( QwtAbstractScaleDraw::Backbone ) )
{
const double pw = qMax( 1, penWidth() ); // pen width can be zero
15 years ago
d += pw;
}
d = qMax( d, minimumExtent() );
15 years ago
return d;
}
/*!
Calculate the minimum length that is needed to draw the scale
\param font Font used for painting the labels
\return Minimum length that is needed to draw the scale
15 years ago
\sa extent()
*/
int QwtScaleDraw::minLength( const QFont &font ) const
15 years ago
{
int startDist, endDist;
getBorderDistHint( font, startDist, endDist );
15 years ago
const QwtScaleDiv &sd = scaleDiv();
const uint minorCount =
sd.ticks( QwtScaleDiv::MinorTick ).count() +
sd.ticks( QwtScaleDiv::MediumTick ).count();
15 years ago
const uint majorCount =
sd.ticks( QwtScaleDiv::MajorTick ).count();
15 years ago
int lengthForLabels = 0;
if ( hasComponent( QwtAbstractScaleDraw::Labels ) )
lengthForLabels = minLabelDist( font ) * majorCount;
15 years ago
int lengthForTicks = 0;
if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
{
const double pw = qMax( 1, penWidth() ); // penwidth can be zero
lengthForTicks = qCeil( ( majorCount + minorCount ) * ( pw + 1.0 ) );
15 years ago
}
return startDist + endDist + qMax( lengthForLabels, lengthForTicks );
15 years ago
}
/*!
Find the position, where to paint a label
The position has a distance that depends on the length of the ticks
in direction of the alignment().
15 years ago
\param value Value
\return Position, where to paint a label
15 years ago
*/
QPointF QwtScaleDraw::labelPosition( double value ) const
15 years ago
{
const double tval = scaleMap().transform( value );
double dist = spacing();
if ( hasComponent( QwtAbstractScaleDraw::Backbone ) )
dist += qMax( 1, penWidth() );
if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
dist += tickLength( QwtScaleDiv::MajorTick );
double px = 0;
double py = 0;
switch ( alignment() )
{
case RightScale:
{
px = d_data->pos.x() + dist;
py = tval;
break;
}
case LeftScale:
{
px = d_data->pos.x() - dist;
py = tval;
break;
}
case BottomScale:
{
px = tval;
py = d_data->pos.y() + dist;
break;
}
case TopScale:
{
px = tval;
py = d_data->pos.y() - dist;
break;
}
15 years ago
}
return QPointF( px, py );
15 years ago
}
/*!
Draw a tick
\param painter Painter
\param value Value of the tick
\param len Length of the tick
15 years ago
\sa drawBackbone(), drawLabel()
*/
void QwtScaleDraw::drawTick( QPainter *painter, double value, double len ) const
15 years ago
{
if ( len <= 0 )
return;
const bool roundingAlignment = QwtPainter::roundingAlignment( painter );
15 years ago
QPointF pos = d_data->pos;
15 years ago
double tval = scaleMap().transform( value );
if ( roundingAlignment )
tval = qRound( tval );
15 years ago
const int pw = penWidth();
int a = 0;
if ( pw > 1 && roundingAlignment )
a = 1;
switch ( alignment() )
{
case LeftScale:
{
double x1 = pos.x() + a;
double x2 = pos.x() + a - pw - len;
if ( roundingAlignment )
{
x1 = qRound( x1 );
x2 = qRound( x2 );
}
15 years ago
QwtPainter::drawLine( painter, x1, tval, x2, tval );
break;
}
case RightScale:
{
double x1 = pos.x();
double x2 = pos.x() + pw + len;
if ( roundingAlignment )
{
x1 = qRound( x1 );
x2 = qRound( x2 );
}
QwtPainter::drawLine( painter, x1, tval, x2, tval );
break;
}
case BottomScale:
{
double y1 = pos.y();
double y2 = pos.y() + pw + len;
if ( roundingAlignment )
{
y1 = qRound( y1 );
y2 = qRound( y2 );
}
QwtPainter::drawLine( painter, tval, y1, tval, y2 );
break;
}
case TopScale:
{
double y1 = pos.y() + a;
double y2 = pos.y() - pw - len + a;
if ( roundingAlignment )
{
y1 = qRound( y1 );
y2 = qRound( y2 );
}
QwtPainter::drawLine( painter, tval, y1, tval, y2 );
break;
}
15 years ago
}
}
/*!
15 years ago
Draws the baseline of the scale
\param painter Painter
\sa drawTick(), drawLabel()
*/
void QwtScaleDraw::drawBackbone( QPainter *painter ) const
15 years ago
{
const bool doAlign = QwtPainter::roundingAlignment( painter );
const QPointF &pos = d_data->pos;
const double len = d_data->len;
const int pw = qMax( penWidth(), 1 );
// pos indicates a border not the center of the backbone line
// so we need to shift its position depending on the pen width
// and the alignment of the scale
double off;
if ( doAlign )
{
if ( alignment() == LeftScale || alignment() == TopScale )
off = ( pw - 1 ) / 2;
else
off = pw / 2;
}
else
{
off = 0.5 * penWidth();
}
switch ( alignment() )
{
case LeftScale:
{
double x = pos.x() - off;
if ( doAlign )
x = qRound( x );
QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + len );
break;
}
case RightScale:
{
double x = pos.x() + off;
if ( doAlign )
x = qRound( x );
QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + len );
break;
}
case TopScale:
{
double y = pos.y() - off;
if ( doAlign )
y = qRound( y );
QwtPainter::drawLine( painter, pos.x(), y, pos.x() + len, y );
break;
}
case BottomScale:
{
double y = pos.y() + off;
if ( doAlign )
y = qRound( y );
QwtPainter::drawLine( painter, pos.x(), y, pos.x() + len, y );
break;
}
15 years ago
}
}
/*!
\brief Move the position of the scale
The meaning of the parameter pos depends on the alignment:
<dl>
<dt>QwtScaleDraw::LeftScale
<dd>The origin is the topmost point of the
backbone. The backbone is a vertical line.
Scale marks and labels are drawn
15 years ago
at the left of the backbone.
<dt>QwtScaleDraw::RightScale
<dd>The origin is the topmost point of the
backbone. The backbone is a vertical line.
15 years ago
Scale marks and labels are drawn
at the right of the backbone.
<dt>QwtScaleDraw::TopScale
<dd>The origin is the leftmost point of the
backbone. The backbone is a horizontal line.
15 years ago
Scale marks and labels are drawn
above the backbone.
<dt>QwtScaleDraw::BottomScale
<dd>The origin is the leftmost point of the
backbone. The backbone is a horizontal line
15 years ago
Scale marks and labels are drawn
below the backbone.
</dl>
\param pos Origin of the scale
\sa pos(), setLength()
*/
void QwtScaleDraw::move( const QPointF &pos )
15 years ago
{
d_data->pos = pos;
updateMap();
}
/*!
15 years ago
\return Origin of the scale
\sa move(), length()
*/
QPointF QwtScaleDraw::pos() const
15 years ago
{
return d_data->pos;
}
/*!
Set the length of the backbone.
15 years ago
The length doesn't include the space needed for
overlapping labels.
\param length Length of the backbone
15 years ago
\sa move(), minLabelDist()
*/
void QwtScaleDraw::setLength( double length )
15 years ago
{
#if 1
15 years ago
if ( length >= 0 && length < 10 )
length = 10;
// why should we accept negative lengths ???
15 years ago
if ( length < 0 && length > -10 )
length = -10;
#else
length = qMax( length, 10 );
#endif
15 years ago
d_data->len = length;
updateMap();
}
/*!
15 years ago
\return the length of the backbone
\sa setLength(), pos()
*/
double QwtScaleDraw::length() const
15 years ago
{
return d_data->len;
}
/*!
15 years ago
Draws the label for a major scale tick
\param painter Painter
\param value Value
\sa drawTick(), drawBackbone(), boundingLabelRect()
*/
void QwtScaleDraw::drawLabel( QPainter *painter, double value ) const
15 years ago
{
QwtText lbl = tickLabel( painter->font(), value );
15 years ago
if ( lbl.isEmpty() )
return;
15 years ago
QPointF pos = labelPosition( value );
15 years ago
QSizeF labelSize = lbl.textSize( painter->font() );
const QTransform transform = labelTransformation( pos, labelSize );
15 years ago
painter->save();
painter->setWorldTransform( transform, true );
lbl.draw ( painter, QRect( QPoint( 0, 0 ), labelSize.toSize() ) );
15 years ago
painter->restore();
}
/*!
\brief Find the bounding rectangle for the label.
The coordinates of the rectangle are absolute ( calculated from pos() ).
15 years ago
in direction of the tick.
\param font Font used for painting
\param value Value
\return Bounding rectangle
15 years ago
\sa labelRect()
*/
QRect QwtScaleDraw::boundingLabelRect( const QFont &font, double value ) const
15 years ago
{
QwtText lbl = tickLabel( font, value );
15 years ago
if ( lbl.isEmpty() )
return QRect();
15 years ago
const QPointF pos = labelPosition( value );
QSizeF labelSize = lbl.textSize( font );
15 years ago
const QTransform transform = labelTransformation( pos, labelSize );
return transform.mapRect( QRect( QPoint( 0, 0 ), labelSize.toSize() ) );
15 years ago
}
/*!
Calculate the transformation that is needed to paint a label
15 years ago
depending on its alignment and rotation.
\param pos Position where to paint the label
\param size Size of the label
\return Transformation matrix
15 years ago
\sa setLabelAlignment(), setLabelRotation()
*/
QTransform QwtScaleDraw::labelTransformation(
const QPointF &pos, const QSizeF &size ) const
{
QTransform transform;
transform.translate( pos.x(), pos.y() );
transform.rotate( labelRotation() );
15 years ago
int flags = labelAlignment();
if ( flags == 0 )
{
switch ( alignment() )
{
case RightScale:
{
if ( flags == 0 )
flags = Qt::AlignRight | Qt::AlignVCenter;
break;
}
case LeftScale:
{
if ( flags == 0 )
flags = Qt::AlignLeft | Qt::AlignVCenter;
break;
}
case BottomScale:
{
if ( flags == 0 )
flags = Qt::AlignHCenter | Qt::AlignBottom;
break;
}
case TopScale:
{
if ( flags == 0 )
flags = Qt::AlignHCenter | Qt::AlignTop;
break;
}
15 years ago
}
}
double x, y;
15 years ago
if ( flags & Qt::AlignLeft )
x = -size.width();
15 years ago
else if ( flags & Qt::AlignRight )
x = 0.0;
15 years ago
else // Qt::AlignHCenter
x = -( 0.5 * size.width() );
15 years ago
if ( flags & Qt::AlignTop )
y = -size.height();
15 years ago
else if ( flags & Qt::AlignBottom )
y = 0;
15 years ago
else // Qt::AlignVCenter
y = -( 0.5 * size.height() );
transform.translate( x, y );
return transform;
}
15 years ago
/*!
Find the bounding rectangle for the label. The coordinates of
the rectangle are relative to spacing + tick length from the backbone
15 years ago
in direction of the tick.
\param font Font used for painting
\param value Value
\return Bounding rectangle that is needed to draw a label
15 years ago
*/
QRectF QwtScaleDraw::labelRect( const QFont &font, double value ) const
{
QwtText lbl = tickLabel( font, value );
15 years ago
if ( lbl.isEmpty() )
return QRectF( 0.0, 0.0, 0.0, 0.0 );
15 years ago
const QPointF pos = labelPosition( value );
15 years ago
const QSizeF labelSize = lbl.textSize( font );
const QTransform transform = labelTransformation( pos, labelSize );
15 years ago
QRectF br = transform.mapRect( QRectF( QPointF( 0, 0 ), labelSize ) );
br.translate( -pos.x(), -pos.y() );
15 years ago
return br;
}
/*!
Calculate the size that is needed to draw a label
\param font Label font
\param value Value
\return Size that is needed to draw a label
15 years ago
*/
QSizeF QwtScaleDraw::labelSize( const QFont &font, double value ) const
15 years ago
{
return labelRect( font, value ).size();
15 years ago
}
/*!
Rotate all labels.
When changing the rotation, it might be necessary to
adjust the label flags too. Finding a useful combination is
often the result of try and error.
\param rotation Angle in degrees. When changing the label rotation,
the label flags often needs to be adjusted too.
\sa setLabelAlignment(), labelRotation(), labelAlignment().
*/
void QwtScaleDraw::setLabelRotation( double rotation )
15 years ago
{
d_data->labelRotation = rotation;
}
/*!
\return the label rotation
\sa setLabelRotation(), labelAlignment()
*/
double QwtScaleDraw::labelRotation() const
{
return d_data->labelRotation;
}
/*!
\brief Change the label flags
Labels are aligned to the point tick length + spacing away from the backbone.
15 years ago
The alignment is relative to the orientation of the label text.
In case of an flags of 0 the label will be aligned
depending on the orientation of the scale:
15 years ago
QwtScaleDraw::TopScale: Qt::AlignHCenter | Qt::AlignTop\n
QwtScaleDraw::BottomScale: Qt::AlignHCenter | Qt::AlignBottom\n
QwtScaleDraw::LeftScale: Qt::AlignLeft | Qt::AlignVCenter\n
QwtScaleDraw::RightScale: Qt::AlignRight | Qt::AlignVCenter\n
15 years ago
Changing the alignment is often necessary for rotated labels.
\param alignment Or'd Qt::AlignmentFlags see <qnamespace.h>
15 years ago
\sa setLabelRotation(), labelRotation(), labelAlignment()
\warning The various alignments might be confusing.
15 years ago
The alignment of the label is not the alignment
of the scale and is not the alignment of the flags
( QwtText::flags() ) returned from QwtAbstractScaleDraw::label().
*/
void QwtScaleDraw::setLabelAlignment( Qt::Alignment alignment )
15 years ago
{
d_data->labelAlignment = alignment;
}
15 years ago
/*!
\return the label flags
\sa setLabelAlignment(), labelRotation()
*/
Qt::Alignment QwtScaleDraw::labelAlignment() const
{
return d_data->labelAlignment;
}
/*!
\param font Font
\return the maximum width of a label
*/
int QwtScaleDraw::maxLabelWidth( const QFont &font ) const
15 years ago
{
double maxWidth = 0.0;
15 years ago
const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
for ( int i = 0; i < ticks.count(); i++ )
{
15 years ago
const double v = ticks[i];
if ( scaleDiv().contains( v ) )
{
const double w = labelSize( font, ticks[i] ).width();
15 years ago
if ( w > maxWidth )
maxWidth = w;
}
}
return qCeil( maxWidth );
15 years ago
}
/*!
\param font Font
\return the maximum height of a label
*/
int QwtScaleDraw::maxLabelHeight( const QFont &font ) const
15 years ago
{
double maxHeight = 0.0;
const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
for ( int i = 0; i < ticks.count(); i++ )
{
15 years ago
const double v = ticks[i];
if ( scaleDiv().contains( v ) )
{
const double h = labelSize( font, ticks[i] ).height();
15 years ago
if ( h > maxHeight )
maxHeight = h;
}
}
return qCeil( maxHeight );
}
15 years ago
void QwtScaleDraw::updateMap()
{
const QPointF pos = d_data->pos;
double len = d_data->len;
15 years ago
QwtScaleMap &sm = scaleMap();
if ( orientation() == Qt::Vertical )
sm.setPaintInterval( pos.y() + len, pos.y() );
15 years ago
else
sm.setPaintInterval( pos.x(), pos.x() + len );
15 years ago
}