1 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2  * Qwt Widget Library
3  * Copyright (C) 1997   Josef Wilgen
4  * Copyright (C) 2002   Uwe Rathmann
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the Qwt License, Version 1.0
8  *****************************************************************************/
9 
10 // vim: expandtab
11 
12 #include <math.h>
13 #include <qpen.h>
14 #include <qpainter.h>
15 #include <qfontmetrics.h>
16 #include "qwt_painter.h"
17 #include "qwt_scale_div.h"
18 #include "qwt_scale_map.h"
19 #include "qwt_round_scale_draw.h"
20 
21 class QwtRoundScaleDraw::PrivateData
22 {
23 public:
PrivateData()24     PrivateData():
25         center(50, 50),
26         radius(50),
27         startAngle(-135 * 16),
28         endAngle(135 * 16)
29     {
30     }
31 
32     QPoint center;
33     int radius;
34 
35     int startAngle;
36     int endAngle;
37 };
38 
39 /*!
40   \brief Constructor
41 
42   The range of the scale is initialized to [0, 100],
43   The center is set to (50, 50) with a radius of 50.
44   The angle range is set to [-135, 135].
45 */
QwtRoundScaleDraw()46 QwtRoundScaleDraw::QwtRoundScaleDraw()
47 {
48     d_data = new QwtRoundScaleDraw::PrivateData;
49 
50     setRadius(50);
51     scaleMap().setPaintInterval(d_data->startAngle, d_data->endAngle);
52 }
53 
54 //! Copy constructor
QwtRoundScaleDraw(const QwtRoundScaleDraw & other)55 QwtRoundScaleDraw::QwtRoundScaleDraw(const QwtRoundScaleDraw &other):
56     QwtAbstractScaleDraw(other)
57 {
58     d_data = new QwtRoundScaleDraw::PrivateData(*other.d_data);
59 }
60 
61 
62 //! Destructor
~QwtRoundScaleDraw()63 QwtRoundScaleDraw::~QwtRoundScaleDraw()
64 {
65     delete d_data;
66 }
67 
68 //! Assignment operator
operator =(const QwtRoundScaleDraw & other)69 QwtRoundScaleDraw &QwtRoundScaleDraw::operator=(const QwtRoundScaleDraw &other)
70 {
71     *(QwtAbstractScaleDraw*)this = (const QwtAbstractScaleDraw &)other;
72     *d_data = *other.d_data;
73     return *this;
74 }
75 
76 /*!
77   Change of radius the scale
78 
79   Radius is the radius of the backbone without ticks and labels.
80 
81   \param radius New Radius
82   \sa moveCenter()
83 */
setRadius(int radius)84 void QwtRoundScaleDraw::setRadius(int radius)
85 {
86     d_data->radius = radius;
87 }
88 
89 /*!
90   Get the radius
91 
92   Radius is the radius of the backbone without ticks and labels.
93 
94   \sa setRadius(), extent()
95 */
radius() const96 int QwtRoundScaleDraw::radius() const
97 {
98     return d_data->radius;
99 }
100 
101 /*!
102    Move the center of the scale draw, leaving the radius unchanged
103 
104    \param center New center
105    \sa setRadius()
106 */
moveCenter(const QPoint & center)107 void QwtRoundScaleDraw::moveCenter(const QPoint &center)
108 {
109     d_data->center = center;
110 }
111 
112 //! Get the center of the scale
center() const113 QPoint QwtRoundScaleDraw::center() const
114 {
115     return d_data->center;
116 }
117 
118 /*!
119   \brief Adjust the baseline circle segment for round scales.
120 
121   The baseline will be drawn from min(angle1,angle2) to max(angle1, angle2).
122   The default setting is [ -135, 135 ].
123   An angle of 0 degrees corresponds to the 12 o'clock position,
124   and positive angles count in a clockwise direction.
125   \param angle1
126   \param angle2 boundaries of the angle interval in degrees.
127   \warning <ul>
128   <li>The angle range is limited to [-360, 360] degrees. Angles exceeding
129       this range will be clipped.
130   <li>For angles more than 359 degrees above or below min(angle1, angle2),
131       scale marks will not be drawn.
132   <li>If you need a counterclockwise scale, use QwtScaleDiv::setRange
133   </ul>
134 */
setAngleRange(double angle1,double angle2)135 void QwtRoundScaleDraw::setAngleRange(double angle1, double angle2)
136 {
137     angle1 = qwtLim(angle1, -360.0, 360.0);
138     angle2 = qwtLim(angle2, -360.0, 360.0);
139 
140     d_data->startAngle = qRound(angle1 * 16.0);
141     d_data->endAngle = qRound(angle2 * 16.0);
142 
143     if (d_data->startAngle == d_data->endAngle)
144     {
145         d_data->startAngle -= 1;
146         d_data->endAngle += 1;
147     }
148 
149     scaleMap().setPaintInterval(d_data->startAngle, d_data->endAngle);
150 }
151 
152 /*!
153    Draws the label for a major scale tick
154 
155    \param painter Painter
156    \param value Value
157 
158    \sa drawTick(), drawBackbone()
159 */
drawLabel(QPainter * painter,double value) const160 void QwtRoundScaleDraw::drawLabel(QPainter *painter, double value) const
161 {
162     const QwtText label = tickLabel(painter->font(), value);
163     if ( label.isEmpty() )
164         return;
165 
166     const int tval = map().transform(value);
167     if ((tval > d_data->startAngle + 359 * 16)
168         || (tval < d_data->startAngle - 359 * 16))
169     {
170        return;
171     }
172 
173     double radius = d_data->radius;
174     if ( hasComponent(QwtAbstractScaleDraw::Ticks) ||
175         hasComponent(QwtAbstractScaleDraw::Backbone) )
176     {
177         radius += spacing();
178     }
179 
180     if ( hasComponent(QwtAbstractScaleDraw::Ticks) )
181         radius += majTickLength();
182 
183     const QSize sz = label.textSize(painter->font());
184     const double arc = tval / 16.0 / 360.0 * 2 * M_PI;
185 
186     const int x = d_data->center.x() +
187         qRound((radius + sz.width() / 2.0) * sin(arc));
188     const int y = d_data->center.y() -
189         qRound( (radius + sz.height() / 2.0) * cos(arc));
190 
191     const QRect r(x - sz.width() / 2, y - sz.height() / 2,
192         sz.width(), sz.height() );
193     label.draw(painter, r);
194 }
195 
196 /*!
197    Draw a tick
198 
199    \param painter Painter
200    \param value Value of the tick
201    \param len Lenght of the tick
202 
203    \sa drawBackbone(), drawLabel()
204 */
drawTick(QPainter * painter,double value,int len) const205 void QwtRoundScaleDraw::drawTick(QPainter *painter, double value, int len) const
206 {
207     if ( len <= 0 )
208         return;
209 
210     const int tval = map().transform(value);
211 
212     const int cx = d_data->center.x();
213     const int cy = d_data->center.y();
214     const int radius = d_data->radius;
215 
216     if ((tval <= d_data->startAngle + 359 * 16)
217         || (tval >= d_data->startAngle - 359 * 16))
218     {
219         const double arc = double(tval) / 16.0 * M_PI / 180.0;
220 
221         const double sinArc = sin(arc);
222         const double cosArc = cos(arc);
223 
224         const int x1 = qRound( cx + radius * sinArc );
225         const int x2 = qRound( cx + (radius + len) * sinArc );
226         const int y1 = qRound( cy - radius * cosArc );
227         const int y2 = qRound( cy - (radius + len) * cosArc );
228 
229         QwtPainter::drawLine(painter, x1, y1, x2, y2);
230     }
231 }
232 
233 /*!
234    Draws the baseline of the scale
235    \param painter Painter
236 
237    \sa drawTick(), drawLabel()
238 */
drawBackbone(QPainter * painter) const239 void QwtRoundScaleDraw::drawBackbone(QPainter *painter) const
240 {
241     const int a1 = qRound(qwtMin(map().p1(), map().p2()) - 90 * 16);
242     const int a2 = qRound(qwtMax(map().p1(), map().p2()) - 90 * 16);
243 
244     const int radius = d_data->radius;
245     const int x = d_data->center.x() - radius;
246     const int y = d_data->center.y() - radius;
247 
248     painter->drawArc(x, y, 2 * radius, 2 * radius,
249         -a2, a2 - a1 + 1);           // counterclockwise
250 }
251 
252 /*!
253    Calculate the extent of the scale
254 
255    The extent is the distcance between the baseline to the outermost
256    pixel of the scale draw. radius() + extent() is an upper limit
257    for the radius of the bounding circle.
258 
259    \param pen Pen that is used for painting backbone and ticks
260    \param font Font used for painting the labels
261 
262    \sa setMinimumExtent(), minimumExtent()
263    \warning The implemented algo is not too smart and
264             calculates only an upper limit, that might be a
265             few pixels too large
266 */
extent(const QPen & pen,const QFont & font) const267 int QwtRoundScaleDraw::extent(const QPen &pen, const QFont &font) const
268 {
269     int d = 0;
270 
271     if ( hasComponent(QwtAbstractScaleDraw::Labels) )
272     {
273         const QwtScaleDiv &sd = scaleDiv();
274         const QwtValueList &ticks = sd.ticks(QwtScaleDiv::MajorTick);
275         for (uint i = 0; i < (uint)ticks.count(); i++)
276         {
277             const double value = ticks[i];
278             if ( !sd.contains(value) )
279                 continue;
280 
281             const QwtText label = tickLabel(font, value);
282             if ( label.isEmpty() )
283                 continue;
284 
285             const int tval = map().transform(value);
286             if ((tval < d_data->startAngle + 360 * 16)
287                 && (tval > d_data->startAngle - 360 * 16))
288             {
289                 const double arc = tval / 16.0 / 360.0 * 2 * M_PI;
290 
291                 const QSize sz = label.textSize(font);
292                 const double off = qwtMax(sz.width(), sz.height());
293 
294                 double x = off * sin(arc);
295                 double y = off * cos(arc);
296 
297                 const int dist = (int)ceil(sqrt(x * x + y * y) + 1 );
298                 if ( dist > d )
299                     d = dist;
300             }
301         }
302     }
303 
304     if ( hasComponent(QwtAbstractScaleDraw::Ticks) )
305     {
306         d += majTickLength();
307     }
308 
309     if ( hasComponent(QwtAbstractScaleDraw::Backbone) )
310     {
311         const int pw = qwtMax( 1, pen.width() );  // penwidth can be zero
312         d += pw;
313     }
314 
315     if ( hasComponent(QwtAbstractScaleDraw::Labels) &&
316         ( hasComponent(QwtAbstractScaleDraw::Ticks) ||
317             hasComponent(QwtAbstractScaleDraw::Backbone) ) )
318     {
319         d += spacing();
320     }
321 
322     d = qwtMax(d, minimumExtent());
323 
324     return d;
325 }
326