# qwt_scale_draw.cpp

    00001 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
    00002  * Qwt Widget Library
    00003  * Copyright (C) 1997   Josef Wilgen
    00004  * Copyright (C) 2002   Uwe Rathmann
    00005  *
    00006  * This library is free software; you can redistribute it and/or
    00007  * modify it under the terms of the Qwt License, Version 1.0
    00008  *****************************************************************************/
    00009
    00010 // vim: expandtab
    00011
    00012 #include <qpen.h>
    00013 #include <qpainter.h>
    00014 #include "qwt_math.h"
    00015 #include "qwt_painter.h"
    00016 #include "qwt_polygon.h"
    00017 #include "qwt_scale_div.h"
    00018 #include "qwt_scale_map.h"
    00019 #include "qwt_scale_draw.h"
    00020
    00021 #if QT_VERSION < 0x040000
    00022 #include <qwmatrix.h>
    00023 #define QwtMatrix QWMatrix
    00024 #else
    00025 #include <qmatrix.h>
    00026 #define QwtMatrix QMatrix
    00027 #endif
    00028
    00029 class QwtScaleDraw::PrivateData
    00030 {
    00031 public:
    00032     PrivateData():
    00033         len(0),
    00034         alignment(QwtScaleDraw::BottomScale),
    00035         labelAlignment(0),
    00036         labelRotation(0.0)
    00037     {
    00038     }
    00039
    00040     QPoint pos;
    00041     int len;
    00042
    00043     Alignment alignment;
    00044
    00045 #if QT_VERSION < 0x040000
    00046     int labelAlignment;
    00047 #else
    00048     Qt::Alignment labelAlignment;
    00049 #endif
    00050     double labelRotation;
    00051 };
    00052
    00060 QwtScaleDraw::QwtScaleDraw()
    00061 {
    00062     d_data = new QwtScaleDraw::PrivateData;
    00063     setLength(100);
    00064 }
    00065
    00067 QwtScaleDraw::QwtScaleDraw(const QwtScaleDraw &other):
    00068     QwtAbstractScaleDraw(other)
    00069 {
    00070     d_data = new QwtScaleDraw::PrivateData(*other.d_data);
    00071 }
    00072
    00074 QwtScaleDraw::~QwtScaleDraw()
    00075 {
    00076     delete d_data;
    00077 }
    00078
    00080 QwtScaleDraw &QwtScaleDraw::operator=(const QwtScaleDraw &other)
    00081 {
    00082     *(QwtAbstractScaleDraw*)this = (const QwtAbstractScaleDraw &)other;
    00083     *d_data = *other.d_data;
    00084     return *this;
    00085 }
    00086
    00091 QwtScaleDraw::Alignment QwtScaleDraw::alignment() const
    00092 {
    00093     return d_data->alignment;
    00094 }
    00095
    00102 void QwtScaleDraw::setAlignment(Alignment align)
    00103 {
    00104     d_data->alignment = align;
    00105 }
    00106
    00115 Qt::Orientation QwtScaleDraw::orientation() const
    00116 {
    00117     switch(d_data->alignment)
    00118     {
    00119         case TopScale:
    00120         case BottomScale:
    00121             return Qt::Horizontal;
    00122         case LeftScale:
    00123         case RightScale:
    00124         default:
    00125             return Qt::Vertical;
    00126     }
    00127 }
    00128
    00139 void QwtScaleDraw::getBorderDistHint(const QFont &font,
    00140     int &start, int &end ) const
    00141 {
    00142     start = 0;
    00143     end = 0;
    00144
    00145     if ( !hasComponent(QwtAbstractScaleDraw::Labels) )
    00146         return;
    00147
    00148     const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick);
    00149     if ( ticks.count() == 0 )
    00150         return;
    00151
    00152     QRect lr = labelRect(font, ticks[0]);
    00153
    00154     // find the distance between tick and border
    00155     int off = qwtAbs(map().transform(ticks[0]) - qRound(map().p1()));
    00156
    00157     if ( orientation() == Qt::Vertical )
    00158         end = lr.bottom() + 1 - off;
    00159     else
    00160         start = -lr.left() - off;
    00161
    00162     const int lastTick = ticks.count() - 1;
    00163     lr = labelRect(font, ticks[lastTick]);
    00164
    00165     // find the distance between tick and border
    00166     off = qwtAbs(map().transform(ticks[lastTick]) - qRound(map().p2()));
    00167
    00168     if ( orientation() == Qt::Vertical )
    00169         start = -lr.top() - off;
    00170     else
    00171         end = lr.right() + 1 - off;
    00172
    00173     // if the distance between tick and border is larger
    00174     // than half of the label width/height, we set to 0
    00175
    00176     if ( start < 0 )
    00177         start = 0;
    00178     if ( end < 0 )
    00179         end = 0;
    00180 }
    00181
    00192 int QwtScaleDraw::minLabelDist(const QFont &font) const
    00193 {
    00194     if ( !hasComponent(QwtAbstractScaleDraw::Labels) )
    00195         return 0;
    00196
    00197     const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick);
    00198     if (ticks.count() == 0)
    00199         return 0;
    00200
    00201     const QFontMetrics fm(font);
    00202
    00203     const bool vertical = (orientation() == Qt::Vertical);
    00204
    00205     QRect bRect1;
    00206     QRect bRect2 = labelRect(font, ticks[0]);
    00207     if ( vertical )
    00208     {
    00209         bRect2.setRect(-bRect2.bottom(), 0, bRect2.height(), bRect2.width());
    00210     }
    00211     int maxDist = 0;
    00212
    00213     for (uint i = 1; i < (uint)ticks.count(); i++ )
    00214     {
    00215         bRect1 = bRect2;
    00216         bRect2 = labelRect(font, ticks[i]);
    00217         if ( vertical )
    00218         {
    00219             bRect2.setRect(-bRect2.bottom(), 0,
    00220                 bRect2.height(), bRect2.width());
    00221         }
    00222
    00223         int dist = fm.leading(); // space between the labels
    00224         if ( bRect1.right() > 0 )
    00225             dist += bRect1.right();
    00226         if ( bRect2.left() < 0 )
    00227             dist += -bRect2.left();
    00228
    00229         if ( dist > maxDist )
    00230             maxDist = dist;
    00231     }
    00232
    00233     double angle = labelRotation() / 180.0 * M_PI;
    00234     if ( vertical )
    00235         angle += M_PI / 2;
    00236
    00237     if ( sin(angle) == 0.0 )
    00238         return maxDist;
    00239
    00240     const int fmHeight = fm.ascent() - 2;
    00241
    00242     // The distance we need until there is
    00243     // the height of the label font. This height is needed
    00244     // for the neighbour labal.
    00245
    00246     int labelDist = (int)(fmHeight / sin(angle) * cos(angle));
    00247     if ( labelDist < 0 )
    00248         labelDist = -labelDist;
    00249
    00250     // The cast above floored labelDist. We want to ceil.
    00251     labelDist++;
    00252
    00253     // For text orientations close to the scale orientation
    00254
    00255     if ( labelDist > maxDist )
    00256         labelDist = maxDist;
    00257
    00258     // For text orientations close to the opposite of the
    00259     // scale orientation
    00260
    00261     if ( labelDist < fmHeight )
    00262         labelDist = fmHeight;
    00263
    00264     return labelDist;
    00265 }
    00266
    00280 int QwtScaleDraw::extent(const QPen &pen, const QFont &font) const
    00281 {
    00282     int d = 0;
    00283
    00284     if ( hasComponent(QwtAbstractScaleDraw::Labels) )
    00285     {
    00286         if ( orientation() == Qt::Vertical )
    00287             d = maxLabelWidth(font);
    00288         else
    00289             d = maxLabelHeight(font);
    00290
    00291         if ( d > 0 )
    00292             d += spacing();
    00293     }
    00294
    00295     if ( hasComponent(QwtAbstractScaleDraw::Ticks) )
    00296     {
    00297         d += majTickLength();
    00298     }
    00299
    00300     if ( hasComponent(QwtAbstractScaleDraw::Backbone) )
    00301     {
    00302         const int pw = qwtMax( 1, pen.width() );  // penwidth can be zero
    00303         d += pw;
    00304     }
    00305
    00306     d = qwtMax(d, minimumExtent());
    00307     return d;
    00308 }
    00309
    00318 int QwtScaleDraw::minLength(const QPen &pen, const QFont &font) const
    00319 {
    00320     int startDist, endDist;
    00321     getBorderDistHint(font, startDist, endDist);
    00322
    00323     const QwtScaleDiv &sd = scaleDiv();
    00324
    00325     const uint minorCount =
    00326         sd.ticks(QwtScaleDiv::MinorTick).count() +
    00327         sd.ticks(QwtScaleDiv::MediumTick).count();
    00328     const uint majorCount =
    00329         sd.ticks(QwtScaleDiv::MajorTick).count();
    00330
    00331     int lengthForLabels = 0;
    00332     if ( hasComponent(QwtAbstractScaleDraw::Labels) )
    00333     {
    00334         if ( majorCount >= 2 )
    00335             lengthForLabels = minLabelDist(font) * (majorCount - 1);
    00336     }
    00337
    00338     int lengthForTicks = 0;
    00339     if ( hasComponent(QwtAbstractScaleDraw::Ticks) )
    00340     {
    00341         const int pw = qwtMax( 1, pen.width() );  // penwidth can be zero
    00342         lengthForTicks = 2 * (majorCount + minorCount) * pw;
    00343     }
    00344
    00345     return startDist + endDist + qwtMax(lengthForLabels, lengthForTicks);
    00346 }
    00347
    00356 QPoint QwtScaleDraw::labelPosition( double value) const
    00357 {
    00358     const int tval = map().transform(value);
    00359     int dist = spacing() + 1;
    00360     if ( hasComponent(QwtAbstractScaleDraw::Ticks) )
    00361         dist += majTickLength();
    00362
    00363     int px = 0;
    00364     int py = 0;
    00365
    00366     switch(alignment())
    00367     {
    00368         case RightScale:
    00369         {
    00370             px = d_data->pos.x() + dist;
    00371             py = tval;
    00372             break;
    00373         }
    00374         case LeftScale:
    00375         {
    00376             px = d_data->pos.x() - dist;
    00377             py = tval;
    00378             break;
    00379         }
    00380         case BottomScale:
    00381         {
    00382             px = tval;
    00383             py = d_data->pos.y() + dist;
    00384             break;
    00385         }
    00386         case TopScale:
    00387         {
    00388             px = tval;
    00389             py = d_data->pos.y() - dist;
    00390             break;
    00391         }
    00392     }
    00393
    00394     return QPoint(px, py);
    00395 }
    00396
    00406 void QwtScaleDraw::drawTick(QPainter *painter, double value, int len) const
    00407 {
    00408     if ( len <= 0 )
    00409         return;
    00410
    00411     int pw2 = qwtMin((int)painter->pen().width(), len) / 2;
    00412
    00413     QwtScaleMap scaleMap = map();
    00414     const QwtMetricsMap metricsMap = QwtPainter::metricsMap();
    00415     QPoint pos = d_data->pos;
    00416
    00417     if ( !metricsMap.isIdentity() )
    00418     {
    00419         /*
    00420            The perfect position of the ticks is important.
    00421            To avoid rounding errors we have to use
    00422            device coordinates.
    00423          */
    00424         QwtPainter::resetMetricsMap();
    00425
    00426         pos = metricsMap.layoutToDevice(pos);
    00427
    00428         if ( orientation() == Qt::Vertical )
    00429         {
    00430             scaleMap.setPaintInterval(
    00431                 metricsMap.layoutToDeviceY((int)scaleMap.p1()),
    00432                 metricsMap.layoutToDeviceY((int)scaleMap.p2())
    00433             );
    00434             len = metricsMap.layoutToDeviceX(len);
    00435         }
    00436         else
    00437         {
    00438             scaleMap.setPaintInterval(
    00439                 metricsMap.layoutToDeviceX((int)scaleMap.p1()),
    00440                 metricsMap.layoutToDeviceX((int)scaleMap.p2())
    00441             );
    00442             len = metricsMap.layoutToDeviceY(len);
    00443         }
    00444     }
    00445
    00446     const int tval = scaleMap.transform(value);
    00447
    00448     switch(alignment())
    00449     {
    00450         case LeftScale:
    00451         {
    00452 #if QT_VERSION < 0x040000
    00453             QwtPainter::drawLine(painter, pos.x() + pw2, tval,
    00454                 pos.x() - len - 2 * pw2, tval);
    00455 #else
    00456             QwtPainter::drawLine(painter, pos.x() - pw2, tval,
    00457                 pos.x() - len, tval);
    00458 #endif
    00459             break;
    00460         }
    00461
    00462         case RightScale:
    00463         {
    00464 #if QT_VERSION < 0x040000
    00465             QwtPainter::drawLine(painter, pos.x(), tval,
    00466                 pos.x() + len + pw2, tval);
    00467 #else
    00468             QwtPainter::drawLine(painter, pos.x() + pw2, tval,
    00469                 pos.x() + len, tval);
    00470 #endif
    00471             break;
    00472         }
    00473
    00474         case BottomScale:
    00475         {
    00476 #if QT_VERSION < 0x040000
    00477             QwtPainter::drawLine(painter, tval, pos.y(),
    00478                 tval, pos.y() + len + 2 * pw2);
    00479 #else
    00480             QwtPainter::drawLine(painter, tval, pos.y() + pw2,
    00481                 tval, pos.y() + len);
    00482 #endif
    00483             break;
    00484         }
    00485
    00486         case TopScale:
    00487         {
    00488 #if QT_VERSION < 0x040000
    00489             QwtPainter::drawLine(painter, tval, pos.y() + pw2,
    00490                 tval, pos.y() - len - 2 * pw2);
    00491 #else
    00492             QwtPainter::drawLine(painter, tval, pos.y() - pw2,
    00493                 tval, pos.y() - len);
    00494 #endif
    00495             break;
    00496         }
    00497     }
    00498     QwtPainter::setMetricsMap(metricsMap); // restore metrics map
    00499 }
    00500
    00507 void QwtScaleDraw::drawBackbone(QPainter *painter) const
    00508 {
    00509     const int bw2 = painter->pen().width() / 2;
    00510
    00511     const QPoint &pos = d_data->pos;
    00512     const int len = d_data->len - 1;
    00513
    00514     switch(alignment())
    00515     {
    00516         case LeftScale:
    00517             QwtPainter::drawLine(painter, pos.x() - bw2,
    00518                 pos.y(), pos.x() - bw2, pos.y() + len );
    00519             break;
    00520         case RightScale:
    00521             QwtPainter::drawLine(painter, pos.x() + bw2,
    00522                 pos.y(), pos.x() + bw2, pos.y() + len);
    00523             break;
    00524         case TopScale:
    00525             QwtPainter::drawLine(painter, pos.x(), pos.y() - bw2,
    00526                 pos.x() + len, pos.y() - bw2);
    00527             break;
    00528         case BottomScale:
    00529             QwtPainter::drawLine(painter, pos.x(), pos.y() + bw2,
    00530                 pos.x() + len, pos.y() + bw2);
    00531             break;
    00532     }
    00533 }
    00534
    00566 void QwtScaleDraw::move(const QPoint &pos)
    00567 {
    00568     d_data->pos = pos;
    00569     updateMap();
    00570 }
    00571
    00576 QPoint QwtScaleDraw::pos() const
    00577 {
    00578     return d_data->pos;
    00579 }
    00580
    00589 void QwtScaleDraw::setLength(int length)
    00590 {
    00591     if ( length >= 0 && length < 10 )
    00592         length = 10;
    00593     if ( length < 0 && length > -10 )
    00594         length = -10;
    00595
    00596     d_data->len = length;
    00597     updateMap();
    00598 }
    00599
    00604 int QwtScaleDraw::length() const
    00605 {
    00606     return d_data->len;
    00607 }
    00608
    00617 void QwtScaleDraw::drawLabel(QPainter *painter, double value) const
    00618 {
    00619     QwtText lbl = tickLabel(painter->font(), value);
    00620     if ( lbl.isEmpty() )
    00621         return;
    00622
    00623     const QPoint pos = labelPosition(value);
    00624
    00625     QSize labelSize = lbl.textSize(painter->font());
    00626     if ( labelSize.height() % 2 )
    00627         labelSize.setHeight(labelSize.height() + 1);
    00628
    00629     const QwtMatrix m = labelMatrix( pos, labelSize);
    00630
    00631     painter->save();
    00632 #if QT_VERSION < 0x040000
    00633     painter->setWorldMatrix(m, true);
    00634 #else
    00635     painter->setMatrix(m, true);
    00636 #endif
    00637
    00638     lbl.draw (painter, QRect(QPoint(0, 0), labelSize) );
    00639     painter->restore();
    00640 }
    00641
    00652 QRect QwtScaleDraw::boundingLabelRect(const QFont &font, double value) const
    00653 {
    00654     QwtText lbl = tickLabel(font, value);
    00655     if ( lbl.isEmpty() )
    00656         return QRect();
    00657
    00658     const QPoint pos = labelPosition(value);
    00659     QSize labelSize = lbl.textSize(font);
    00660     if ( labelSize.height() % 2 )
    00661         labelSize.setHeight(labelSize.height() + 1);
    00662
    00663     const QwtMatrix m = labelMatrix( pos, labelSize);
    00664     return m.mapRect(QRect(QPoint(0, 0), labelSize));
    00665 }
    00666
    00676 QwtMatrix QwtScaleDraw::labelMatrix(
    00677     const QPoint &pos, const QSize &size) const
    00678 {
    00679     QwtMatrix m;
    00680     m.translate(pos.x(), pos.y());
    00681     m.rotate(labelRotation());
    00682
    00683     int flags = labelAlignment();
    00684     if ( flags == 0 )
    00685     {
    00686         switch(alignment())
    00687         {
    00688             case RightScale:
    00689             {
    00690                 if ( flags == 0 )
    00691                     flags = Qt::AlignRight | Qt::AlignVCenter;
    00692                 break;
    00693             }
    00694             case LeftScale:
    00695             {
    00696                 if ( flags == 0 )
    00697                     flags = Qt::AlignLeft | Qt::AlignVCenter;
    00698                 break;
    00699             }
    00700             case BottomScale:
    00701             {
    00702                 if ( flags == 0 )
    00703                     flags = Qt::AlignHCenter | Qt::AlignBottom;
    00704                 break;
    00705             }
    00706             case TopScale:
    00707             {
    00708                 if ( flags == 0 )
    00709                     flags = Qt::AlignHCenter | Qt::AlignTop;
    00710                 break;
    00711             }
    00712         }
    00713     }
    00714
    00715     const int w = size.width();
    00716     const int h = size.height();
    00717
    00718     int x, y;
    00719
    00720     if ( flags & Qt::AlignLeft )
    00721         x = -w;
    00722     else if ( flags & Qt::AlignRight )
    00723         x = -(w % 2);
    00724     else // Qt::AlignHCenter
    00725         x = -(w / 2);
    00726
    00727     if ( flags & Qt::AlignTop )
    00728         y =  -h ;
    00729     else if ( flags & Qt::AlignBottom )
    00730         y = -(h % 2);
    00731     else // Qt::AlignVCenter
    00732         y = -(h/2);
    00733
    00734     m.translate(x, y);
    00735
    00736     return m;
    00737 }
    00738
    00747 QRect QwtScaleDraw::labelRect(const QFont &font, double value) const
    00748 {
    00749     QwtText lbl = tickLabel(font, value);
    00750     if ( lbl.isEmpty() )
    00751         return QRect(0, 0, 0, 0);
    00752
    00753     const QPoint pos = labelPosition(value);
    00754
    00755     QSize labelSize = lbl.textSize(font);
    00756     if ( labelSize.height() % 2 )
    00757     {
    00758         labelSize.setHeight(labelSize.height() + 1);
    00759     }
    00760
    00761     const QwtMatrix m = labelMatrix(pos, labelSize);
    00762
    00763 #if 0
    00764     QRect br = QwtMetricsMap::translate(m, QRect(QPoint(0, 0), labelSize));
    00765 #else
    00766     QwtPolygon pol(4);
    00767     pol.setPoint(0, 0, 0);
    00768     pol.setPoint(1, 0, labelSize.height() - 1 );
    00769     pol.setPoint(2, labelSize.width() - 1, 0);
    00770     pol.setPoint(3, labelSize.width() - 1, labelSize.height() - 1 );
    00771
    00772     pol = QwtMetricsMap::translate(m, pol);
    00773     QRect br = pol.boundingRect();
    00774 #endif
    00775
    00776 #if QT_VERSION < 0x040000
    00777     br.moveBy(-pos.x(), -pos.y());
    00778 #else
    00779     br.translate(-pos.x(), -pos.y());
    00780 #endif
    00781
    00782     return br;
    00783 }
    00784
    00791 QSize QwtScaleDraw::labelSize(const QFont &font, double value) const
    00792 {
    00793     return labelRect(font, value).size();
    00794 }
    00795
    00809 void QwtScaleDraw::setLabelRotation(double rotation)
    00810 {
    00811     d_data->labelRotation = rotation;
    00812 }
    00813
    00818 double QwtScaleDraw::labelRotation() const
    00819 {
    00820     return d_data->labelRotation;
    00821 }
    00822
    00848 #if QT_VERSION < 0x040000
    00849 void QwtScaleDraw::setLabelAlignment(int alignment)
    00850 #else
    00851 void QwtScaleDraw::setLabelAlignment(Qt::Alignment alignment)
    00852 #endif
    00853 {
    00854     d_data->labelAlignment = alignment;
    00855 }
    00856
    00861 #if QT_VERSION < 0x040000
    00862 int QwtScaleDraw::labelAlignment() const
    00863 #else
    00864 Qt::Alignment QwtScaleDraw::labelAlignment() const
    00865 #endif
    00866 {
    00867     return d_data->labelAlignment;
    00868 }
    00869
    00874 int QwtScaleDraw::maxLabelWidth(const QFont &font) const
    00875 {
    00876     int maxWidth = 0;
    00877
    00878     const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick);
    00879     for (uint i = 0; i < (uint)ticks.count(); i++)
    00880     {
    00881         const double v = ticks[i];
    00882         if ( scaleDiv().contains(v) )
    00883         {
    00884             const int w = labelSize(font, ticks[i]).width();
    00885             if ( w > maxWidth )
    00886                 maxWidth = w;
    00887         }
    00888     }
    00889
    00890     return maxWidth;
    00891 }
    00892
    00897 int QwtScaleDraw::maxLabelHeight(const QFont &font) const
    00898 {
    00899     int maxHeight = 0;
    00900
    00901     const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick);
    00902     for (uint i = 0; i < (uint)ticks.count(); i++)
    00903     {
    00904         const double v = ticks[i];
    00905         if ( scaleDiv().contains(v) )
    00906         {
    00907             const int h = labelSize(font, ticks[i]).height();
    00908             if ( h > maxHeight )
    00909                 maxHeight = h;
    00910         }
    00911     }
    00912
    00913     return maxHeight;
    00914 }
    00915
    00916 void QwtScaleDraw::updateMap()
    00917 {
    00918     QwtScaleMap &sm = scaleMap();
    00919     if ( orientation() == Qt::Vertical )
    00920         sm.setPaintInterval(d_data->pos.y() + d_data->len, d_data->pos.y());
    00921     else
    00922         sm.setPaintInterval(d_data->pos.x(), d_data->pos.x() + d_data->len);
    00923 }