File indexing completed on 2024-05-19 16:37:07

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