You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
knemo/src/knemod/signalplotter.cpp

707 lines
20 KiB

/*
KSysGuard, the KDE System Guard
Copyright (c) 1999 - 2002 Chris Schlaeger <cs@kde.org>
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 <cs@kde.org>.
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 <tqpainter.h>
#include <tqpixmap.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( TQWidget *parent, const char *name )
: TQDialog( 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 );
tqsetSizePolicy( TQSizePolicy( TQSizePolicy::Expanding,
TQSizePolicy::Expanding, false ) );
mShowVerticalLines = true;
mVerticalLinesColor = TQColor( 0x04FB1D );
mVerticalLinesDistance = 30;
mVerticalLinesScroll = true;
mVerticalLinesOffset = 0;
mHorizontalScale = 1;
mShowHorizontalLines = true;
mHorizontalLinesColor = TQColor( 0x04FB1D );
mHorizontalLinesCount = 5;
mShowLabels = true;
mShowTopBar = false;
mFontSize = 8;
mBackgroundColor = TQColor( 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;
TQDialog::hide();
}
void SignalPlotter::show()
{
TQDialog::show();
/**
* mPosInitialized should always be true, except when
* starting KNemo for the very first time.
*/
if ( mPosInitialized )
move( mPos );
}
bool SignalPlotter::addBeam( const TQColor &color )
{
double* d = new double[ mSamples ];
memset( d, 0, sizeof(double) * mSamples );
mBeamData.append( d );
mBeamColor.append( color );
return true;
}
void SignalPlotter::addSample( const TQValueList<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 toQt::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.
TQValueList<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;
}
TQValueList<TQColor> &SignalPlotter::beamColors()
{
return mBeamColor;
}
void SignalPlotter::removeBeam( uint pos )
{
mBeamColor.remove( mBeamColor.at( pos ) );
mBeamData.remove( pos );
}
void SignalPlotter::setTitle( const TQString &title )
{
mTitle = title;
}
TQString 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 TQColor &color )
{
mVerticalLinesColor = color;
}
TQColor 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 TQColor &color )
{
mHorizontalLinesColor = color;
}
TQColor 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 TQColor &color )
{
mBackgroundColor = color;
}
TQColor SignalPlotter::backgroundColor() const
{
return mBackgroundColor;
}
void SignalPlotter::resizeEvent( TQResizeEvent* )
{
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( TQPaintEvent* )
{
uint w = width();
uint h = height();
/* Do not do repaints when the widget is not yet setup properly. */
if ( w <= 2 )
return;
TQPixmap pm( w, h );
TQPainter p;
p.tqbegin( TQT_TQPAINTDEVICE(&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( TQColor( tqcolorGroup().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( TQFont( 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, TQt::AlignCenter, mTitle );
TQValueList<TQColor>::Iterator col_speeds;
col_speeds = mBeamColor.begin();
TQColor UploadColor = *(col_speeds++);
TQColor DownloadColor = *(col_speeds);
// download speed
TQString DownloadSpeedText;
DownloadSpeedText.sprintf("in: %0.2f KB/s", DownloadSpeed);
p.setPen( DownloadColor );
p.drawText(x0/3, 0, x0/3, top - 2, TQt::AlignCenter, DownloadSpeedText );
// upload speed
TQString UploadSpeedText;
UploadSpeedText.sprintf("out: %0.2f KB/s", UploadSpeed);
p.setPen( UploadColor );
p.drawText(2*x0/3, 0, x0/3, top - 2, TQt::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;
TQValueList<TQColor>::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;
TQValueList<TQColor>::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++ ) {
TQValueList<TQColor>::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. */
TQPen lastPen = TQPen( p.pen() );
p.setPen( (*col).dark( 150 ) );
p.setBrush( (*col).dark( 150 ) );
TQPointArray 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( TQFont( p.font().family(), mFontSize ) );
TQString 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 = TQString( "%1" ).tqarg( maxValue - y * ( range / mHorizontalLinesCount ) );
p.drawText( 6, top + y * ( h / mHorizontalLinesCount ) - 1, val );
}
}
if ( mShowLabels && h > ( mFontSize + 1 ) * ( mHorizontalLinesCount + 1 )
&& w > 60 ) {
val = TQString( "%1" ).tqarg( minValue );
p.drawText( 6, top + h - 2, val );
}
}
p.end();
bitBlt( this, 0, 0, &pm );
}
#include "signalplotter.moc"