# 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     d_data->len = qwtMax(length, 10);
    00592     updateMap();
    00593 }
    00594
    00599 int QwtScaleDraw::length() const
    00600 {
    00601     return d_data->len;
    00602 }
    00603
    00612 void QwtScaleDraw::drawLabel(QPainter *painter, double value) const
    00613 {
    00614     QwtText lbl = tickLabel(painter->font(), value);
    00615     if ( lbl.isEmpty() )
    00616         return;
    00617
    00618     const QPoint pos = labelPosition(value);
    00619
    00620     QSize labelSize = lbl.textSize(painter->font());
    00621     if ( labelSize.height() % 2 )
    00622         labelSize.setHeight(labelSize.height() + 1);
    00623
    00624     const QwtMatrix m = labelMatrix( pos, labelSize);
    00625
    00626     painter->save();
    00627 #if QT_VERSION < 0x040000
    00628     painter->setWorldMatrix(m, true);
    00629 #else
    00630     painter->setMatrix(m, true);
    00631 #endif
    00632
    00633     lbl.draw (painter, QRect(QPoint(0, 0), labelSize) );
    00634     painter->restore();
    00635 }
    00636
    00646 QwtMatrix QwtScaleDraw::labelMatrix(
    00647     const QPoint &pos, const QSize &size) const
    00648 {
    00649     QwtMatrix m;
    00650     m.translate(pos.x(), pos.y());
    00651     m.rotate(labelRotation());
    00652
    00653     int flags = labelAlignment();
    00654     if ( flags == 0 )
    00655     {
    00656         switch(alignment())
    00657         {
    00658             case RightScale:
    00659             {
    00660                 if ( flags == 0 )
    00661                     flags = Qt::AlignRight | Qt::AlignVCenter;
    00662                 break;
    00663             }
    00664             case LeftScale:
    00665             {
    00666                 if ( flags == 0 )
    00667                     flags = Qt::AlignLeft | Qt::AlignVCenter;
    00668                 break;
    00669             }
    00670             case BottomScale:
    00671             {
    00672                 if ( flags == 0 )
    00673                     flags = Qt::AlignHCenter | Qt::AlignBottom;
    00674                 break;
    00675             }
    00676             case TopScale:
    00677             {
    00678                 if ( flags == 0 )
    00679                     flags = Qt::AlignHCenter | Qt::AlignTop;
    00680                 break;
    00681             }
    00682         }
    00683     }
    00684
    00685     const int w = size.width();
    00686     const int h = size.height();
    00687
    00688     int x, y;
    00689
    00690     if ( flags & Qt::AlignLeft )
    00691         x = -w;
    00692     else if ( flags & Qt::AlignRight )
    00693         x = -(w % 2);
    00694     else // Qt::AlignHCenter
    00695         x = -(w / 2);
    00696
    00697     if ( flags & Qt::AlignTop )
    00698         y =  -h ;
    00699     else if ( flags & Qt::AlignBottom )
    00700         y = -(h % 2);
    00701     else // Qt::AlignVCenter
    00702         y = -(h/2);
    00703
    00704     m.translate(x, y);
    00705
    00706     return m;
    00707 }
    00708
    00717 QRect QwtScaleDraw::labelRect(const QFont &font, double value) const
    00718 {
    00719     QwtText lbl = tickLabel(font, value);
    00720     if ( lbl.isEmpty() )
    00721         return QRect(0, 0, 0, 0);
    00722
    00723     const QPoint pos = labelPosition(value);
    00724
    00725     QSize labelSize = lbl.textSize(font);
    00726     if ( labelSize.height() % 2 )
    00727     {
    00728         labelSize.setHeight(labelSize.height() + 1);
    00729     }
    00730
    00731     const QwtMatrix m = labelMatrix(pos, labelSize);
    00732
    00733 #if 0
    00734     QRect br = QwtMetricsMap::translate(m, QRect(QPoint(0, 0), labelSize));
    00735 #else
    00736     QwtPolygon pol(4);
    00737     pol.setPoint(0, 0, 0);
    00738     pol.setPoint(1, 0, labelSize.height() - 1 );
    00739     pol.setPoint(2, labelSize.width() - 1, 0);
    00740     pol.setPoint(3, labelSize.width() - 1, labelSize.height() - 1 );
    00741
    00742     pol = QwtMetricsMap::translate(m, pol);
    00743     QRect br = pol.boundingRect();
    00744 #endif
    00745
    00746 #if QT_VERSION < 0x040000
    00747     br.moveBy(-pos.x(), -pos.y());
    00748 #else
    00749     br.translate(-pos.x(), -pos.y());
    00750 #endif
    00751
    00752     return br;
    00753 }
    00754
    00761 QSize QwtScaleDraw::labelSize(const QFont &font, double value) const
    00762 {
    00763     return labelRect(font, value).size();
    00764 }
    00765
    00779 void QwtScaleDraw::setLabelRotation(double rotation)
    00780 {
    00781     d_data->labelRotation = rotation;
    00782 }
    00783
    00788 double QwtScaleDraw::labelRotation() const
    00789 {
    00790     return d_data->labelRotation;
    00791 }
    00792
    00818 #if QT_VERSION < 0x040000
    00819 void QwtScaleDraw::setLabelAlignment(int alignment)
    00820 #else
    00821 void QwtScaleDraw::setLabelAlignment(Qt::Alignment alignment)
    00822 #endif
    00823 {
    00824     d_data->labelAlignment = alignment;
    00825 }
    00826
    00831 #if QT_VERSION < 0x040000
    00832 int QwtScaleDraw::labelAlignment() const
    00833 #else
    00834 Qt::Alignment QwtScaleDraw::labelAlignment() const
    00835 #endif
    00836 {
    00837     return d_data->labelAlignment;
    00838 }
    00839
    00844 int QwtScaleDraw::maxLabelWidth(const QFont &font) const
    00845 {
    00846     int maxWidth = 0;
    00847
    00848     const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick);
    00849     for (uint i = 0; i < (uint)ticks.count(); i++)
    00850     {
    00851         const double v = ticks[i];
    00852         if ( scaleDiv().contains(v) )
    00853         {
    00854             const int w = labelSize(font, ticks[i]).width();
    00855             if ( w > maxWidth )
    00856                 maxWidth = w;
    00857         }
    00858     }
    00859
    00860     return maxWidth;
    00861 }
    00862
    00867 int QwtScaleDraw::maxLabelHeight(const QFont &font) const
    00868 {
    00869     int maxHeight = 0;
    00870
    00871     const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick);
    00872     for (uint i = 0; i < (uint)ticks.count(); i++)
    00873     {
    00874         const double v = ticks[i];
    00875         if ( scaleDiv().contains(v) )
    00876         {
    00877             const int h = labelSize(font, ticks[i]).height();
    00878             if ( h > maxHeight )
    00879                 maxHeight = h;
    00880         }
    00881     }
    00882
    00883     return maxHeight;
    00884 }
    00885
    00886 void QwtScaleDraw::updateMap()
    00887 {
    00888     QwtScaleMap &sm = scaleMap();
    00889     if ( orientation() == Qt::Vertical )
    00890         sm.setPaintInterval(d_data->pos.y() + d_data->len - 1, d_data->pos.y());
    00891     else
    00892         sm.setPaintInterval(d_data->pos.x(), d_data->pos.x() + d_data->len - 1);
    00893 }