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