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 }

Generated on Mon Feb 26 21:22:38 2007 for Qwt User's Guide by  doxygen 1.4.6