qtdmm/src/dmmgraph.cpp

1743 lines
40 KiB
C++
Executable File

//======================================================================
// File: dmmgraph.cpp
// Author: Matthias Toussaint
// Created: Tue Apr 10 17:45:35 CEST 2001
//----------------------------------------------------------------------
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose and without fee is hereby granted,
// provided that below copyright notice appear in all copies and that
// both that copyright notice and this permission notice appear in
// supporting documentation.
//
// This file is provided AS IS with no warranties of any kind. The
// author shall have no liability with respect to the infringement of
// copyrights, trade secrets or any patents by this file or any part
// thereof. In no event will the author be liable for any lost revenue
// or profits or other special, indirect and consequential damages.
//----------------------------------------------------------------------
// (c) 2001-2009 Matthias Toussaint
//======================================================================
#include <qapplication.h>
#include <qcursor.h>
#include <qdatetime.h>
#include <qfile.h>
#include <qfiledialog.h>
#include <qlabel.h>
#include <qmessagebox.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <Q3PopupMenu>
#include <qprinter.h>
#include <qregexp.h>
#include <qscrollbar.h>
#include <qtextstream.h>
#include <qtooltip.h>
#include <qstringlist.h>
#include <QWidget>
#include <QMouseEvent>
#include <QWheelEvent>
#include <dmmgraph.h>
#include <math.h> // RedHat needs it
#include <cstdlib>
#include <iostream>
#include <icon.xpm>
DMMGraph::DMMGraph( QWidget *parent, const char *name ) :
QWidget( parent, name ),
m_size( 600 ),
m_length( 3600 ),
m_scaleMin( 0 ),
m_scaleMax( 0 ),
m_autoScale( true ),
m_array(m_length),
m_arrayInt(m_length),
m_pointer( 0 ),
m_ystep( 1 ),
m_sampleTime( 1 ),
m_sampleLength( 0 ),
m_running( false ),
m_connected( false ),
m_mode( DMMGraph::Manual ),
m_mouseDown( false ),
m_lastValValid( false ),
m_dirty( false ),
m_alertUnsaved( true ),
m_externalStarted( false ),
m_crosshair( true ),
m_pointMode( Circle ),
m_intPointMode( Square ),
m_lineMode( Solid ),
m_intLineMode( NoLine ),
m_drawArray( m_length ),
m_integrationScale( 1.0 ),
m_integrationThreshold( 0.0 ),
m_integrationOffset( 0.0 ),
m_includeZero( false ),
m_mousePan( false )
{
scrollbar = new QScrollBar( Qt::Horizontal, this );
scrollbar->setGeometry( 0, height()-16, width(), 16 );
scrollbar->setTracking( true );
scrollbar->setCursor( Qt::ArrowCursor );
connect( scrollbar, SIGNAL( valueChanged(int) ),
this, SLOT( update() ));
m_remainingLength = m_sampleLength;
emitInfo();
m_infoBox = new QLabel( 0, 0, Qt::FramelessWindowHint |
Qt::Tool );
m_infoBox->resize( 100, 50 );
m_infoBox->setFrameStyle( QFrame::Box | QFrame::Plain );
m_infoBox->setPalette( QToolTip::palette() );
startTimer( 200 );
setMouseTracking( true );
m_popup = new Q3PopupMenu( this );
connect( m_popup, SIGNAL( activated(int) ),
this, SLOT( popupSLOT(int) ));
}
DMMGraph::~DMMGraph()
{
}
void
DMMGraph::timerEvent( QTimerEvent * )
{
update();
}
void
DMMGraph::paintEvent( QPaintEvent * )
{
QPixmap pix( width(), height()-16 );
QPainter p;
p.begin( &pix );
paint( &p, width(), height()-16, m_xfactor, m_xstep, m_yfactor, m_ystep,
m_maxUnit, m_hUnitFact, m_hUnit, true, false );
p.end();
p.begin( this );
p.drawPixmap( 0, 0, pix );
if (m_mouseDown)
{
drawCursor( m_mpos );
fillInfoBox( m_mpos );
}
}
void
DMMGraph::print( QPrinter *prt, const QString & title, const QString & comment )
{
if (!title.isEmpty())
{
prt->setDocName( title );
}
else
{
QString tmpStr;
tmpStr.sprintf( "QtDMM: %s", QDateTime::currentDateTime().toString().latin1() );
}
prt->setCreator( "QtDMM: (c) 2001 Matthias Toussaint" );
prt->setPrintProgram( "QtDMM: (c) 2001 Matthias Toussaint" );
QPainter p;
p.begin( prt );
int w = width();
int h = height();
p.setFont( QFont( "Helvetica", 16 ) );
QRect tRect = p.boundingRect( 0, 0, w, h, Qt::AlignTop | Qt::AlignHCenter | Qt::WordBreak, title );
p.drawText( tRect, Qt::AlignTop | Qt::AlignHCenter | Qt::WordBreak, title );
p.setFont( QFont( "Helvetica", 10 ));
QFontMetrics fm = p.fontMetrics();
int maxWidth = QMAX( fm.width( tr( "Sampling start:" ) ),
fm.width( tr( "Sampling resolution:" )));
int tHeight = fm.height();
p.drawText( 0, tRect.height()+10, maxWidth, tHeight, Qt::AlignLeft | Qt::AlignVCenter,
tr( "Sampling start:" ) );
p.drawText( maxWidth+10, tRect.height()+10, w-maxWidth-10, tHeight, Qt::AlignLeft | Qt::AlignVCenter,
m_graphStartDateTime.toString() );
p.drawText( 0, tRect.height()+10+tHeight, maxWidth, tHeight, Qt::AlignLeft | Qt::AlignVCenter,
tr( "Sampling resolution:" ) );
QString tmpStr;
tmpStr.sprintf( "%d %s", m_sampleTime, tr( "Seconds" ).latin1() );
p.drawText( maxWidth+10, tRect.height()+10+tHeight,
w-maxWidth-10, tHeight, Qt::AlignLeft | Qt::AlignVCenter,
tmpStr );
//p.setFont( QFont( "Helvetica", 10 ));
QRect cRect = p.boundingRect( 0, 0, w, h, Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak, comment );
p.drawText( 0, tRect.height()+20+2*tHeight, w, cRect.height(),
Qt::AlignLeft | Qt::AlignTop | Qt::WordBreak, comment );
h -= tRect.height()+30+2*tHeight+cRect.height();
double yfactor, ystep;
yfactor = createYScale( h, ystep );
double xfactor, xstep, hUnitFact, maxUnit;
QString hUnit;
xfactor = createTimeScale( w, xstep, hUnitFact, maxUnit, hUnit );
p.setClipRect( 0, tRect.height()+30+2*tHeight+cRect.height(), w, h );
p.setClipping( true );
p.translate( 0, tRect.height()+30+2*tHeight+cRect.height() );
paint( &p, w, h, xfactor, xstep, yfactor, ystep,
maxUnit, hUnitFact, hUnit, prt->colorMode() == QPrinter::Color, true );
p.end();
}
void
DMMGraph::paint( QPainter *p, int w, int h,
double xfactor, double xstep,
double yfactor, double ystep,
double maxUnit, double hUnitFact, const QString & hUnit,
bool color, bool printer )
{
p->setBrush( m_bgColor );
p->setPen( Qt::black );
p->drawRect( 0, 0, w, h );
if (m_scaleMin != m_scaleMax)
{
paintHorizontalGrid( p, yfactor, ystep, color );
paintVerticalGrid( p, xfactor, xstep, maxUnit, hUnitFact, hUnit, color );
paintData( p, xfactor, yfactor, color, printer );
paintThresholds( p, xfactor, yfactor, color, printer );
}
}
void
DMMGraph::paintHorizontalGrid( QPainter *p, double yfactor, double ystep, bool color )
{
if (color)
{
p->setPen( m_gridColor );
}
else
{
p->setPen( Qt::black );
}
// draw border
//
p->drawRect( m_graphRect );
// draw zero line
//
int y = (int)qRound( m_graphRect.y()+(m_scaleMax)/yfactor );
if (y>m_graphRect.y()+m_fontHeight && y<m_graphRect.y()+m_graphRect.height())
{
p->drawLine( m_graphRect.x()-3, y, m_graphRect.x()+m_graphRect.width(), y );
}
if (color)
{
p->setPen( QPen( m_gridColor, 0, Qt::DotLine ) );
}
else
{
p->setPen( QPen( Qt::black, 0, Qt::DotLine ) );
}
QString scaleVal;
// Draw horizontal lines
// from 0 to max
//
double val = 0.0;
while (val < m_scaleMax)
{
y = (int)qRound( m_graphRect.y()+(m_scaleMax-val)/yfactor );
if (y>m_graphRect.y()+m_fontHeight && y<m_graphRect.y()+m_graphRect.height())
{
p->drawLine( m_graphRect.x()-3, y, m_graphRect.x()+m_graphRect.width(), y );
scaleVal.sprintf( "%g", val*m_factor );
p->drawText( 1, y-10, m_graphRect.x()-4, 20, Qt::AlignRight | Qt::AlignVCenter, scaleVal );
}
val += ystep;
}
// from 0 to min
//
val = -ystep;
while (val > m_scaleMin)
{
y = (int)qRound( m_graphRect.y()+(m_scaleMax-val)/yfactor );
if (y>m_graphRect.y()+m_fontHeight && y<m_graphRect.y()+m_graphRect.height())
{
p->drawLine( m_graphRect.x()-3, y, m_graphRect.x()+m_graphRect.width(), y );
scaleVal.sprintf( "%g", val*m_factor );
p->drawText( 1, y-10, m_graphRect.x()-4, 20, Qt::AlignRight | Qt::AlignVCenter, scaleVal );
}
val -= ystep;
}
if (!m_unit.isEmpty())
{
QString tmp = "[";
tmp += m_prefix;
tmp += m_unit;
tmp += "]";
p->drawText( 4, 4, 40, 20, Qt::AlignLeft | Qt::AlignTop, tmp ); //m_unit );
}
}
void
DMMGraph::paintVerticalGrid( QPainter *p, double xfactor, double xstep,
double maxUnit, double hUnitFact, const QString & hUnit, bool color )
{
int sv = QMAX( 0, scrollbar->value() );
if (color)
{
p->setPen( QPen( m_gridColor, 0, Qt::DotLine ) );
}
else
{
p->setPen( QPen( Qt::black, 0, Qt::DotLine ) );
}
QString scaleVal;
double val = xstep;
while (val < maxUnit)
{
int x = m_graphRect.x() + (int)qRound( 1+(val)/hUnitFact - sv/xfactor );
if (x > m_graphRect.x() && x < m_graphRect.x()+m_graphRect.width()-1)
{
p->drawLine( x, m_graphRect.y(), x, m_graphRect.y()+m_graphRect.height()+3 );
if (x > m_graphRect.x() && x < m_graphRect.x()+m_graphRect.width()-1)
{
scaleVal.sprintf( "%g", val );
p->drawText( x-20, m_graphRect.y()+m_graphRect.height()+3,
40, m_fontHeight-3, Qt::AlignCenter, scaleVal );
}
}
val += xstep;
}
p->drawText( width()-40, m_graphRect.y()+m_graphRect.height(),
36, 20, Qt::AlignRight | Qt::AlignVCenter, hUnit );
}
void
DMMGraph::paintData( QPainter *p, double xfactor,
double yfactor, bool color, bool printer )
{
p->setClipRect( m_graphRect );
int sv = QMAX( 0, scrollbar->value() );
// draw cursor
//
int x = (int)qRound( (m_pointer-sv-1)/xfactor ) + m_graphRect.x();
if (!printer && x>m_graphRect.x() && x <= m_graphRect.x()+m_graphRect.width()-1)
{
p->setPen( m_cursorColor );
p->drawLine( x, 6, x, m_graphRect.y()+m_graphRect.height()-1 );
}
// draw integration curve
//
int y;
int pCnt;
if (m_showIntegration)
{
y = (int)qRound( m_graphRect.y()+
(m_scaleMax-m_integrationOffset-
m_arrayInt[sv]*m_integrationScale)/yfactor );
if (color)
{
p->setPen( QPen( m_intColor, m_intLineWidth,
penStyle( m_intLineMode ), Qt::RoundCap, Qt::RoundJoin ) );
}
else
{
p->setPen( QPen( Qt::darkGray, m_intLineWidth,
penStyle( m_intLineMode ), Qt::RoundCap, Qt::RoundJoin ) );
}
pCnt = 1;
m_drawArray[0] = QPoint( m_graphRect.x(), y );
for (int i=sv+1; i<m_pointer; i++)
{
int x = (int)qRound( (i-sv)/xfactor ) + 51;
if (x <= m_graphRect.x()+m_graphRect.width())
{
y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_integrationOffset-
m_arrayInt[i]*m_integrationScale)/yfactor );
//y = (int)qRound( 1+(m_scaleMax-m_arrayInt[i])/yfactor );
m_drawArray[pCnt++] = QPoint( x, y );
}
}
if (pCnt)
p->drawPolyline( m_drawArray.data(), pCnt );
//y = (int)qRound( 1+(m_scaleMax-m_arrayInt[scrollbar->value()])/yfactor );
y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_integrationOffset-
m_arrayInt[sv]*m_integrationScale)/yfactor );
if (color)
{
p->setPen( m_intColor );
}
else
{
p->setPen( Qt::darkGray );
}
for (int i=sv+1; i<m_pointer; i++)
{
int x = (int)qRound( (i-sv)/xfactor ) + m_graphRect.x();
if (x <= m_graphRect.x()+m_graphRect.width())
{
y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_integrationOffset-
m_arrayInt[i]*m_integrationScale)/yfactor );
//y = (int)qRound( 1+(m_scaleMax-m_arrayInt[i])/yfactor );
drawPoint( m_intPointMode, p, x, y );
}
}
}
// draw data curve
//
y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_array[sv])/yfactor );
if (color)
{
p->setPen( QPen( m_dataColor, m_lineWidth,
penStyle( m_lineMode ), Qt::RoundCap, Qt::RoundJoin ) );
}
else
{
p->setPen( QPen( Qt::black, m_lineWidth,
penStyle( m_lineMode ), Qt::RoundCap, Qt::RoundJoin ) );
}
pCnt = 1;
m_drawArray[0] = QPoint( m_graphRect.x(), y );
for (int i=sv+1; i<m_pointer; i++)
{
int x = (int)qRound( (i-sv)/xfactor ) + m_graphRect.x();
if (x <= m_graphRect.x()+m_graphRect.width())
{
int y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_array[i])/yfactor );
m_drawArray[pCnt++] = QPoint( x, y );
}
}
if (pCnt)
p->drawPolyline( m_drawArray.data(), pCnt );
y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_array[sv])/yfactor );
if (color)
{
p->setPen( m_dataColor );
}
else
{
p->setPen( Qt::black );
}
for (int i=sv+1; i<m_pointer; i++)
{
int x = (int)qRound( (i-sv)/xfactor ) + m_graphRect.x();
if (x <= m_graphRect.x()+m_graphRect.width())
{
int y = (int)qRound( m_graphRect.y()+(m_scaleMax-m_array[i])/yfactor );
drawPoint( m_pointMode, p, x, y );
}
}
p->setClipping( false );
}
void
DMMGraph::paintThresholds( QPainter *p, double /* xfactor */,
double /*yfactor*/, bool color, bool printer )
{
if (m_startExternal)
{
if (printer && !color)
{
p->setPen( Qt::gray );
}
else
{
p->setPen( m_externalColor );
}
p->drawLine( m_graphRect.x(), m_externalThresholdY,
m_graphRect.x()+m_graphRect.width(), m_externalThresholdY );
}
if (m_showIntegration)
{
if (printer && !color)
{
p->setPen( Qt::darkGray );
}
else
{
p->setPen( m_intThresholdColor );
}
p->drawLine( m_graphRect.x(), m_integrationThresholdY,
m_graphRect.x()+m_graphRect.width(), m_integrationThresholdY );
}
if (m_mode == Raising || m_mode == Falling)
{
if (printer && !color)
{
p->setPen( Qt::gray );
}
else
{
p->setPen( m_startColor );
}
p->drawLine( m_graphRect.x(), m_triggerThresholdY,
m_graphRect.x()+m_graphRect.width(), m_triggerThresholdY );
}
}
void
DMMGraph::resizeEvent( QResizeEvent * )
{
QFontMetrics fm = fontMetrics();
int xOff = fm.width( "-999.99" );
m_fontHeight = fm.height() + 4;
m_graphRect = QRect(xOff, 5, width()-5-xOff, height()-16-5-m_fontHeight );
scrollbar->setGeometry( 0, height()-16, width(), 16 );
m_yfactor = createYScale( m_graphRect.height(), m_ystep );
m_xfactor = createTimeScale( m_graphRect.width(), m_xstep, m_hUnitFact,
m_maxUnit, m_hUnit );
m_externalThresholdY = (int)qRound( m_graphRect.y()+(m_scaleMax-m_externalThreshold)/m_yfactor );
m_integrationThresholdY = (int)qRound( m_graphRect.y()+(m_scaleMax-m_integrationThreshold)/m_yfactor );
double val = m_mode == Raising ? m_raisingThreshold : m_fallingThreshold;
m_triggerThresholdY = (int)qRound( m_graphRect.y()+(m_scaleMax-val)/m_yfactor );
}
void
DMMGraph::setGraphSize( int size, int length )
{
m_size = (int)((double)size/m_sampleTime*10.);
m_length = (int)((double)length/m_sampleTime*10. + 1);
scrollbar->setMinValue( 0 );
scrollbar->setMaxValue( m_length-1-m_size );
scrollbar->setLineStep( (m_size-1)/10 );
scrollbar->setPageStep( m_size );
m_array.resize( m_length );
m_arrayInt.resize( m_length );
if (m_pointer >= m_length)
{
m_pointer = m_length-1;
}
m_drawArray.resize( m_length );
emitInfo();
resizeEvent( 0 );
update();
}
void
DMMGraph::startSLOT()
{
m_sampleCounter = 0;
m_sum = 0;
clearSLOT();
m_running = true;
m_remainingLength = m_sampleLength;
m_pointer = 0;
emitInfo();
emit running( true );
m_graphStartDateTime = QDateTime::currentDateTime();
m_externalStarted = false;
m_arrayInt[0] = 0;
}
void
DMMGraph::stopSLOT()
{
m_running = false;
emitInfo();
emit running( false );
}
void
DMMGraph::addValue( double val )
{
if (m_mode == DMMGraph::Time && !m_running)
{
// we may miss the start due to aliasing otherwise
int diff = m_startTime.secsTo( QTime::currentTime() );
if (diff >= 0 && diff < 2)
{
qApp->beep();
startSLOT();
}
}
if (m_mode == DMMGraph::Raising && !m_running)
{
if (m_lastValValid)
{
if (m_lastVal < m_raisingThreshold && val >= m_raisingThreshold)
{
qApp->beep();
startSLOT();
}
}
}
if (m_mode == DMMGraph::Falling && !m_running)
{
if (m_lastValValid)
{
if (m_lastVal > m_fallingThreshold && val <= m_fallingThreshold)
{
qApp->beep();
startSLOT();
}
}
}
if (!m_externalStarted && m_running && m_startExternal)
{
if (m_externalFalling &&
m_lastVal > m_externalThreshold &&
val <= m_externalThreshold)
{
m_externalStarted = true;
emit externalTriggered();
}
else if (!m_externalFalling &&
m_lastVal < m_externalThreshold &&
val >= m_externalThreshold)
{
m_externalStarted = true;
emit externalTriggered();
}
}
m_lastValValid = true;
m_lastVal = val;
if (!m_running) return;
m_sum += val;
if (0 == m_sampleCounter)
{
m_dirty = true;
if (!m_first)
{
val = m_sum / (double)m_sampleTime;
}
m_first = false;
m_sum = 0.0;
if (m_pointer >= m_length)
{
for (int i=1; i<m_length; i++)
{
m_array[i-1] = m_array[i];
m_arrayInt[i-1] = m_arrayInt[i];
}
m_pointer = m_length-1;
}
if (m_pointer > 0)
{
if (m_lastVal <= m_integrationThreshold)
{
m_arrayInt[m_pointer] = 0.0;
}
else
{
m_arrayInt[m_pointer] = m_arrayInt[m_pointer-1]+val;
}
}
else
{
m_arrayInt[m_pointer] = QMAX( val, m_integrationThreshold );
}
m_array[m_pointer++] = val;
bool resFlag = false;
if (m_autoScale)
{
resFlag = computeMinMax( val );
//cerr << "val=" << val << " min=" << m_scaleMin << " max=" << m_scaleMax << endl;
computeUnitFactor();
}
if (resFlag)
resizeEvent( 0 );
//update();
}
m_sampleCounter++;
m_remainingLength = QMAX( 0, m_remainingLength-1 );
if (m_sampleCounter == m_sampleTime)
{
m_sampleCounter = 0;
emitInfo();
}
if (0 == m_remainingLength && m_sampleLength != 0)
{
qApp->beep();
stopSLOT();
return;
}
}
double
DMMGraph::createYScale( int h, double & ystep )
{
double yfactor = (m_scaleMax-m_scaleMin) / (double)h;
double idiv = (double)(h-2)/(double)(3*m_fontHeight);
double ddiv = (m_scaleMax-m_scaleMin)/idiv;
// try to find 1-2-5 division between 1e-18 and 1e18
//
double one = 1e-18;
double two = 2e-18;
double five = 5e-18;
bool gotcha = false;
do
{
if (ddiv > one && ddiv <= two)
{
ystep = one;
gotcha = true;
}
else if (ddiv > two && ddiv <= five)
{
ystep = two;
gotcha = true;
}
else if (ddiv > five && ddiv < one*10.)
{
ystep = five;
gotcha = true;
}
one *= 10.;
two *= 10.;
five *= 10.;
}
while (!gotcha && one < 1e18);
return yfactor;
}
// returns xfactor
double
DMMGraph::createTimeScale( int w, double & xstep, double & hUnitFact,
double & maxUnit, QString & hUnit )
{
double xfactor = (double)m_size/(double)(w-52);
double idiv = (double)(w-2-50)/80.;
double ddiv = m_size*m_sampleTime/10./idiv;
hUnit = tr( "[sec]" );
hUnitFact = xfactor*m_sampleTime/10.;
maxUnit = (m_length-1)*m_sampleTime/10.;
if (ddiv > 60)
{
ddiv /= 60;
hUnit = tr( "[min]" );
hUnitFact /= 60;
maxUnit /= 60;
if (ddiv > 60)
{
ddiv /= 60;
hUnitFact /= 60;
maxUnit /= 60;
hUnit = tr( "[hour]" );
if (ddiv > 24)
{
ddiv /= 24;
hUnitFact /= 24;
maxUnit /= 24;
hUnit = tr( "[day]" );
}
}
}
// try to find 1-2-5 division between 1 and 1e9
//
double one = 1;
double two = 2;
double five = 5;
bool gotcha = false;
do
{
if (ddiv > one && ddiv <= two)
{
xstep = one;
gotcha = true;
}
else if (ddiv > two && ddiv <= five)
{
xstep = two;
gotcha = true;
}
else if (ddiv > five && ddiv < one*10.)
{
xstep = five;
gotcha = true;
}
one *= 10.;
two *= 10.;
five *= 10.;
}
while (!gotcha && one < 1e9);
return xfactor;
}
void
DMMGraph::setUnit( const QString & unit )
{
if (unit.left(1) == "n")
{
m_unit = unit.mid( 1 );
}
else if (unit.left(1) == "u")
{
m_unit = unit.mid( 1 );
}
else if (unit.left(1) == "m")
{
m_unit = unit.mid( 1 );
}
else if (unit.left(1) == "k")
{
m_unit = unit.mid( 1 );
}
else if (unit.left(1) == "M")
{
m_unit = unit.mid( 1 );
}
else
{
m_unit = unit;
}
return;
}
void
DMMGraph::clearSLOT()
{
m_pointer = 0;
if (m_autoScale)
{
if (m_includeZero)
{
m_scaleMin = m_scaleMax = 0;
}
else
{
m_scaleMin = 1e40;
m_scaleMax = -1e40;
}
}
m_graphStartDateTime = QDateTime::currentDateTime();
m_first = true;
m_dirty = false;
update();
}
void
DMMGraph::emitInfo()
{
int seconds = m_remainingLength / 10;
int w = seconds / 60 / 60 / 24 / 7;
int d = ( seconds / 60 / 60 / 24 ) % 7;
int h = ( seconds / 60 / 60 ) % (24);
int m = (seconds / 60 ) % 60;
int s = seconds % 60;
QString txt;
if (w)
{
txt.sprintf( "%d/%d - %dweek%s %dday%s %02d:%02d:%02d - %s",
m_pointer, m_length, w, (w>1 ? "s" : ""), d, (d>1 ? "s" : ""), h, m, s,
m_running ? tr( "Sampling" ).latin1() : tr( "Stopped" ).latin1() );
}
else if (d)
{
txt.sprintf( "%d/%d - %dday%s %02d:%02d:%02d - %s",
m_pointer, m_length, d, (d>1 ? "s" : ""), h, m, s,
m_running ? tr( "Sampling" ).latin1() : tr( "Stopped" ).latin1() );
}
else if (h)
{
txt.sprintf( "%d/%d - %02d:%02d:%02d - %s",
m_pointer, m_length, h, m, s,
m_running ? tr( "Sampling" ).latin1() : tr( "Stopped" ).latin1() );
}
else
{
txt.sprintf( "%d/%d - %02d:%02d - %s",
m_pointer, m_length, m, s,
m_running ? tr( "Sampling" ).latin1() : tr( "Stopped" ).latin1() );
}
emit info( txt );
}
void
DMMGraph::wheelEvent( QWheelEvent *ev )
{
if (ev->delta() > 0)
{
emit zoomOut( 1.1 );
}
else
{
emit zoomIn( 1.1 );
}
}
void
DMMGraph::mousePressEvent( QMouseEvent *ev )
{
if (ev->button() == Qt::LeftButton)
{
if (m_scaleMin == m_scaleMax || m_scaleMin == 1e40) return;
if (ev->x() < m_graphRect.x()) return;
m_mouseDown = true;
m_mousePan = false;
if (m_cursorMode == NoCursor)
{
drawCursor( ev->pos() );
m_mpos = ev->pos();
m_infoBox->move( ev->globalPos().x()+4, ev->globalPos().y()+4 );
fillInfoBox( ev->pos() );
m_infoBox->show();
}
}
else if(ev->button() == Qt::RightButton)
{
m_popup->clear();
if (m_connected)
{
m_popup->insertItem( tr("Disconnect"), IDDisconnect );
}
else
{
m_popup->insertItem( tr("Connect"), IDConnect );
}
m_popup->insertSeparator();
if (m_running)
{
m_popup->insertItem( tr("Stop recorder"), IDStopRecorder );
}
else
{
m_popup->insertItem( tr("Start recorder"), IDStartRecorder );
}
m_popup->insertItem( tr("Clear graph"), IDClearGraph );
m_popup->insertSeparator();
m_popup->insertItem( tr("Configure..."), IDConfigure );
if (!m_running)
{
m_popup->insertSeparator();
m_popup->insertItem( tr("Export data..."), IDExportData );
m_popup->insertItem( tr("Import data..."), IDImportData );
}
m_popup->popup( ev->globalPos() );
}
else if (ev->button() == Qt::MidButton)
{
m_mouseDown = false;
m_mousePan = true;
m_mpos = ev->pos();
}
}
void
DMMGraph::mouseMoveEvent( QMouseEvent *ev )
{
if (m_scaleMin == m_scaleMax || m_scaleMin == 1e40) return;
int sv = QMAX( 0, scrollbar->value() );
if (!m_mouseDown)
{
if (m_mousePan)
{
//cerr << "delta: " << m_mpos.x()-ev->pos().x() << " " << m_mpos.y()-ev->pos().y() << endl;
double offset = (m_mpos.x()-ev->pos().x())*m_xfactor;
//cerr << "delta=" << m_mpos.x()-ev->pos().x() << " offset=" << offset << endl;
if (fabs(offset) >= 1)
{
scrollbar->setValue( QMIN( scrollbar->maxValue(), sv + (int)qRound(offset) ) );
m_mpos = ev->pos();
}
}
else
{
if (abs(ev->y()-m_triggerThresholdY) < 3)
{
setCursor( Qt::SplitVCursor );
m_cursorMode = Trigger;
}
else if (abs(ev->y()-m_externalThresholdY) < 3)
{
setCursor( Qt::SplitVCursor );
m_cursorMode = External;
}
else if (abs(ev->y()-m_integrationThresholdY) < 3)
{
setCursor( Qt::SplitVCursor );
m_cursorMode = Integration;
}
else
{
setCursor( Qt::ArrowCursor );
m_cursorMode = NoCursor;
}
}
}
else if (m_cursorMode == Trigger)
{
m_triggerThresholdY = ev->y();
if (m_mode == Raising)
{
m_raisingThreshold = m_scaleMax - ( m_triggerThresholdY - m_graphRect.y()) * m_yfactor;
emit thresholdChanged( Trigger, m_raisingThreshold );
}
else
{
m_fallingThreshold = m_scaleMax - ( m_triggerThresholdY - m_graphRect.y()) * m_yfactor;
emit thresholdChanged( Trigger, m_fallingThreshold );
}
update();
}
else if (m_cursorMode == External)
{
m_externalThresholdY = ev->y();
m_externalThreshold = m_scaleMax - ( m_externalThresholdY - m_graphRect.y()) * m_yfactor;
emit thresholdChanged( External, m_externalThreshold );
update();
}
else if (m_cursorMode == Integration)
{
m_integrationThresholdY = ev->y();
m_integrationThreshold = m_scaleMax - ( m_integrationThresholdY - m_graphRect.y()) * m_yfactor;
emit thresholdChanged( Integration, m_integrationThreshold );
update();
}
else if (m_cursorMode == NoCursor)
{
drawCursor( m_mpos );
if (ev->x() < 51)
{
drawCursor( QPoint( 51, ev->y() ));
m_mpos = QPoint( 51, ev->y() );
}
else
{
drawCursor( ev->pos() );
m_mpos = ev->pos();
}
m_infoBox->move( ev->globalPos().x()+4, ev->globalPos().y()+4 );
fillInfoBox( ev->pos() );
}
}
void
DMMGraph::mouseReleaseEvent( QMouseEvent * )
{
if (m_scaleMin == m_scaleMax || m_scaleMin == 1e40) return;
if (m_mouseDown)
{
drawCursor( m_mpos );
m_infoBox->hide();
}
m_mouseDown = false;
m_mousePan = false;
}
void
DMMGraph::drawCursor( const QPoint & pos )
{
QPainter p(this);
p.setCompositionMode( QPainter::RasterOp_SourceXorDestination );
p.setPen( Qt::white );
p.drawLine( pos.x(), m_graphRect.y(), pos.x(), m_graphRect.height()+m_graphRect.y()-1 );
int sv = QMAX( 0, scrollbar->value() );
if (m_crosshair)
{
int x = (int)qRound( (pos.x()-m_graphRect.x())*m_xfactor + sv );
if (x < 0) x = 0;
if (x < m_pointer)
{
double y = m_array[x];
int Y = (int)qRound( m_graphRect.y()+(m_scaleMax-y)/m_yfactor );
p.drawLine( m_graphRect.x(), Y, m_graphRect.x()+m_graphRect.width()-1, Y );
}
}
}
void
DMMGraph::fillInfoBox( const QPoint & pos )
{
int sv = QMAX( 0, scrollbar->value() );
int x = (int)qRound( (pos.x()-m_graphRect.x())*m_xfactor + sv );
if (x < 0) x = 0;
int sec = (int)((double)x * m_sampleTime/10.);
QTime t = m_graphStartDateTime.time().addSecs( sec );
QString tmpStr = t.toString();
if (x < m_pointer)
{
tmpStr += "\n";
QString prefix ="";
double val = m_array[x];
//tmpStr += EngNumberValidator::engValue( val );
if (fabs(val) < 1 && val != 0)
{
val *= 1000;
prefix = "m";
}
if (fabs(val) < 1 && val != 0)
{
val *= 1000;
prefix = "u";
}
if (fabs(val) < 1 && val != 0)
{
val *= 1000;
prefix = "n";
}
if (fabs(val) < 1 && val != 0)
{
val *= 1000;
prefix = "p";
}
if (fabs(val) >= 1000)
{
val /= 1000;
prefix = "k";
}
if (fabs(val) >= 1000)
{
val /= 1000;
prefix = "M";
}
if (fabs(val) >= 1000)
{
val /= 1000;
prefix = "G";
}
QString unit = prefix + m_unit;
QString tmpVal;
tmpVal.sprintf( "%g %s", val, (const char *)unit.local8Bit() );
tmpStr += tmpVal;
}
m_infoBox->setText( tmpStr );
m_infoBox->adjustSize();
}
bool
DMMGraph::exportDataSLOT()
{
QString fn = QFileDialog::getSaveFileName( QString::null,
"All files (*.*)",
this );
if (!fn.isNull())
{
QFile file( fn );
file.open( IO_WriteOnly );
QTextStream ts( &file );
for (int i=0; i<m_pointer; i++)
{
QDateTime dt = m_graphStartDateTime.addSecs( i*(int)qRound(m_sampleTime/10.) );
QString line;
line.sprintf( "%02d.%02d.%04d\t%02d:%02d:%02d\t%06g\t%s\n",
dt.date().day(), dt.date().month(), dt.date().year(),
dt.time().hour(), dt.time().minute(), dt.time().second(),
m_array[i],
m_unit/*.mid( 1, m_unit.length()-2 )*/.latin1() );
ts << line;
}
m_dirty = false;
file.close();
return true;
}
return false;
}
void
DMMGraph::importDataSLOT()
{
if (m_dirty && m_alertUnsaved)
{
QMessageBox question( tr("QtDMM: Unsaved data" ),
tr("<font size=+2><b>Unsaved data</b></font><p>"
"Importing data will overwrite your measured data"
"<p>Do you want to export your unsaved data first?" ),
QMessageBox::Information,
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No,
QMessageBox::Cancel | QMessageBox::Escape );
question.setButtonText( QMessageBox::Yes, tr("Export data first") );
question.setButtonText( QMessageBox::No, tr("Import & overwrite data") );
question.setIconPixmap( QPixmap((const char **)icon_xpm ) );
switch (question.exec())
{
case QMessageBox::Yes:
exportDataSLOT();
return;
case QMessageBox::Cancel:
return;
}
}
QString fn = QFileDialog::getOpenFileName( QString::null,
"All files (*.*)",
this );
int cnt = 0;
int sample = 0;
QDateTime graphEnd;
if (!fn.isNull())
{
// First pass -> figure out size and sample time
QFile file( fn );
QStringList token;
QStringList dateToken;
QStringList timeToken;
if (file.open( IO_ReadOnly ))
{
QTextStream ts( &file );
QString line = ts.readLine();
if (!line.isNull())
{
QRegExp re( "[0-9]+\\.[0-9]+\\.[0-9]+\t[0-9]+:[0-9]+:[0-9]+\t[0-9]*\\.[0-9]+\t.*" );
if (!re.exactMatch(line))
{
emit error( tr("Oops! Seems not to be a valid file") );
return;
}
token = QStringList::split( "\t", line );
dateToken = QStringList::split( ".", token[0] );
timeToken = QStringList::split( ":", token[1] );
QTime startTime = QTime( timeToken[0].toInt(),
timeToken[1].toInt(),
timeToken[2].toInt() );
QDate startDate = QDate( dateToken[2].toInt(),
dateToken[1].toInt(),
dateToken[0].toInt() );
m_graphStartDateTime = QDateTime( startDate, startTime );
graphEnd = QDateTime( startDate, startTime );
setUnit( line.mid( 27, 3 ) );
cnt++;
while (!ts.atEnd())
{
line = ts.readLine();
if (!line.isEmpty())
{
token = QStringList::split( "\t", line );
dateToken = QStringList::split( ".", token[0] );
timeToken = QStringList::split( ":", token[1] );
QTime nowTime = QTime( timeToken[0].toInt(),
timeToken[1].toInt(),
timeToken[2].toInt() );
QDate nowDate = QDate( dateToken[2].toInt(),
dateToken[1].toInt(),
dateToken[0].toInt() );
sample += QDateTime( startDate, startTime ).secsTo(
QDateTime( nowDate, nowTime ) );
startTime = nowTime;
startDate = nowDate;
graphEnd = QDateTime( startDate, startTime );
cnt++;
}
}
}
file.close();
}
//std::cerr << "sample=" << sample << std::endl;
m_sampleTime = sample / (cnt>1 ? cnt-1 : 1); //m_graphStartDateTime.secsTo( graphEnd );
int size = m_size*m_sampleTime;
//int length = (m_length-1)*m_sampleTime;
if (cnt > 1)
{
//if (sample/(cnt-1) != m_sampleTime)
{
emit sampleTime( m_sampleTime );
}
m_sampleTime = sample/(cnt-1);
}
/* if (cnt*m_sampleTime > length)
{
if (size > cnt*m_sampleTime) size = cnt*m_sampleTime;
emit graphSize( size, cnt*m_sampleTime );
setGraphSize( size, cnt*m_sampleTime );
}*/
m_scaleMin = 1e40;
m_scaleMax = -1e40;
if (file.open( IO_ReadOnly ))
{
// TEST
setGraphSize( size, cnt*m_sampleTime );
int i = 0;
QTextStream ts( &file );
QString line;
while (!(line = ts.readLine()).isNull())
{
if (!line.isEmpty())
{
token = QStringList::split( "\t", line );
m_array[i++] = token[2].toDouble();
}
}
m_sampleCounter = m_pointer = cnt;
setScale( true, true, 0, 0 );
file.close();
m_dirty = false;
emit error( fn );
update();
computeUnitFactor();
}
//std::cerr << "min=" << m_scaleMin << " max=" << m_scaleMax << std::endl;
//std::cerr << "factor=" << m_factor << " prefix=" <<
// m_prefix.latin1() << " unit=" << m_unit.latin1() << std::endl;
//std::cerr << "size=" << size << "cnt*m_sampleTime=" << cnt*m_sampleTime << std::endl;
// TEST
emit graphSize( size, cnt*m_sampleTime );
}
}
void
DMMGraph::setThresholds( double falling, double raising )
{
m_fallingThreshold = falling;
m_raisingThreshold = raising;
}
void
DMMGraph::setScale( bool autoScale, bool includeZero, double min, double max )
{
m_autoScale = autoScale;
m_includeZero = includeZero;
if (!autoScale)
{
m_scaleMin = min;
m_scaleMax = max;
computeUnitFactor();
}
else
{
if (m_includeZero)
{
m_scaleMin = m_scaleMax = 0;
}
else
{
m_scaleMin = 1e40;
m_scaleMax = -1e40;
}
for (int i=0; i<m_pointer; i++)
{
const double val = m_array[i];
computeMinMax( val );
}
computeUnitFactor();
}
resizeEvent( 0 );
}
bool
DMMGraph::computeMinMax( double val )
{
bool ret = false;
if (val > m_scaleMax*0.95)
{
if (val > 0)
{
m_scaleMax = val*1.2;
}
else
{
m_scaleMax = val/1.2;
}
ret = true;
}
if (val < m_scaleMin*0.95)
{
if (val > 0)
{
m_scaleMin = val/1.2;
}
else
{
m_scaleMin = val*1.2;
}
ret = true;
}
return ret;
}
void
DMMGraph::setColors( const QColor & bg, const QColor & grid,
const QColor & data, const QColor & cursor,
const QColor & start, const QColor & external,
const QColor & integration, const QColor & intThreshold )
{
m_bgColor = bg;
m_gridColor = grid;
m_dataColor = data;
m_cursorColor = cursor;
m_startColor = start;
m_externalColor = external;
m_intColor = integration;
m_intThresholdColor = intThreshold;
update();
}
void
DMMGraph::setLineStyle( int lineMode, int pointMode,
int intLineMode, int intPointMode )
{
m_lineMode = (LineMode)lineMode;
m_pointMode = (PointMode)pointMode;
m_intLineMode = (LineMode)intLineMode;
m_intPointMode = (PointMode)intPointMode;
}
void
DMMGraph::setLine( int d, int i )
{
m_lineWidth = d;
m_intLineWidth = i;
update();
}
void
DMMGraph::setExternal( bool on, bool falling, double threshold )
{
m_startExternal = on;
m_externalFalling = falling;
m_externalThreshold = threshold;
}
void
DMMGraph::drawPoint( PointMode mode, QPainter *p, int x, int y )
{
QPoint arr[4];
switch (mode)
{
case NoPoint:
return;
case Square:
p->drawRect( x-2, y-2, 5, 5 );
return;
case Circle:
p->drawEllipse( x-2, y-2, 5, 5 );
return;
case Diamond:
arr[0] = QPoint( x-3, y );
arr[1] = QPoint( x, y+3 );
arr[2] = QPoint( x+3, y );
arr[3] = QPoint( x, y-3 );
p->drawPolygon( arr, 4 );
return;
case X:
p->drawLine( x-3, y-3, x+3, y+3 );
p->drawLine( x+3, y-3, x-3, y+3 );
return;
case LargeSquare:
p->drawRect( x-3, y-3, 7, 7 );
return;
case LargeCircle:
p->drawEllipse( x-3, y-3, 7, 7 );
return;
case LargeDiamond:
arr[0] = QPoint( x-4, y );
arr[1] = QPoint( x, y+4 );
arr[2] = QPoint( x+4, y );
arr[3] = QPoint( x, y-4 );
p->drawPolygon( arr, 4 );
return;
case LargeX:
p->drawLine( x-4, y-4, x+4, y+4 );
p->drawLine( x+4, y-4, x-4, y+4 );
return;
}
}
Qt::PenStyle
DMMGraph::penStyle( LineMode mode )
{
switch (mode)
{
case NoLine:
return Qt::NoPen;
case Solid:
return Qt::SolidLine;
case Dot:
return Qt::DotLine;
}
return Qt::SolidLine;
}
void
DMMGraph::setIntegration( bool showInt, double sc, double th, double off )
{
m_showIntegration = showInt;
m_integrationScale = sc;
m_integrationThreshold = th;
m_integrationOffset = off;
}
void
DMMGraph::computeUnitFactor()
{
m_factor = 1.;
m_prefix = "";
if (m_unit == "C" || m_unit == "%") return;
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) > 1000)
{
m_factor /= 1000.;
m_prefix = "k";
}
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) > 1000)
{
m_factor /= 1000.;
m_prefix = "M";
}
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) > 1000)
{
m_factor /= 1000.;
m_prefix = "G";
}
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) < 1)
{
m_factor *= 1000.;
m_prefix = "m";
}
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) < 1)
{
m_factor *= 1000.;
m_prefix = "µ";
}
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) < 1)
{
m_factor *= 1000.;
m_prefix = "n";
}
if (QMAX( fabs(m_scaleMax*m_factor), fabs(m_scaleMin*m_factor)) < 1)
{
m_factor *= 1000.;
m_prefix = "p";
}
}
void DMMGraph::popupSLOT( int id )
{
switch (id)
{
case IDConnect:
emit connectDMM( true );
break;
case IDDisconnect:
emit connectDMM( false );
break;
case IDStopRecorder:
stopSLOT();
break;
case IDStartRecorder:
startSLOT();
break;
case IDClearGraph:
clearSLOT();
break;
case IDConfigure:
emit configure();
break;
case IDExportData:
emit exportData();
break;
case IDImportData:
emit importData();
break;
}
}