File indexing completed on 2024-04-28 05:31:46

0001 /*
0002     SPDX-FileCopyrightText: 2006-2009 John Tapsell <tapsell@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 
0006 */
0007 
0008 #ifdef GRAPHICS_SIGNAL_PLOTTER
0009 #define KSignalPlotter KGraphicsSignalPlotter
0010 #define KSignalPlotterPrivate KGraphicsSignalPlotterPrivate
0011 #include "kgraphicssignalplotter.h"
0012 #else
0013 #undef KSignalPlotter
0014 #undef KSignalPlotterPrivate
0015 #include "ksignalplotter.h"
0016 #endif
0017 
0018 #include "ksignalplotter_debug.h"
0019 #include "ksignalplotter_p.h"
0020 
0021 #include <QDebug>
0022 #include <QEvent>
0023 #include <QPaintEvent>
0024 #include <QPainter>
0025 #include <QPainterPath>
0026 #include <QPixmap>
0027 
0028 #ifdef GRAPHICS_SIGNAL_PLOTTER
0029 #include <QGraphicsSceneResizeEvent>
0030 #include <QStyleOptionGraphicsItem>
0031 #endif
0032 
0033 #include <cmath>
0034 #include <kiconloader.h>
0035 #include <limits>
0036 
0037 #ifdef SVG_SUPPORT
0038 #include <Plasma/Svg>
0039 #include <Plasma/Theme>
0040 #endif
0041 
0042 #define VERTICAL_LINE_OFFSET 1
0043 // Never store less 1000 samples if not visible.  This is kinda arbitrary
0044 #define NUM_SAMPLES_WHEN_INVISIBLE ((uint)1000)
0045 
0046 #ifdef GRAPHICS_SIGNAL_PLOTTER
0047 KGraphicsSignalPlotter::KGraphicsSignalPlotter(QGraphicsItem *parent)
0048     : QGraphicsWidget(parent)
0049     , d(new KGraphicsSignalPlotterPrivate(this))
0050 #else
0051 KSignalPlotter::KSignalPlotter(QWidget *parent)
0052     : QWidget(parent)
0053     , d(new KSignalPlotterPrivate(this))
0054 #endif
0055 {
0056     qRegisterMetaType<KLocalizedString>("KLocalizedString");
0057     // Anything smaller than this does not make sense.
0058     setMinimumSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall);
0059     QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0060     sizePolicy.setHeightForWidth(false);
0061     setSizePolicy(sizePolicy);
0062 
0063 #ifdef GRAPHICS_SIGNAL_PLOTTER
0064     setFlag(QGraphicsItem::ItemClipsToShape);
0065 #endif
0066 }
0067 
0068 KSignalPlotter::~KSignalPlotter()
0069 {
0070     delete d;
0071 }
0072 
0073 KLocalizedString KSignalPlotter::unit() const
0074 {
0075     return d->mUnit;
0076 }
0077 void KSignalPlotter::setUnit(const KLocalizedString &unit)
0078 {
0079     d->mUnit = unit;
0080 }
0081 
0082 void KSignalPlotter::addBeam(const QColor &color)
0083 {
0084     // When we add a new beam, go back and set the data for this beam to NaN for all the other times, to pad it out.
0085     // This is because it makes it easier for moveSensors
0086     for (QList<QList<qreal>>::Iterator it = d->mBeamData.begin(), total = d->mBeamData.end(); it != total; ++it) {
0087         (*it).append(std::numeric_limits<qreal>::quiet_NaN());
0088     }
0089     d->mBeamColors.append(color);
0090     d->mBeamColorsLight.append(color.lighter());
0091 }
0092 
0093 QColor KSignalPlotter::beamColor(int index) const
0094 {
0095     return d->mBeamColors[index];
0096 }
0097 
0098 void KSignalPlotter::setBeamColor(int index, const QColor &color)
0099 {
0100     if (!color.isValid()) {
0101         qCDebug(LIBKSYSGUARD_KSIGNALPLOTTER) << "Invalid color";
0102         return;
0103     }
0104     if (index >= d->mBeamColors.count()) {
0105         qCDebug(LIBKSYSGUARD_KSIGNALPLOTTER) << "Invalid index" << index;
0106         return;
0107     }
0108     Q_ASSERT(d->mBeamColors.count() == d->mBeamColorsLight.count());
0109 
0110     d->mBeamColors[index] = color;
0111     d->mBeamColorsLight[index] = color.lighter();
0112 }
0113 
0114 int KSignalPlotter::numBeams() const
0115 {
0116     return d->mBeamColors.count();
0117 }
0118 
0119 void KSignalPlotter::addSample(const QList<qreal> &sampleBuf)
0120 {
0121     d->addSample(sampleBuf);
0122 #ifdef USE_SEPERATE_WIDGET
0123     d->mGraphWidget->update();
0124 #else
0125     update(d->mPlottingArea);
0126 #endif
0127 }
0128 void KSignalPlotter::reorderBeams(const QList<int> &newOrder)
0129 {
0130     d->reorderBeams(newOrder);
0131 }
0132 
0133 void KSignalPlotter::changeRange(qreal min, qreal max)
0134 {
0135     if (min == d->mUserMinValue && max == d->mUserMaxValue) {
0136         return;
0137     }
0138     d->mUserMinValue = min;
0139     d->mUserMaxValue = max;
0140     d->calculateNiceRange();
0141 }
0142 
0143 void KSignalPlotter::removeBeam(int index)
0144 {
0145     if (index >= d->mBeamColors.size()) {
0146         return;
0147     }
0148     if (index >= d->mBeamColorsLight.size()) {
0149         return;
0150     }
0151     d->mBeamColors.removeAt(index);
0152     d->mBeamColorsLight.removeAt(index);
0153 
0154     QList<QList<qreal>>::Iterator i;
0155     for (i = d->mBeamData.begin(); i != d->mBeamData.end(); ++i) {
0156         if ((*i).size() >= index) {
0157             (*i).removeAt(index);
0158         }
0159     }
0160     if (d->mUseAutoRange) {
0161         d->rescale();
0162     }
0163 }
0164 
0165 void KSignalPlotter::setScaleDownBy(qreal value)
0166 {
0167     if (d->mScaleDownBy == value) {
0168         return;
0169     }
0170     d->mScaleDownBy = value;
0171     d->mBackgroundImage = QPixmap(); // we changed a paint setting, so reset the cache
0172     d->calculateNiceRange();
0173     update();
0174 }
0175 
0176 qreal KSignalPlotter::scaleDownBy() const
0177 {
0178     return d->mScaleDownBy;
0179 }
0180 
0181 void KSignalPlotter::setUseAutoRange(bool value)
0182 {
0183     d->mUseAutoRange = value;
0184     d->calculateNiceRange();
0185     // this change will be detected in paint and the image cache regenerated
0186 }
0187 
0188 bool KSignalPlotter::useAutoRange() const
0189 {
0190     return d->mUseAutoRange;
0191 }
0192 
0193 void KSignalPlotter::setMinimumValue(qreal min)
0194 {
0195     if (min == d->mUserMinValue) {
0196         return;
0197     }
0198     d->mUserMinValue = min;
0199     d->calculateNiceRange();
0200     update();
0201     // this change will be detected in paint and the image cache regenerated
0202 }
0203 
0204 qreal KSignalPlotter::minimumValue() const
0205 {
0206     return d->mUserMinValue;
0207 }
0208 
0209 void KSignalPlotter::setMaximumValue(qreal max)
0210 {
0211     if (max == d->mUserMaxValue) {
0212         return;
0213     }
0214     d->mUserMaxValue = max;
0215     d->calculateNiceRange();
0216     update();
0217     // this change will be detected in paint and the image cache regenerated
0218 }
0219 
0220 qreal KSignalPlotter::maximumValue() const
0221 {
0222     return d->mUserMaxValue;
0223 }
0224 
0225 qreal KSignalPlotter::currentMaximumRangeValue() const
0226 {
0227     return d->mNiceMaxValue;
0228 }
0229 
0230 qreal KSignalPlotter::currentMinimumRangeValue() const
0231 {
0232     return d->mNiceMinValue;
0233 }
0234 
0235 void KSignalPlotter::setHorizontalScale(uint scale)
0236 {
0237     if (scale == d->mHorizontalScale || scale == 0) {
0238         return;
0239     }
0240 
0241     d->mHorizontalScale = scale;
0242     d->updateDataBuffers();
0243 #ifdef USE_QIMAGE
0244     d->mScrollableImage = QImage();
0245 #else
0246     d->mScrollableImage = QPixmap();
0247 #endif
0248 
0249     update();
0250 }
0251 
0252 int KSignalPlotter::horizontalScale() const
0253 {
0254     return d->mHorizontalScale;
0255 }
0256 
0257 void KSignalPlotter::setShowVerticalLines(bool value)
0258 {
0259     if (d->mShowVerticalLines == value) {
0260         return;
0261     }
0262     d->mShowVerticalLines = value;
0263     d->mBackgroundImage = QPixmap(); // we changed a paint setting, so reset the cache
0264 #ifdef USE_QIMAGE
0265     d->mScrollableImage = QImage();
0266 #else
0267     d->mScrollableImage = QPixmap();
0268 #endif
0269     update();
0270 }
0271 
0272 bool KSignalPlotter::showVerticalLines() const
0273 {
0274     return d->mShowVerticalLines;
0275 }
0276 
0277 void KSignalPlotter::setVerticalLinesDistance(uint distance)
0278 {
0279     if (distance == d->mVerticalLinesDistance) {
0280         return;
0281     }
0282     d->mVerticalLinesDistance = distance;
0283     d->mBackgroundImage = QPixmap(); // we changed a paint setting, so reset the cache
0284 #ifdef USE_QIMAGE
0285     d->mScrollableImage = QImage();
0286 #else
0287     d->mScrollableImage = QPixmap();
0288 #endif
0289     update();
0290 }
0291 
0292 uint KSignalPlotter::verticalLinesDistance() const
0293 {
0294     return d->mVerticalLinesDistance;
0295 }
0296 
0297 void KSignalPlotter::setVerticalLinesScroll(bool value)
0298 {
0299     if (value == d->mVerticalLinesScroll) {
0300         return;
0301     }
0302     d->mVerticalLinesScroll = value;
0303 #ifdef USE_QIMAGE
0304     d->mScrollableImage = QImage();
0305 #else
0306     d->mScrollableImage = QPixmap();
0307 #endif
0308     update();
0309 }
0310 
0311 bool KSignalPlotter::verticalLinesScroll() const
0312 {
0313     return d->mVerticalLinesScroll;
0314 }
0315 
0316 void KSignalPlotter::setShowHorizontalLines(bool value)
0317 {
0318     if (value == d->mShowHorizontalLines) {
0319         return;
0320     }
0321     d->mShowHorizontalLines = value;
0322 #ifdef USE_QIMAGE
0323     d->mScrollableImage = QImage();
0324 #else
0325     d->mScrollableImage = QPixmap();
0326 #endif
0327 
0328     update();
0329 }
0330 
0331 bool KSignalPlotter::showHorizontalLines() const
0332 {
0333     return d->mShowHorizontalLines;
0334 }
0335 
0336 void KSignalPlotter::setShowAxis(bool value)
0337 {
0338     if (value == d->mShowAxis) {
0339         return;
0340     }
0341     d->mShowAxis = value;
0342     d->mBackgroundImage = QPixmap(); // we changed a paint setting, so reset the cache
0343 #ifdef USE_QIMAGE
0344     d->mScrollableImage = QImage();
0345 #else
0346     d->mScrollableImage = QPixmap();
0347 #endif
0348     update();
0349 }
0350 
0351 bool KSignalPlotter::showAxis() const
0352 {
0353     return d->mShowAxis;
0354 }
0355 
0356 QString KSignalPlotter::svgBackground() const
0357 {
0358     return d->mSvgFilename;
0359 }
0360 void KSignalPlotter::setSvgBackground(const QString &filename)
0361 {
0362     if (d->mSvgFilename == filename) {
0363         return;
0364     }
0365     d->mSvgFilename = filename;
0366 #ifdef SVG_SUPPORT
0367     if (filename.isEmpty()) {
0368         delete d->mSvgRenderer;
0369         d->mSvgRenderer = NULL;
0370     } else {
0371         if (!d->mSvgRenderer) {
0372             d->mSvgRenderer = new Plasma::Svg(this);
0373         }
0374         d->mSvgRenderer->setImagePath(d->mSvgFilename);
0375     }
0376 #endif
0377     d->mBackgroundImage = QPixmap();
0378     update();
0379 }
0380 
0381 void KSignalPlotter::setMaxAxisTextWidth(int axisTextWidth)
0382 {
0383     if (d->mAxisTextWidth == axisTextWidth) {
0384         return;
0385     }
0386     d->mAxisTextWidth = axisTextWidth;
0387     d->mBackgroundImage = QPixmap(); // we changed a paint setting, so reset the cache
0388     update();
0389 }
0390 
0391 int KSignalPlotter::maxAxisTextWidth() const
0392 {
0393     return d->mAxisTextWidth;
0394 }
0395 void KSignalPlotter::changeEvent(QEvent *event)
0396 {
0397     switch (event->type()) {
0398     case QEvent::ApplicationPaletteChange:
0399     case QEvent::PaletteChange:
0400     case QEvent::FontChange:
0401     case QEvent::LanguageChange:
0402     case QEvent::LocaleChange:
0403     case QEvent::LayoutDirectionChange:
0404         d->mBackgroundImage = QPixmap(); // we changed a paint setting, so reset the cache
0405         update();
0406         break;
0407     default: // Do nothing
0408         break;
0409     }
0410 }
0411 
0412 #ifdef GRAPHICS_SIGNAL_PLOTTER
0413 void KGraphicsSignalPlotter::resizeEvent(QGraphicsSceneResizeEvent *event)
0414 {
0415     QRect boundingBox(QPoint(0, 0), event->newSize().toSize());
0416     int fontHeight = QFontMetrics(font()).height();
0417 #else
0418 void KSignalPlotter::resizeEvent(QResizeEvent *event)
0419 {
0420     QRect boundingBox(QPoint(0, 0), event->size());
0421     int fontHeight = fontMetrics().height();
0422 #endif
0423     if (d->mShowAxis && d->mAxisTextWidth != 0 && boundingBox.width() > (d->mAxisTextWidth * 1.10 + 2)
0424         && boundingBox.height() > fontHeight) { // if there's room to draw the labels, then draw them!
0425         // We want to adjust the size of plotter bit inside so that the axis text aligns nicely at the top and bottom
0426         // but we don't want to sacrifice too much of the available room, so don't use it if it will take more than 20% of the available space
0427         qreal offset = (fontHeight + 1) / 2;
0428         if (offset < boundingBox.height() * 0.1)
0429             boundingBox.adjust(0, offset, 0, -offset);
0430 
0431         int padding = 1;
0432         if (boundingBox.width() > d->mAxisTextWidth + 50) {
0433             padding = 10; // If there's plenty of room, at 10 pixels for padding the axis text, so that it looks nice
0434         }
0435 
0436         if (layoutDirection() == Qt::RightToLeft) {
0437             boundingBox.setRight(boundingBox.right() - d->mAxisTextWidth - padding);
0438         } else {
0439             boundingBox.setLeft(d->mAxisTextWidth + padding);
0440         }
0441         d->mActualAxisTextWidth = d->mAxisTextWidth;
0442     } else {
0443         d->mActualAxisTextWidth = 0;
0444     }
0445     if (d->mShowThinFrame) {
0446         boundingBox.adjust(0, 0, -1, -1);
0447     }
0448 
0449     // Remember bounding box to pass to update, so that we only update the plotting area the next time, if that's the only that thing that has changed
0450     d->mPlottingArea = boundingBox;
0451 
0452     // Calculate the new number of horizontal lines
0453     int newHorizontalLinesCount = qBound(0, (int)(boundingBox.height() / fontHeight) - 2, 4);
0454     if (newHorizontalLinesCount != d->mHorizontalLinesCount) {
0455         d->mHorizontalLinesCount = newHorizontalLinesCount;
0456         d->calculateNiceRange();
0457     }
0458 #ifdef USE_QIMAGE
0459     d->mScrollableImage = QImage();
0460 #else
0461     d->mScrollableImage = QPixmap();
0462 #endif
0463 
0464 #ifdef USE_SEPERATE_WIDGET
0465     d->mGraphWidget->setVisible(true);
0466     d->mGraphWidget->setGeometry(boundingBox);
0467 #endif
0468 
0469     d->updateDataBuffers();
0470 }
0471 
0472 KSignalPlotterPrivate::KSignalPlotterPrivate(KSignalPlotter *q_ptr_)
0473     : q(q_ptr_)
0474 {
0475     mPrecision = 0;
0476     mMaxSamples = NUM_SAMPLES_WHEN_INVISIBLE;
0477     mMinValue = mMaxValue = std::numeric_limits<qreal>::quiet_NaN();
0478     mUserMinValue = mUserMaxValue = 0.0;
0479     mNiceMinValue = mNiceMaxValue = 0.0;
0480     mNiceRange = 0;
0481     mUseAutoRange = true;
0482     mScaleDownBy = 1;
0483     mShowThinFrame = true;
0484     mSmoothGraph = true;
0485     mShowVerticalLines = false;
0486     mVerticalLinesDistance = 30;
0487     mVerticalLinesScroll = true;
0488     mVerticalLinesOffset = 0;
0489     mHorizontalScale = 6;
0490     mShowHorizontalLines = true;
0491     mHorizontalLinesCount = 4;
0492     mShowAxis = true;
0493     mAxisTextWidth = 0;
0494     mScrollOffset = 0;
0495     mStackBeams = false;
0496     mFillOpacity = 20;
0497     mRescaleTime = 0;
0498     mUnit = ki18n("%1");
0499     mAxisTextOverlapsPlotter = false;
0500     mActualAxisTextWidth = 0;
0501 #ifdef USE_SEPERATE_WIDGET
0502     mGraphWidget = new GraphWidget(q); ///< This is the widget that draws the actual graph
0503     mGraphWidget->signalPlotterPrivate = this;
0504     mGraphWidget->setVisible(false);
0505 #endif
0506 }
0507 
0508 void KSignalPlotterPrivate::recalculateMaxMinValueForSample(const QList<qreal> &sampleBuf, int time)
0509 {
0510     if (mStackBeams) {
0511         qreal value = 0;
0512         for (int i = sampleBuf.count() - 1; i >= 0; i--) {
0513             qreal newValue = sampleBuf[i];
0514             if (!std::isinf(newValue) && !std::isnan(newValue)) {
0515                 value += newValue;
0516             }
0517         }
0518         if (std::isnan(mMinValue) || mMinValue > value) {
0519             mMinValue = value;
0520         }
0521         if (std::isnan(mMaxValue) || mMaxValue < value) {
0522             mMaxValue = value;
0523         }
0524         if (value > 0.7 * mMaxValue) {
0525             mRescaleTime = time;
0526         }
0527     } else {
0528         qreal value;
0529         for (int i = sampleBuf.count() - 1; i >= 0; i--) {
0530             value = sampleBuf[i];
0531             if (!std::isinf(value) && !std::isnan(value)) {
0532                 if (std::isnan(mMinValue) || mMinValue > value) {
0533                     mMinValue = value;
0534                 }
0535                 if (std::isnan(mMaxValue) || mMaxValue < value) {
0536                     mMaxValue = value;
0537                 }
0538                 if (value > 0.7 * mMaxValue) {
0539                     mRescaleTime = time;
0540                 }
0541             }
0542         }
0543     }
0544 }
0545 
0546 void KSignalPlotterPrivate::rescale()
0547 {
0548     mMaxValue = mMinValue = std::numeric_limits<qreal>::quiet_NaN();
0549     for (int i = mBeamData.count() - 1; i >= 0; i--) {
0550         recalculateMaxMinValueForSample(mBeamData[i], i);
0551     }
0552     calculateNiceRange();
0553 }
0554 
0555 void KSignalPlotterPrivate::addSample(const QList<qreal> &sampleBuf)
0556 {
0557     if (sampleBuf.count() != mBeamColors.count()) {
0558         qCDebug(LIBKSYSGUARD_KSIGNALPLOTTER) << "Sample data discarded - contains wrong number of beams";
0559         return;
0560     }
0561     mBeamData.prepend(sampleBuf);
0562     if ((unsigned int)mBeamData.size() > mMaxSamples) {
0563         // we have too many.  Remove the last item
0564         mBeamData.removeLast();
0565         if ((unsigned int)mBeamData.size() > mMaxSamples) {
0566             // If we still have too many, then we have resized the widget.
0567             // Remove one more.  That way we will slowly resize to the new
0568             // size
0569             mBeamData.removeLast();
0570         }
0571     }
0572 
0573     if (mUseAutoRange) {
0574         recalculateMaxMinValueForSample(sampleBuf, 0);
0575 
0576         if (mRescaleTime++ > mMaxSamples) {
0577             rescale();
0578         } else if (mMinValue < mNiceMinValue || mMaxValue > mNiceMaxValue
0579                    || (mMaxValue > mUserMaxValue && mNiceRange != 1 && mMaxValue < (mNiceRange * 0.75 + mNiceMinValue)) || mNiceRange == 0) {
0580             calculateNiceRange();
0581         }
0582     } else {
0583         if (mMinValue < mNiceMinValue || mMaxValue > mNiceMaxValue
0584             || (mMaxValue > mUserMaxValue && mNiceRange != 1 && mMaxValue < (mNiceRange * 0.75 + mNiceMinValue)) || mNiceRange == 0) {
0585             calculateNiceRange();
0586         }
0587     }
0588 
0589     if (mScrollableImage.isNull()) {
0590         return;
0591     }
0592     QPainter pCache(&mScrollableImage);
0593     drawBeamToScrollableImage(&pCache, 0);
0594 }
0595 
0596 void KSignalPlotterPrivate::reorderBeams(const QList<int> &newOrder)
0597 {
0598     if (newOrder.count() != mBeamColors.count()) {
0599         return;
0600     }
0601     QList<QList<qreal>>::Iterator it;
0602     for (it = mBeamData.begin(); it != mBeamData.end(); ++it) {
0603         if (newOrder.count() != (*it).count()) {
0604             qCWarning(LIBKSYSGUARD_KSIGNALPLOTTER) << "Serious problem in move sample.  beamdata[i] has " << (*it).count() << " and neworder has "
0605                                                    << newOrder.count();
0606         } else {
0607             QList<qreal> newBeam;
0608             for (int i = 0; i < newOrder.count(); i++) {
0609                 int newIndex = newOrder[i];
0610                 newBeam.append((*it).at(newIndex));
0611             }
0612             (*it) = newBeam;
0613         }
0614     }
0615     QList<QColor> newBeamColors;
0616     QList<QColor> newBeamColorsDark;
0617     for (int i = 0; i < newOrder.count(); i++) {
0618         int newIndex = newOrder[i];
0619         newBeamColors.append(mBeamColors.at(newIndex));
0620         newBeamColorsDark.append(mBeamColorsLight.at(newIndex));
0621     }
0622     mBeamColors = newBeamColors;
0623     mBeamColorsLight = newBeamColorsDark;
0624 }
0625 
0626 void KSignalPlotterPrivate::updateDataBuffers()
0627 {
0628     /*  This is called when the widget has resized
0629      *
0630      *  Determine new number of samples first.
0631      *  +0.5 to ensure rounding up
0632      *  +4 for extra data points so there is
0633      *     1) no wasted space and
0634      *     2) no loss of precision when drawing the first data point.
0635      */
0636     if (q->isVisible()) {
0637         mMaxSamples = uint(q->size().width() / mHorizontalScale + 4);
0638     } else {
0639         // If it's not visible, we can't rely on sensible values for width.  Store some minimum number of data points
0640         mMaxSamples = qMin((uint)(q->size().width() / mHorizontalScale + 4), NUM_SAMPLES_WHEN_INVISIBLE);
0641     }
0642 }
0643 
0644 #ifdef GRAPHICS_SIGNAL_PLOTTER
0645 void KGraphicsSignalPlotter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0646 {
0647     Q_UNUSED(widget);
0648 #else
0649 void KSignalPlotter::paintEvent(QPaintEvent *event)
0650 {
0651     if (testAttribute(Qt::WA_PendingResizeEvent)) {
0652         return; // lets not do this more than necessary, shall we?
0653     }
0654     QPainter p(this);
0655     QPainter *painter = &p;
0656 #endif
0657 
0658     uint w = size().width();
0659     uint h = size().height();
0660     /* Do not do repaints when the widget is not yet setup properly. */
0661     if (w <= 2 || h <= 2) {
0662         return;
0663     }
0664 
0665 #ifdef GRAPHICS_SIGNAL_PLOTTER
0666     bool onlyDrawPlotter = option && d->mPlottingArea.contains(option->exposedRect.toRect());
0667 #else
0668     bool onlyDrawPlotter = event && d->mPlottingArea.contains(event->rect());
0669 #endif
0670 
0671 #ifdef USE_SEPERATE_WIDGET
0672     if (onlyDrawPlotter) {
0673         return; // The painting will be handled entirely by GraphWidget::paintEvent
0674     }
0675 #endif
0676 
0677     if (!onlyDrawPlotter && d->mShowThinFrame) {
0678         d->drawThinFrame(painter, d->mPlottingArea.adjusted(0, 0, 1, 1)); // We have a 'frame' in the bottom and right - so subtract them from the view
0679     }
0680 
0681 #ifndef USE_SEPERATE_WIDGET
0682     d->drawWidget(painter, d->mPlottingArea); // Draw the widget only if we don't have a GraphWidget to draw it for us
0683 #endif
0684 
0685     if (d->mShowAxis) {
0686 #ifdef GRAPHICS_SIGNAL_PLOTTER
0687         int fontheight = QFontMetrics(font()).height();
0688 #else
0689         int fontheight = fontMetrics().height();
0690 #endif
0691         if (d->mPlottingArea.height() > fontheight) { // if there's room to draw the labels, then draw them!
0692             d->drawAxisText(painter, QRect(0, 0, w, h));
0693         }
0694     }
0695 }
0696 
0697 #ifdef USE_SEPERATE_WIDGET
0698 void GraphWidget::paintEvent(QPaintEvent *)
0699 {
0700     if (testAttribute(Qt::WA_PendingResizeEvent)) {
0701         return; // lets not do this more than necessary, shall we?
0702     }
0703 
0704     uint w = width();
0705     uint h = height();
0706     /* Do not do repaints when the widget is not yet setup properly. */
0707     if (w <= 2 || h <= 2) {
0708         return;
0709     }
0710     QPainter p(this);
0711 
0712     signalPlotterPrivate->drawWidget(&p, QRect(0, 0, w, h));
0713 
0714     if (signalPlotterPrivate->mAxisTextOverlapsPlotter && signalPlotterPrivate->mShowAxis) {
0715         uint fontheight = signalPlotterPrivate->q->fontMetrics().height();
0716         if (h > fontheight) { // if there's room to draw the labels, then draw them!
0717             QSize originalSize = signalPlotterPrivate->q->size();
0718             signalPlotterPrivate->drawAxisText(&p, QRect(-signalPlotterPrivate->mPlottingArea.topLeft(), originalSize));
0719         }
0720     }
0721 }
0722 #endif
0723 
0724 void KSignalPlotterPrivate::drawWidget(QPainter *p, const QRect &boundingBox)
0725 {
0726 #ifdef SVG_SUPPORT
0727     if (!mSvgFilename.isEmpty()) {
0728         if (mBackgroundImage.isNull() || mBackgroundImage.height() != boundingBox.height()
0729             || mBackgroundImage.width() != boundingBox.width()) { // recreate on resize etc
0730             updateSvgBackground(boundingBox);
0731         }
0732         p->drawPixmap(boundingBox, mBackgroundImage);
0733     }
0734 #endif
0735 
0736     if (mScrollableImage.isNull()) {
0737         redrawScrollableImage();
0738     }
0739 
0740     // We draw the pixmap in two halves, wrapping around the window
0741     if (mScrollOffset > 1) {
0742 #ifdef USE_QIMAGE
0743         p->drawImage(boundingBox.right() - mScrollOffset + 2, boundingBox.top(), mScrollableImage, 0, 0, mScrollOffset - 1, boundingBox.height());
0744 #else
0745         p->drawPixmap(boundingBox.right() - mScrollOffset + 2, boundingBox.top(), mScrollableImage, 0, 0, mScrollOffset - 1, boundingBox.height());
0746 #endif
0747     }
0748     int widthOfSecondHalf = boundingBox.width() - mScrollOffset + 1;
0749     if (widthOfSecondHalf > 0) {
0750 #ifdef USE_QIMAGE
0751         p->drawImage(boundingBox.left(),
0752                      boundingBox.top(),
0753                      mScrollableImage,
0754                      mScrollableImage.width() - widthOfSecondHalf - 1,
0755                      0,
0756                      widthOfSecondHalf,
0757                      boundingBox.height());
0758 #else
0759         p->drawPixmap(boundingBox.left(),
0760                       boundingBox.top(),
0761                       mScrollableImage,
0762                       mScrollableImage.width() - widthOfSecondHalf - 1,
0763                       0,
0764                       widthOfSecondHalf,
0765                       boundingBox.height());
0766 #endif
0767     }
0768 
0769     /* Draw scope-like grid vertical lines if it doesn't move.  If it does move, draw it in the dynamic part of the code*/
0770     if (mShowVerticalLines && !mVerticalLinesScroll) {
0771         drawVerticalLines(p, boundingBox);
0772     }
0773 }
0774 
0775 void KSignalPlotterPrivate::drawBackground(QPainter *p, const QRect &boundingBox) const
0776 {
0777     p->setRenderHint(QPainter::Antialiasing, false);
0778 #ifdef SVG_SUPPORT
0779     if (!mSvgFilename.isEmpty()) {
0780         // our background is an svg, so don't paint over the top of it
0781         p->fillRect(boundingBox, Qt::transparent);
0782     } else
0783 #endif
0784     {
0785         p->fillRect(boundingBox, q->palette().brush(QPalette::Base));
0786     }
0787 
0788     if (mShowHorizontalLines) {
0789         drawHorizontalLines(p, boundingBox.adjusted(0, 0, 1, 0));
0790     }
0791 
0792     if (mShowVerticalLines && mVerticalLinesScroll) {
0793         drawVerticalLines(p, boundingBox, mVerticalLinesOffset);
0794     }
0795 
0796     p->setRenderHint(QPainter::Antialiasing, true);
0797 }
0798 
0799 bool KSignalPlotter::thinFrame() const
0800 {
0801     return d->mShowThinFrame;
0802 }
0803 
0804 void KSignalPlotter::setThinFrame(bool thinFrame)
0805 {
0806     if (thinFrame == d->mShowThinFrame) {
0807         return;
0808     }
0809 
0810     d->mShowThinFrame = thinFrame;
0811     update(); // Trigger a repaint
0812 }
0813 
0814 #ifdef SVG_SUPPORT
0815 void KSignalPlotterPrivate::updateSvgBackground(const QRect &boundingBox)
0816 {
0817     Q_ASSERT(!mSvgFilename.isEmpty());
0818     Q_ASSERT(boundingBox.isNull());
0819     mBackgroundImage = QPixmap(boundingBox.width(), boundingBox.height());
0820     Q_ASSERT(!mBackgroundImage.isNull());
0821     QPainter pCache(&mBackgroundImage);
0822     pCache.fill(q->palette().color(QPalette::Base));
0823 
0824     svgRenderer->resize(boundingBox.size());
0825     svgRenderer->paint(&pCache, 0, 0);
0826 }
0827 #endif
0828 
0829 void KSignalPlotterPrivate::redrawScrollableImage()
0830 {
0831     // Align width of bounding box to the size of the horizontal scale
0832     int alignedWidth = ((mPlottingArea.width() + 1) / mHorizontalScale + 1) * mHorizontalScale;
0833     // Redraw the whole thing
0834 #ifdef USE_QIMAGE
0835     mScrollableImage = QImage(alignedWidth, mPlottingArea.height(), QImage::Format_ARGB32_Premultiplied);
0836 #else
0837     mScrollableImage = QPixmap(alignedWidth, mPlottingArea.height());
0838 #endif
0839     Q_ASSERT(!mScrollableImage.isNull());
0840 
0841     mScrollOffset = 0;
0842     mVerticalLinesOffset = mVerticalLinesDistance - mHorizontalScale + 1; // mVerticalLinesDistance - alignedWidth % mVerticalLinesDistance;
0843     // We need to draw the background for areas without a beam
0844     int withoutBeamWidth = qMax(mBeamData.size() - 1, 0) * mHorizontalScale;
0845     QPainter pCache(&mScrollableImage);
0846     if (withoutBeamWidth < mScrollableImage.width()) {
0847         drawBackground(&pCache, QRect(withoutBeamWidth, 0, alignedWidth - withoutBeamWidth, mScrollableImage.height()));
0848     }
0849 
0850     /* Draw scope-like grid vertical lines */
0851     mVerticalLinesOffset = 0;
0852     if (mBeamData.size() > 2) {
0853         for (int i = mBeamData.size() - 2; i >= 0; i--) {
0854             drawBeamToScrollableImage(&pCache, i);
0855         }
0856     }
0857 }
0858 
0859 void KSignalPlotterPrivate::drawThinFrame(QPainter *p, const QRect &boundingBox)
0860 {
0861     /* Draw a line along the bottom and the right side of the
0862      * widget to create a 3D like look. */
0863     p->setRenderHint(QPainter::Antialiasing, false);
0864     p->setPen(QPen(q->palette().color(QPalette::Light), 0));
0865     p->drawLine(boundingBox.bottomLeft(), boundingBox.bottomRight());
0866     p->drawLine(boundingBox.bottomRight(), boundingBox.topRight());
0867     p->setRenderHint(QPainter::Antialiasing, true);
0868 }
0869 
0870 void KSignalPlotterPrivate::calculateNiceRange()
0871 {
0872     qreal max = mUserMaxValue;
0873     qreal min = mUserMinValue;
0874     if (mUseAutoRange) {
0875         // Allow max value to go very slightly over the given max, for rounding reasons
0876         if (!std::isnan(mMaxValue) && mMaxValue * 0.99 > max) {
0877             max = mMaxValue;
0878         }
0879         if (!std::isnan(mMinValue) && mMinValue * 0.99 < min) {
0880             min = mMinValue;
0881         }
0882     }
0883 
0884     /* If the range is too small we will force it to 1.0 since it
0885      * looks a lot nicer. */
0886     if (max - min < 0.000001) {
0887         max = min + 1;
0888     }
0889 
0890     qreal range = max - min;
0891 
0892     // Massage the range so that the grid shows some nice values.
0893     qreal step;
0894     int number_lines_above_zero = 0;
0895     int number_lines_below_zero = 0;
0896     // If y=0 is visible and have at least 1 horizontal lines make sure that we have a line crossing through 0
0897     bool alignToXAxis = min < 0 && max > 0 && mHorizontalLinesCount >= 1;
0898     if (alignToXAxis) {
0899         number_lines_above_zero = int(mHorizontalLinesCount * max / range);
0900         number_lines_below_zero = mHorizontalLinesCount - number_lines_above_zero - 1; // subtract 1 line for the actual 0 line
0901         step = qMax(max / (mScaleDownBy * (number_lines_above_zero + 1)), -min / (mScaleDownBy * (number_lines_below_zero + 1)));
0902     } else {
0903         step = range / (mScaleDownBy * (mHorizontalLinesCount + 1));
0904     }
0905 
0906     const int sigFigs = 2; // Number of significant figures of the step to use.  Update the 0.05 below if this changes
0907     int logdim = (int)floor(log10(step)) - (sigFigs - 1); // find the order of the number, reduced by 1.  E.g. if step=1234 then logdim is 3-1=2
0908     qreal dim = pow((qreal)10.0, logdim); // e.g.  if step=1234, logdim=2, so dim = 100
0909     int a = (int)ceil(step / dim - 0.000005); // so a = ceil(1234/100) = ceil(12.34) = 13    (we subtract an epsilon)
0910 
0911     if (logdim >= 0) {
0912         // Work out the number of decimal places.  e.g. If step=1.5, then logdim = -1 so precision is 1
0913         mPrecision = 0;
0914     } else if (a % 10 == 0) {
0915         // We just happened to round to a nice number, requiring 1 less precision
0916         mPrecision = -logdim - 1;
0917     } else {
0918         mPrecision = -logdim;
0919     }
0920     // e.g. if step=1234, dim=100, a=13  so step= 1300.  So 1234 was rounded up to 1300
0921     step = dim * a;
0922 
0923     range = mScaleDownBy * step * (mHorizontalLinesCount + 1);
0924 
0925     if (alignToXAxis) {
0926         max = mScaleDownBy * step * (number_lines_above_zero + 1);
0927         min = -mScaleDownBy * step * (number_lines_below_zero + 1);
0928     } else {
0929         max = min + range;
0930     }
0931 
0932     if (mNiceMinValue == min && mNiceRange == range) {
0933         return; // nothing changed
0934     }
0935 
0936     mNiceMaxValue = max;
0937     mNiceMinValue = min;
0938     mNiceRange = range;
0939 
0940 #ifdef USE_QIMAGE
0941     mScrollableImage = QImage();
0942 #else
0943     mScrollableImage = QPixmap();
0944 #endif
0945     Q_EMIT q->axisScaleChanged();
0946     q->update();
0947 }
0948 
0949 void KSignalPlotterPrivate::drawVerticalLines(QPainter *p, const QRect &boundingBox, int offset) const
0950 {
0951     QColor color = q->palette().color(QPalette::Window);
0952     if (!mVerticalLinesScroll) {
0953         color.setAlpha(127);
0954     }
0955     p->setPen(QPen(color, 0));
0956 
0957     p->setRenderHint(QPainter::Antialiasing, false);
0958     for (int x = boundingBox.right() - (offset % mVerticalLinesDistance); x >= boundingBox.left(); x -= mVerticalLinesDistance) {
0959         p->drawLine(x, boundingBox.top(), x, boundingBox.bottom());
0960     }
0961     p->setRenderHint(QPainter::Antialiasing, true);
0962 }
0963 
0964 void KSignalPlotterPrivate::drawBeamToScrollableImage(QPainter *p, int index)
0965 {
0966     QRect cacheBoundingBox = QRect(mScrollOffset, 0, mHorizontalScale, mScrollableImage.height());
0967 
0968     drawBackground(p, cacheBoundingBox);
0969     drawBeam(p, cacheBoundingBox, mHorizontalScale, index);
0970 
0971     mScrollOffset += mHorizontalScale;
0972     mVerticalLinesOffset = (mVerticalLinesOffset + mHorizontalScale) % mVerticalLinesDistance;
0973     if (mScrollOffset >= mScrollableImage.width() - 1) {
0974         mScrollOffset = 0;
0975         mVerticalLinesOffset--; // We skip over the last pixel drawn
0976     }
0977 }
0978 
0979 void KSignalPlotterPrivate::drawBeam(QPainter *p, const QRect &boundingBox, int horizontalScale, int index)
0980 {
0981     if (mNiceRange == 0) {
0982         return;
0983     }
0984     QPen pen;
0985     if (mHorizontalScale == 1) {
0986         // Don't use a pen width of 2 if there's only 1 pixel between points
0987         pen.setWidth(1);
0988     } else {
0989         pen.setWidth(2);
0990     }
0991 
0992     pen.setCapStyle(Qt::FlatCap);
0993 
0994     qreal scaleFac = (boundingBox.height() - 2) / mNiceRange;
0995     if (mBeamData.size() - 1 <= index) {
0996         return; // Something went wrong?
0997     }
0998 
0999     const QList<qreal> &datapoints = mBeamData[index];
1000     const QList<qreal> &prev_datapoints = mBeamData[index + 1];
1001     bool hasPrevPrevDatapoints = (index + 2 < mBeamData.size()); // used for bezier curve gradient calculation
1002     const QList<qreal> &prev_prev_datapoints = hasPrevPrevDatapoints ? mBeamData[index + 2] : prev_datapoints;
1003 
1004     qreal x0 = boundingBox.right();
1005     qreal x1 = qMax(boundingBox.right() - horizontalScale, 0);
1006 
1007     qreal xaxis = boundingBox.bottom();
1008     if (mNiceMinValue < 0) {
1009         xaxis = qMax(qreal(xaxis + mNiceMinValue * scaleFac), qreal(boundingBox.top()));
1010     }
1011 
1012     const int count = qMin(datapoints.size(), mBeamColors.size());
1013     QList<QPainterPath> paths(count);
1014     QPointF previous_c0;
1015     QPointF previous_c1;
1016     QPointF previous_c2;
1017     QPointF previous_c3;
1018     qreal previous_point0 = 0;
1019     qreal previous_point1 = 0;
1020     qreal previous_point2 = 0;
1021     bool firstLine = true;
1022     for (int j = 0; j < count; ++j) {
1023         qreal point0 = datapoints[j];
1024         if (std::isnan(point0)) {
1025             continue; // Just do not draw points with nans. skip them
1026         }
1027 
1028         qreal point1 = prev_datapoints[j];
1029         qreal point2 = prev_prev_datapoints[j];
1030 
1031         if (std::isnan(point1)) {
1032             point1 = point0;
1033         } else if (mSmoothGraph && !std::isinf(point1)) {
1034             // Apply a weighted average just to smooth the graph out a bit
1035             // Do not try to smooth infinities or nans
1036             point0 = (2 * point0 + point1) / 3;
1037             if (!std::isnan(point2) && !std::isinf(point2)) {
1038                 point1 = (2 * point1 + point2) / 3;
1039             }
1040             // We don't bother to average out y2.  This will introduce slight inaccuracies in the gradients, but they aren't really noticeable.
1041         }
1042         if (std::isnan(point2)) {
1043             point2 = point1;
1044         }
1045 
1046         if (mStackBeams) {
1047             point0 = previous_point0 = previous_point0 + point0;
1048             point1 = previous_point1 = previous_point1 + point1;
1049             point2 = previous_point2 = previous_point2 + point2;
1050         }
1051 
1052         qreal y0 = qBound((qreal)boundingBox.top(), boundingBox.bottom() - (point0 - mNiceMinValue) * scaleFac, (qreal)boundingBox.bottom());
1053         qreal y1 = qBound((qreal)boundingBox.top(), boundingBox.bottom() - (point1 - mNiceMinValue) * scaleFac, (qreal)boundingBox.bottom());
1054         qreal y2 = qBound((qreal)boundingBox.top(), boundingBox.bottom() - (point2 - mNiceMinValue) * scaleFac, (qreal)boundingBox.bottom());
1055 
1056         QPainterPath &path = paths[j];
1057         path.moveTo(x1, y1);
1058         QPointF c1(x1 + horizontalScale / 3.0, (4 * y1 - y2) / 3.0); // Control point 1 - same gradient as prev_prev_datapoint to prev_datapoint
1059         QPointF c2(x1 + 2 * horizontalScale / 3.0, (2 * y0 + y1) / 3.0); // Control point 2 - same gradient as prev_datapoint to datapoint
1060         QPointF c3(x0, y0);
1061         path.cubicTo(c1, c2, c3);
1062         if (mFillOpacity) {
1063             QPainterPath fillPath = path; // Make a copy to do our fill
1064             if (!mStackBeams || firstLine) {
1065                 fillPath.lineTo(x0, xaxis);
1066                 fillPath.lineTo(x1, xaxis);
1067                 fillPath.lineTo(x1, y1);
1068             } else {
1069                 fillPath.lineTo(x0, previous_c3.y());
1070                 fillPath.cubicTo(previous_c2, previous_c1, previous_c0);
1071                 fillPath.lineTo(x1, y1);
1072             }
1073             if (mStackBeams) {
1074                 previous_c0 = QPointF(x1, y1);
1075                 previous_c1 = c1;
1076                 previous_c2 = c2;
1077                 previous_c3 = c3;
1078             }
1079             QColor fillColor = mBeamColors[j];
1080             fillColor.setAlpha(mFillOpacity);
1081             p->fillPath(fillPath, fillColor);
1082         }
1083         firstLine = false;
1084     }
1085     for (int j = count - 1; j >= 0; --j) {
1086         if (mFillOpacity) {
1087             pen.setColor(mBeamColorsLight.at(j));
1088         } else {
1089             pen.setColor(mBeamColors.at(j));
1090         }
1091         p->strokePath(paths.at(j), pen);
1092     }
1093 }
1094 
1095 void KSignalPlotterPrivate::drawAxisText(QPainter *p, const QRect &boundingBox)
1096 {
1097     if (mHorizontalLinesCount < 0) {
1098         return;
1099     }
1100     p->setFont(q->font());
1101     qreal stepsize = mNiceRange / (mScaleDownBy * (mHorizontalLinesCount + 1));
1102     if (mActualAxisTextWidth == 0) {
1103         // If we are drawing completely inside the plotter area, using the Text color
1104         p->setPen(QPen(q->palette().brush(QPalette::Text), 0));
1105     } else {
1106         p->setPen(QPen(q->palette().brush(QPalette::WindowText), 0));
1107     }
1108     int axisTitleIndex = 1;
1109     QString val;
1110     int numItems = mHorizontalLinesCount + 2;
1111     int fontHeight = p->fontMetrics().height();
1112     if (numItems == 2 && boundingBox.height() < fontHeight * 2) {
1113         numItems = 1;
1114     }
1115     mAxisTextOverlapsPlotter = false;
1116     for (int y = 0; y < numItems; y++, axisTitleIndex++) {
1117         // Make sure it's y*h first to avoid rounding bugs
1118         int y_coord = boundingBox.top() + (y * (boundingBox.height() - fontHeight)) / (mHorizontalLinesCount + 1);
1119         qreal value;
1120         if (y == mHorizontalLinesCount + 1) {
1121             value = mNiceMinValue / mScaleDownBy; // sometimes using the formulas gives us a value very slightly off
1122         } else {
1123             value = mNiceMaxValue / mScaleDownBy - y * stepsize;
1124         }
1125 
1126         val = scaledValueAsString(value, mPrecision);
1127         QRect textBoundingRect = p->fontMetrics().boundingRect(val);
1128 
1129         if (textBoundingRect.width() > mActualAxisTextWidth) {
1130             mAxisTextOverlapsPlotter = true;
1131         }
1132         int offset = qMax(mActualAxisTextWidth - textBoundingRect.right(), -textBoundingRect.left());
1133         if (q->layoutDirection() == Qt::RightToLeft) {
1134             p->drawText(boundingBox.left(), y_coord, boundingBox.width() - offset, fontHeight + 1, Qt::AlignLeft | Qt::AlignTop, val);
1135         } else {
1136             p->drawText(boundingBox.left() + offset, y_coord, boundingBox.width() - offset, fontHeight + 1, Qt::AlignLeft | Qt::AlignTop, val);
1137         }
1138     }
1139 }
1140 
1141 void KSignalPlotterPrivate::drawHorizontalLines(QPainter *p, const QRect &boundingBox) const
1142 {
1143     if (mHorizontalLinesCount <= 0) {
1144         return;
1145     }
1146     p->setPen(QPen(q->palette().color(QPalette::Window)));
1147     for (int y = 0; y <= mHorizontalLinesCount + 1; y++) {
1148         // note that the y_coord starts from 0.  so we draw from pixel number 0 to h-1.  Thus the -1 in the y_coord
1149         int y_coord = boundingBox.top() + (y * (boundingBox.height() - 1)) / (mHorizontalLinesCount + 1); // Make sure it's y*h first to avoid rounding bugs
1150         p->drawLine(boundingBox.left(), y_coord, boundingBox.right() - 1, y_coord);
1151     }
1152 }
1153 
1154 int KSignalPlotter::currentAxisPrecision() const
1155 {
1156     return d->mPrecision;
1157 }
1158 
1159 qreal KSignalPlotter::lastValue(int i) const
1160 {
1161     if (d->mBeamData.isEmpty() || d->mBeamData.first().size() <= i) {
1162         return std::numeric_limits<qreal>::quiet_NaN();
1163     }
1164     return d->mBeamData.first().at(i);
1165 }
1166 
1167 QString KSignalPlotter::lastValueAsString(int i, int precision) const
1168 {
1169     if (d->mBeamData.isEmpty() || d->mBeamData.first().size() <= i || std::isnan(d->mBeamData.first().at(i))) {
1170         return QString();
1171     }
1172     return valueAsString(d->mBeamData.first().at(i), precision); // retrieve the newest value for this beam
1173 }
1174 
1175 QString KSignalPlotter::valueAsString(qreal value, int precision) const
1176 {
1177     if (std::isnan(value)) {
1178         return QString();
1179     }
1180     value = value / d->mScaleDownBy; // scale the value.  E.g. from Bytes to KiB
1181     return d->scaledValueAsString(value, precision);
1182 }
1183 
1184 QString KSignalPlotterPrivate::scaledValueAsString(qreal value, int precision) const
1185 {
1186     qreal absvalue = qAbs(value);
1187     if (precision == -1) {
1188         if (absvalue >= 99.5) {
1189             precision = 0;
1190         } else if (absvalue >= 0.995 || (mScaleDownBy == 1 && mNiceMaxValue > 20)) {
1191             precision = 1;
1192         } else {
1193             precision = 2;
1194         }
1195     }
1196 
1197     if (absvalue < 1E6) {
1198         if (precision == 0) {
1199             return mUnit.subs((long)value).toString();
1200         } else {
1201             return mUnit.subs(value, 0, 'f', precision).toString();
1202         }
1203     } else {
1204         return mUnit.subs(value, 0, 'g', precision).toString();
1205     }
1206 }
1207 
1208 bool KSignalPlotter::smoothGraph() const
1209 {
1210     return d->mSmoothGraph;
1211 }
1212 
1213 void KSignalPlotter::setSmoothGraph(bool smooth)
1214 {
1215     d->mSmoothGraph = smooth;
1216 #ifdef USE_QIMAGE
1217     d->mScrollableImage = QImage();
1218 #else
1219     d->mScrollableImage = QPixmap();
1220 #endif
1221 }
1222 
1223 bool KSignalPlotter::stackGraph() const
1224 {
1225     return d->mStackBeams;
1226 }
1227 
1228 void KSignalPlotter::setStackGraph(bool stack)
1229 {
1230     d->mStackBeams = stack;
1231 #ifdef USE_QIMAGE
1232     d->mScrollableImage = QImage();
1233 #else
1234     d->mScrollableImage = QPixmap();
1235 #endif
1236 }
1237 
1238 int KSignalPlotter::fillOpacity() const
1239 {
1240     return d->mFillOpacity;
1241 }
1242 
1243 void KSignalPlotter::setFillOpacity(int fill)
1244 {
1245     d->mFillOpacity = fill;
1246 #ifdef USE_QIMAGE
1247     d->mScrollableImage = QImage();
1248 #else
1249     d->mScrollableImage = QPixmap();
1250 #endif
1251 }
1252 
1253 #ifdef GRAPHICS_SIGNAL_PLOTTER
1254 QPainterPath KGraphicsSignalPlotter::opaqueArea() const
1255 {
1256     return shape(); // The whole thing is opaque
1257 }
1258 
1259 QSizeF KGraphicsSignalPlotter::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
1260 {
1261     Q_UNUSED(constraint);
1262     if (which == Qt::PreferredSize) {
1263         return QSizeF(200, 200);
1264     }
1265     return QGraphicsWidget::sizeHint(which, constraint);
1266 }
1267 #else
1268 QSize KSignalPlotter::sizeHint() const
1269 {
1270     return QSize(200, 200); // Just a random size which would usually look okay
1271 }
1272 #endif
1273 
1274 #ifdef USE_SEPERATE_WIDGET
1275 GraphWidget::GraphWidget(QWidget *parent)
1276     : QWidget(parent)
1277 {
1278 }
1279 #endif
1280 
1281 #undef KSignalPlotter
1282 #undef KSignalPlotterPrivate