diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-17 01:06:58 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-17 01:06:58 +0000 |
commit | ccceabd36ed168eefd785fb4fb720a577482fec1 (patch) | |
tree | 643927e0ff83170395fa3be8f50c69dae48be1ca /src/knemod/signalplotter.cpp | |
download | knemo-ccceabd36ed168eefd785fb4fb720a577482fec1.tar.gz knemo-ccceabd36ed168eefd785fb4fb720a577482fec1.zip |
Added KDE3 version of knemo
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/knemo@1091560 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/knemod/signalplotter.cpp')
-rw-r--r-- | src/knemod/signalplotter.cpp | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/src/knemod/signalplotter.cpp b/src/knemod/signalplotter.cpp new file mode 100644 index 0000000..4945a2d --- /dev/null +++ b/src/knemod/signalplotter.cpp @@ -0,0 +1,706 @@ +/* + KSysGuard, the KDE System Guard + + Copyright (c) 1999 - 2002 Chris Schlaeger <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of version 2 of the GNU General Public + License as published by the Free Software Foundation + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + KSysGuard is currently maintained by Chris Schlaeger <[email protected]>. + Please do not commit any changes without consulting me first. Thanks! + + $Id: SignalPlotter.cc,v 1.7 2004/01/04 13:43:48 waba Exp $ +*/ + +#include <math.h> +#include <string.h> + +#include <qpainter.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <kconfig.h> + +#include "signalplotter.h" + +static inline int min( int a, int b ) +{ + return ( a < b ? a : b ); +} + +SignalPlotter::SignalPlotter( QWidget *parent, const char *name ) + : QDialog( parent, name ), + mPosInitialized( false ), + mName( name ) +{ + // Auto deletion does not work for pointer to arrays. + mBeamData.setAutoDelete( false ); + + setBackgroundMode( NoBackground ); + + mSamples = 0; + mMinValue = mMaxValue = 0.0; + mUseAutoRange = true; + + mGraphStyle = GRAPH_POLYGON; + + // Anything smaller than this does not make sense. + setMinimumSize( 16, 16 ); + setSizePolicy( QSizePolicy( QSizePolicy::Expanding, + QSizePolicy::Expanding, false ) ); + + mShowVerticalLines = true; + mVerticalLinesColor = QColor( 0x04FB1D ); + mVerticalLinesDistance = 30; + mVerticalLinesScroll = true; + mVerticalLinesOffset = 0; + mHorizontalScale = 1; + + mShowHorizontalLines = true; + mHorizontalLinesColor = QColor( 0x04FB1D ); + mHorizontalLinesCount = 5; + + mShowLabels = true; + mShowTopBar = false; + mFontSize = 8; + + mBackgroundColor = QColor( 0x313031 ); + + // Restore window size and position. + KConfig* config = new KConfig( "knemorc", false ); + if ( config->hasGroup( "Interface_" + mName ) ) + { + config->setGroup( "Interface_" + mName ); + if ( config->hasKey( "PlotterX" ) && config->hasKey( "PlotterY" ) ) + { + mPos.setX( config->readNumEntry( "PlotterX" ) ); + mPos.setY( config->readNumEntry( "PlotterY" ) ); + mPosInitialized = true; + } + if ( config->hasKey( "PlotterWidth" ) && config->hasKey( "PlotterHeight" ) ) + resize( config->readNumEntry( "PlotterWidth" ), + config->readNumEntry( "PlotterHeight" ) ); + } + delete config; +} + +SignalPlotter::~SignalPlotter() +{ + for ( double* p = mBeamData.first(); p; p = mBeamData.next() ) + delete [] p; + + // Store window size and position. + KConfig* config = new KConfig( "knemorc", false ); + if ( config->hasGroup( "Interface_" + mName ) ) + { + config->setGroup( "Interface_" + mName ); + config->writeEntry( "PlotterX", x() ); + config->writeEntry( "PlotterY", y() ); + config->writeEntry( "PlotterWidth", width() ); + config->writeEntry( "PlotterHeight", height() ); + config->sync(); + } + delete config; +} + +void SignalPlotter::hide() +{ + mPos = pos(); + mPosInitialized = true; + QDialog::hide(); +} + +void SignalPlotter::show() +{ + QDialog::show(); + /** + * mPosInitialized should always be true, except when + * starting KNemo for the very first time. + */ + if ( mPosInitialized ) + move( mPos ); +} + +bool SignalPlotter::addBeam( const QColor &color ) +{ + double* d = new double[ mSamples ]; + memset( d, 0, sizeof(double) * mSamples ); + mBeamData.append( d ); + mBeamColor.append( color ); + + return true; +} + +void SignalPlotter::addSample( const QValueList<double>& sampleBuf ) +{ + if ( mBeamData.count() != sampleBuf.count() ) + return; + + double* d; + if ( mUseAutoRange ) { + double sum = 0; + for ( d = mBeamData.first(); d; d = mBeamData.next() ) { + sum += d[ 0 ]; + if ( sum < mMinValue ) + mMinValue = sum; + if ( sum > mMaxValue ) + mMaxValue = sum; + } + } + + /* If the vertical lines are scrolling, increment the offset + * so they move with the data. The vOffset / hScale confusion + * is because v refers to Vertical Lines, and h to the horizontal + * distance between the vertical lines. */ + if ( mVerticalLinesScroll ) { + mVerticalLinesOffset = ( mVerticalLinesOffset + mHorizontalScale) + % mVerticalLinesDistance; + } + + // Shift data buffers one sample down and insert new samples. + QValueList<double>::ConstIterator s; + for ( d = mBeamData.first(), s = sampleBuf.begin(); d; d = mBeamData.next(), ++s ) { + memmove( d, d + 1, ( mSamples - 1 ) * sizeof( double ) ); + d[ mSamples - 1 ] = *s; + } + + update(); +} + +void SignalPlotter::changeRange( int beam, double min, double max ) +{ + // Only the first beam affects range calculation. + if ( beam > 1 ) + return; + + mMinValue = min; + mMaxValue = max; +} + +QValueList<QColor> &SignalPlotter::beamColors() +{ + return mBeamColor; +} + +void SignalPlotter::removeBeam( uint pos ) +{ + mBeamColor.remove( mBeamColor.at( pos ) ); + mBeamData.remove( pos ); +} + +void SignalPlotter::setTitle( const QString &title ) +{ + mTitle = title; +} + +QString SignalPlotter::title() const +{ + return mTitle; +} + +void SignalPlotter::setUseAutoRange( bool value ) +{ + mUseAutoRange = value; +} + +bool SignalPlotter::useAutoRange() const +{ + return mUseAutoRange; +} + +void SignalPlotter::setMinValue( double min ) +{ + mMinValue = min; +} + +double SignalPlotter::minValue() const +{ + return ( mUseAutoRange ? 0 : mMinValue ); +} + +void SignalPlotter::setMaxValue( double max ) +{ + mMaxValue = max; +} + +double SignalPlotter::maxValue() const +{ + return ( mUseAutoRange ? 0 : mMaxValue ); +} + +void SignalPlotter::setGraphStyle( uint style ) +{ + mGraphStyle = style; +} + +uint SignalPlotter::graphStyle() const +{ + return mGraphStyle; +} + +void SignalPlotter::setHorizontalScale( uint scale ) +{ + if (scale == mHorizontalScale) + return; + + mHorizontalScale = scale; + if (isVisible()) + updateDataBuffers(); +} + +uint SignalPlotter::horizontalScale() const +{ + return mHorizontalScale; +} + +void SignalPlotter::setShowVerticalLines( bool value ) +{ + mShowVerticalLines = value; +} + +bool SignalPlotter::showVerticalLines() const +{ + return mShowVerticalLines; +} + +void SignalPlotter::setVerticalLinesColor( const QColor &color ) +{ + mVerticalLinesColor = color; +} + +QColor SignalPlotter::verticalLinesColor() const +{ + return mVerticalLinesColor; +} + +void SignalPlotter::setVerticalLinesDistance( int distance ) +{ + mVerticalLinesDistance = distance; +} + +int SignalPlotter::verticalLinesDistance() const +{ + return mVerticalLinesDistance; +} + +void SignalPlotter::setVerticalLinesScroll( bool value ) +{ + mVerticalLinesScroll = value; +} + +bool SignalPlotter::verticalLinesScroll() const +{ + return mVerticalLinesScroll; +} + +void SignalPlotter::setShowHorizontalLines( bool value ) +{ + mShowHorizontalLines = value; +} + +bool SignalPlotter::showHorizontalLines() const +{ + return mShowHorizontalLines; +} + +void SignalPlotter::setHorizontalLinesColor( const QColor &color ) +{ + mHorizontalLinesColor = color; +} + +QColor SignalPlotter::horizontalLinesColor() const +{ + return mHorizontalLinesColor; +} + +void SignalPlotter::setHorizontalLinesCount( int count ) +{ + mHorizontalLinesCount = count; +} + +int SignalPlotter::horizontalLinesCount() const +{ + return mHorizontalLinesCount; +} + +void SignalPlotter::setShowLabels( bool value ) +{ + mShowLabels = value; +} + +bool SignalPlotter::showLabels() const +{ + return mShowLabels; +} + +void SignalPlotter::setShowTopBar( bool value ) +{ + mShowTopBar = value; +} + +bool SignalPlotter::showTopBar() const +{ + return mShowTopBar; +} + +void SignalPlotter::setFontSize( int size ) +{ + mFontSize = size; +} + +int SignalPlotter::fontSize() const +{ + return mFontSize; +} + +void SignalPlotter::setBackgroundColor( const QColor &color ) +{ + mBackgroundColor = color; +} + +QColor SignalPlotter::backgroundColor() const +{ + return mBackgroundColor; +} + +void SignalPlotter::resizeEvent( QResizeEvent* ) +{ + Q_ASSERT( width() > 2 ); + + updateDataBuffers(); +} + +void SignalPlotter::updateDataBuffers() +{ + /* Since the data buffers for the beams are equal in size to the + * width of the widget minus 2 we have to enlarge or shrink the + * buffers accordingly when a resize occures. To have a nicer + * display we try to keep as much data as possible. Data that is + * lost due to shrinking the buffers cannot be recovered on + * enlarging though. */ + + /* Determine new number of samples first. + * +0.5 to ensure rounding up + * +2 for extra data points so there is + * 1) no wasted space and + * 2) no loss of precision when drawing the first data point. */ + uint newSampleNum = static_cast<uint>( ( ( width() - 2 ) / + mHorizontalScale ) + 2.5 ); + + // overlap between the old and the new buffers. + int overlap = min( mSamples, newSampleNum ); + + for ( uint i = 0; i < mBeamData.count(); ++i ) { + double* nd = new double[ newSampleNum ]; + + // initialize new part of the new buffer + if ( newSampleNum > (uint)overlap ) + memset( nd, 0, sizeof( double ) * ( newSampleNum - overlap ) ); + + // copy overlap from old buffer to new buffer + memcpy( nd + ( newSampleNum - overlap ), mBeamData.at( i ) + + ( mSamples - overlap ), overlap * sizeof( double ) ); + + mBeamData.remove( i ); + mBeamData.insert( i, nd ); + } + + mSamples = newSampleNum; +} + +void SignalPlotter::paintEvent( QPaintEvent* ) +{ + uint w = width(); + uint h = height(); + + /* Do not do repaints when the widget is not yet setup properly. */ + if ( w <= 2 ) + return; + + QPixmap pm( w, h ); + QPainter p; + p.begin( &pm, this ); + + pm.fill( mBackgroundColor ); + /* Draw white line along the bottom and the right side of the + * widget to create a 3D like look. */ + p.setPen( QColor( colorGroup().light() ) ); + p.drawLine( 0, h - 1, w - 1, h - 1 ); + p.drawLine( w - 1, 0, w - 1, h - 1 ); + + p.setClipRect( 1, 1, w - 2, h - 2 ); + double range = mMaxValue - mMinValue; + + /* If the range is too small we will force it to 1.0 since it + * looks a lot nicer. */ + if ( range < 0.000001 ) + range = 1.0; + + double minValue = mMinValue; + if ( mUseAutoRange ) { + if ( mMinValue != 0.0 ) { + double dim = pow( 10, floor( log10( fabs( mMinValue ) ) ) ) / 2; + if ( mMinValue < 0.0 ) + minValue = dim * floor( mMinValue / dim ); + else + minValue = dim * ceil( mMinValue / dim ); + range = mMaxValue - minValue; + if ( range < 0.000001 ) + range = 1.0; + } + // Massage the range so that the grid shows some nice values. + double step = range / mHorizontalLinesCount; + double dim = pow( 10, floor( log10( step ) ) ) / 2; + range = dim * ceil( step / dim ) * mHorizontalLinesCount; + } + double maxValue = minValue + range; + + int top = 0; + if ( mShowTopBar && h > ( mFontSize + 2 + mHorizontalLinesCount * 10 ) ) { + /* Draw horizontal bar with current sensor values at top of display. */ + p.setPen( mHorizontalLinesColor ); + int x0 = w / 2; + p.setFont( QFont( p.font().family(), mFontSize ) ); + top = p.fontMetrics().height(); + h -= top; + int h0 = top - 2; + + // JJ 2005-07-18: show numerical in/out values in the top bar ---> + double *d1 = mBeamData.first(); + double UploadSpeed = 0; + if(d1) + UploadSpeed = d1[ w - 3 ]; // read value from graph data + + double *d2 = mBeamData.next(); + double DownloadSpeed = 0; + if(d2) + DownloadSpeed = d2[ w - 3 ]; // read value from graph data + + // The left side of the top bar is now divided into three sections: + // - name of interface (original title) + // - download speed (numerically, from the value shown by the bar on the right) + // - upload speed (numerically, from the value shown by the bar on the right) + + // title + p.drawText(0, 0, x0/3, top - 2, Qt::AlignCenter, mTitle ); + + QValueList<QColor>::Iterator col_speeds; + col_speeds = mBeamColor.begin(); + QColor UploadColor = *(col_speeds++); + QColor DownloadColor = *(col_speeds); + + // download speed + QString DownloadSpeedText; + DownloadSpeedText.sprintf("in: %0.2f KB/s", DownloadSpeed); + p.setPen( DownloadColor ); + p.drawText(x0/3, 0, x0/3, top - 2, Qt::AlignCenter, DownloadSpeedText ); + + // upload speed + QString UploadSpeedText; + UploadSpeedText.sprintf("out: %0.2f KB/s", UploadSpeed); + p.setPen( UploadColor ); + p.drawText(2*x0/3, 0, x0/3, top - 2, Qt::AlignCenter, UploadSpeedText ); + + // restore correct pen color for the separator lines + p.setPen( mHorizontalLinesColor ); + // <--- JJ 2005-07-18 + + p.drawLine( x0 - 1, 1, x0 - 1, h0 ); + p.drawLine( 0, top - 1, w - 2, top - 1 ); + + double bias = -minValue; + double scaleFac = ( w - x0 - 2 ) / range; + QValueList<QColor>::Iterator col; + col = mBeamColor.begin(); + for ( double* d = mBeamData.first(); d; d = mBeamData.next(), ++col ) { + int start = x0 + (int)( bias * scaleFac ); + int end = x0 + (int)( ( bias += d[ w - 3 ] ) * scaleFac ); + /* If the rect is wider than 2 pixels we draw only the last + * pixels with the bright color. The rest is painted with + * a 50% darker color. */ + if ( end - start > 1 ) { + p.setPen( (*col).dark( 150 ) ); + p.setBrush( (*col).dark( 150 ) ); + p.drawRect( start, 1, end - start, h0 ); + p.setPen( *col ); + p.drawLine( end, 1, end, h0 ); + } else if ( start - end > 1 ) { + p.setPen( (*col).dark( 150 ) ); + p.setBrush( (*col).dark( 150 ) ); + p.drawRect( end, 1, start - end, h0 ); + p.setPen( *col ); + p.drawLine( end, 1, end, h0 ); + } else { + p.setPen( *col ); + p.drawLine( start, 1, start, h0 ); + } + } + } + + /* Draw scope-like grid vertical lines */ + if ( mShowVerticalLines && w > 60 ) { + p.setPen( mVerticalLinesColor ); + for ( uint x = mVerticalLinesOffset; x < ( w - 2 ); x += mVerticalLinesDistance ) + p.drawLine( w - x, top, w - x, h + top - 2 ); + } + + /* In autoRange mode we determine the range and plot the values in + * one go. This is more efficiently than running through the + * buffers twice but we do react on recently discarded samples as + * well as new samples one plot too late. So the range is not + * correct if the recently discarded samples are larger or smaller + * than the current extreme values. But we can probably live with + * this. */ + if ( mUseAutoRange ) + mMinValue = mMaxValue = 0.0; + + /* Plot stacked values */ + double scaleFac = ( h - 2 ) / range; + if ( mGraphStyle == GRAPH_ORIGINAL ) { + int xPos = 0; + for ( int i = 0; i < mSamples; i++, xPos += mHorizontalScale ) { + double bias = -minValue; + QValueList<QColor>::Iterator col; + col = mBeamColor.begin(); + double sum = 0.0; + for ( double* d = mBeamData.first(); d; d = mBeamData.next(), ++col ) { + if ( mUseAutoRange ) { + sum += d[ i ]; + if ( sum < mMinValue ) + mMinValue = sum; + if ( sum > mMaxValue ) + mMaxValue = sum; + } + int start = top + h - 2 - (int)( bias * scaleFac ); + int end = top + h - 2 - (int)( ( bias + d[ i ] ) * scaleFac ); + bias += d[ i ]; + /* If the line is longer than 2 pixels we draw only the last + * 2 pixels with the bright color. The rest is painted with + * a 50% darker color. */ + if ( end - start > 2 ) { + p.fillRect( xPos, start, mHorizontalScale, end - start - 1, (*col).dark( 150 ) ); + p.fillRect( xPos, end - 1, mHorizontalScale, 2, *col ); + } else if ( start - end > 2 ) { + p.fillRect( xPos, start, mHorizontalScale, end - start + 1, (*col).dark( 150 ) ); + p.fillRect( xPos, end + 1, mHorizontalScale, 2, *col ); + } else + p.fillRect( xPos, start, mHorizontalScale, end - start, *col ); + + } + } + } else if ( mGraphStyle == GRAPH_POLYGON ) { + int *prevVals = new int[ mBeamData.count() ]; + int hack[ 4 ] = { 0, 0, 0, 0 }; + int x1 = w - ( ( mSamples + 1 ) * mHorizontalScale ); + + for ( int i = 0; i < mSamples; i++ ) { + QValueList<QColor>::Iterator col; + col = mBeamColor.begin(); + double sum = 0.0; + int y = top + h - 2; + int oldY = top + h; + int oldPrevY = oldY; + int height = 0; + int j = 0; + int jMax = mBeamData.count() - 1; + x1 += mHorizontalScale; + int x2 = x1 + mHorizontalScale; + + for ( double* d = mBeamData.first(); d; d = mBeamData.next(), ++col, j++ ) { + if ( mUseAutoRange ) { + sum += d[ i ]; + if ( sum < mMinValue ) + mMinValue = sum; + if ( sum > mMaxValue ) + mMaxValue = sum; + } + height = (int)( ( d[ i ] - minValue ) * scaleFac ); + y -= height; + + /* If the line is longer than 2 pixels we draw only the last + * 2 pixels with the bright color. The rest is painted with + * a 50% darker color. */ + QPen lastPen = QPen( p.pen() ); + p.setPen( (*col).dark( 150 ) ); + p.setBrush( (*col).dark( 150 ) ); + QPointArray pa( 4 ); + int prevY = ( i == 0 ) ? y : prevVals[ j ]; + pa.putPoints( 0, 1, x1, prevY ); + pa.putPoints( 1, 1, x2, y ); + pa.putPoints( 2, 1, x2, oldY ); + pa.putPoints( 3, 1, x1, oldPrevY ); + p.drawPolygon( pa ); + p.setPen( lastPen ); + if ( jMax == 0 ) { + // draw as normal, no deferred drawing req'd. + p.setPen( *col ); + p.drawLine( x1, prevY, x2, y ); + } else if ( j == jMax ) { + // draw previous values and current values + p.drawLine( hack[ 0 ], hack[ 1 ], hack[ 2 ], hack[ 3 ] ); + p.setPen( *col ); + p.drawLine( x1, prevY, x2, y ); + } else if ( j == 0 ) { + // save values only + hack[ 0 ] = x1; + hack[ 1 ] = prevY; + hack[ 2 ] = x2; + hack[ 3 ] = y; + p.setPen( *col ); + } else { + p.drawLine( hack[ 0 ], hack[ 1 ], hack[ 2 ], hack[ 3 ] ); + hack[ 0 ] = x1; + hack[ 1 ] = prevY; + hack[ 2 ] = x2; + hack[ 3 ] = y; + p.setPen( *col ); + } + + prevVals[ j ] = y; + oldY = y; + oldPrevY = prevY; + } + } + + delete[] prevVals; + } + + /* Draw horizontal lines and values. Lines are drawn when the + * height is greater than 10 times hCount + 1, values are shown + * when width is greater than 60 */ + if ( mShowHorizontalLines && h > ( 10 * ( mHorizontalLinesCount + 1 ) ) ) { + p.setPen( mHorizontalLinesColor ); + p.setFont( QFont( p.font().family(), mFontSize ) ); + QString val; + for ( uint y = 1; y < mHorizontalLinesCount; y++ ) { + p.drawLine( 0, top + y * ( h / mHorizontalLinesCount ), w - 2, + top + y * ( h / mHorizontalLinesCount ) ); + if ( mShowLabels && h > ( mFontSize + 1 ) * ( mHorizontalLinesCount + 1 ) + && w > 60 ) { + val = QString( "%1" ).arg( maxValue - y * ( range / mHorizontalLinesCount ) ); + p.drawText( 6, top + y * ( h / mHorizontalLinesCount ) - 1, val ); + } + } + + if ( mShowLabels && h > ( mFontSize + 1 ) * ( mHorizontalLinesCount + 1 ) + && w > 60 ) { + val = QString( "%1" ).arg( minValue ); + p.drawText( 6, top + h - 2, val ); + } + } + + p.end(); + bitBlt( this, 0, 0, &pm ); +} + +#include "signalplotter.moc" |