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