summaryrefslogtreecommitdiffstats
path: root/src/gui/oscilloscopeview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/oscilloscopeview.cpp')
-rw-r--r--src/gui/oscilloscopeview.cpp431
1 files changed, 431 insertions, 0 deletions
diff --git a/src/gui/oscilloscopeview.cpp b/src/gui/oscilloscopeview.cpp
new file mode 100644
index 0000000..3d2a40a
--- /dev/null
+++ b/src/gui/oscilloscopeview.cpp
@@ -0,0 +1,431 @@
+/***************************************************************************
+ * Copyright (C) 2005 by David Saxton *
+ * david@bluehaze.org *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ ***************************************************************************/
+
+#include "oscilloscope.h"
+#include "oscilloscopedata.h"
+#include "oscilloscopeview.h"
+#include "probepositioner.h"
+#include "simulator.h"
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kpopupmenu.h>
+#include <qcheckbox.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qlabel.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qscrollbar.h>
+#include <qtimer.h>
+
+#include <algorithm>
+#include <cmath>
+
+inline ullong min( ullong a, ullong b )
+{
+ return a < b ? a : b;
+}
+
+
+OscilloscopeView::OscilloscopeView( QWidget *parent, const char *name )
+ : QFrame( parent, name, WNoAutoErase ),
+ b_needRedraw(true),
+ m_pixmap(0l),
+ m_fps(10),
+ m_sliderValueAtClick(-1),
+ m_clickOffsetPos(-1),
+ m_pSimulator( Simulator::self() ),
+ m_halfOutputHeight(0.0)
+{
+ KGlobal::config()->setGroup("Oscilloscope");
+ m_fps = KGlobal::config()->readNumEntry( "FPS", 25 );
+
+ setBackgroundMode(NoBackground);
+ setMouseTracking(true);
+
+ m_updateViewTmr = new QTimer(this);
+ connect( m_updateViewTmr, SIGNAL(timeout()), this, SLOT(updateViewTimeout()) );
+}
+
+
+OscilloscopeView::~OscilloscopeView()
+{
+ delete m_pixmap;
+ m_pixmap = 0l;
+}
+
+
+void OscilloscopeView::updateView()
+{
+ if (m_updateViewTmr->isActive() )
+ return;
+
+ m_updateViewTmr->start( 1000/m_fps, true );
+}
+
+
+void OscilloscopeView::updateViewTimeout()
+{
+ b_needRedraw = true;
+ repaint(false);
+ updateTimeLabel();
+}
+
+
+void OscilloscopeView::updateTimeLabel()
+{
+ if ( hasMouse() )
+ {
+ int x = mapFromGlobal( QCursor::pos() ).x();
+ double time = (double(Oscilloscope::self()->scrollTime()) / LOGIC_UPDATE_RATE) + (x / Oscilloscope::self()->pixelsPerSecond());
+ Oscilloscope::self()->timeLabel->setText( QString::number( time, 'f', 6 ) );
+ }
+
+ else
+ Oscilloscope::self()->timeLabel->setText( QString::null );
+}
+
+
+void OscilloscopeView::resizeEvent( QResizeEvent *e )
+{
+ delete m_pixmap;
+ m_pixmap = new QPixmap( e->size() );
+ b_needRedraw = true;
+ QFrame::resizeEvent(e);
+}
+
+
+void OscilloscopeView::mousePressEvent( QMouseEvent *event )
+{
+ switch ( event->button() )
+ {
+ case Qt::LeftButton:
+ {
+ event->accept();
+ m_clickOffsetPos = event->pos().x();
+ m_sliderValueAtClick = Oscilloscope::self()->horizontalScroll->value();
+ setCursor( Qt::SizeAllCursor );
+ return;
+ }
+
+ case Qt::RightButton:
+ {
+ event->accept();
+
+ KPopupMenu fpsMenu;
+ fpsMenu.insertTitle( i18n("Framerate") );
+
+ const int fps[] = { 10, 25, 50, 75, 100 };
+
+ for ( uint i=0; i<5; ++i )
+ {
+ const int num = fps[i];
+ fpsMenu.insertItem( i18n("%1 fps").arg(num), num );
+ fpsMenu.setItemChecked( num, num == m_fps );
+ }
+
+ connect( &fpsMenu, SIGNAL(activated(int )), this, SLOT(slotSetFrameRate(int )) );
+ fpsMenu.exec( event->globalPos() );
+ return;
+ }
+
+ default:
+ {
+ QFrame::mousePressEvent(event);
+ return;
+ }
+ }
+}
+
+
+void OscilloscopeView::mouseMoveEvent( QMouseEvent *event )
+{
+ event->accept();
+ updateTimeLabel();
+
+ if ( m_sliderValueAtClick != -1 )
+ {
+ int dx = event->pos().x() - m_clickOffsetPos;
+ int dTick = int( dx * Oscilloscope::self()->sliderTicksPerSecond() / Oscilloscope::self()->pixelsPerSecond() );
+ Oscilloscope::self()->horizontalScroll->setValue( m_sliderValueAtClick - dTick );
+ }
+}
+
+
+void OscilloscopeView::mouseReleaseEvent( QMouseEvent *event )
+{
+ if ( m_sliderValueAtClick == -1 )
+ return QFrame::mouseReleaseEvent(event);
+
+ event->accept();
+ m_sliderValueAtClick = -1;
+ setCursor( Qt::ArrowCursor );
+}
+
+
+void OscilloscopeView::slotSetFrameRate( int fps )
+{
+ m_fps = fps;
+ KGlobal::config()->setGroup("Oscilloscope");
+ KGlobal::config()->writeEntry( "FPS", m_fps );
+}
+
+
+// returns a % b
+static double lld_modulus( llong a, double b )
+{
+ return double(a) - llong(a/b)*b;
+}
+
+
+void OscilloscopeView::paintEvent( QPaintEvent *e )
+{
+ QRect r = e->rect();
+
+ if (b_needRedraw)
+ {
+ updateOutputHeight();
+ const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
+
+ QPainter p;
+ m_pixmap->fill( paletteBackgroundColor() );
+ p.begin(m_pixmap);
+ p.setClipRegion(e->region());
+
+ //BEGIN Draw vertical marker lines
+ const double divisions = 5.0;
+ const double min_sep = 10.0;
+
+ double spacing = pixelsPerSecond/(std::pow( divisions, std::floor(std::log(pixelsPerSecond/min_sep)/std::log(divisions)) ));
+
+ // Pixels offset is the number of pixels that the view is scrolled along
+ const llong pixelsOffset = llong(Oscilloscope::self()->scrollTime()*pixelsPerSecond/LOGIC_UPDATE_RATE);
+ double linesOffset = - lld_modulus( pixelsOffset, spacing );
+
+ int blackness = 256 - int(184.0 * spacing / (min_sep*divisions*divisions));
+ p.setPen( QColor( blackness, blackness, blackness ) );
+
+ for ( double i = linesOffset; i <= frameRect().width(); i += spacing )
+ p.drawLine( int(i), 1, int(i), frameRect().height()-2 );
+
+
+
+ spacing *= divisions;
+ linesOffset = - lld_modulus( pixelsOffset, spacing );
+
+ blackness = 256 - int(184.0 * spacing / (min_sep*divisions*divisions));
+ p.setPen( QColor( blackness, blackness, blackness ) );
+
+ for ( double i = linesOffset; i <= frameRect().width(); i += spacing )
+ p.drawLine( int(i), 1, int(i), frameRect().height()-2 );
+
+
+
+ spacing *= divisions;
+ linesOffset = - lld_modulus( pixelsOffset, spacing );
+
+ blackness = 256 - int(184.0);
+ p.setPen( QColor( blackness, blackness, blackness ) );
+
+ for ( double i = linesOffset; i <= frameRect().width(); i += spacing )
+ p.drawLine( int(i), 1, int(i), frameRect().height()-2 );
+ //END Draw vertical marker lines
+
+ drawLogicData(p);
+ drawFloatingData(p);
+
+ p.setPen(Qt::black);
+ p.drawRect( frameRect() );
+
+ b_needRedraw = false;
+ }
+
+ bitBlt( this, r.x(), r.y(), m_pixmap, r.x(), r.y(), r.width(), r.height() );
+}
+
+
+void OscilloscopeView::updateOutputHeight()
+{
+ m_halfOutputHeight = int((Oscilloscope::self()->probePositioner->probeOutputHeight() - (probeArrowWidth/Oscilloscope::self()->numberOfProbes()))/2)-1;
+}
+
+
+void OscilloscopeView::drawLogicData( QPainter & p )
+{
+ const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
+
+ const LogicProbeDataMap::iterator end = Oscilloscope::self()->m_logicProbeDataMap.end();
+ for ( LogicProbeDataMap::iterator it = Oscilloscope::self()->m_logicProbeDataMap.begin(); it != end; ++it )
+ {
+ // When searching for the next logic value to display, we look along
+ // until there is a recorded point which is at least one pixel along
+ // If we are zoomed out far, there might be thousands of data points
+ // between each pixel. It is time consuming searching for the next point
+ // to display one at a time, so we record the average number of data points
+ // between pixels ( = deltaAt / totalDeltaAt )
+ llong deltaAt = 1;
+ int totalDeltaAt = 1;
+
+ LogicProbeData * probe = it.data();
+ StoredData<LogicDataPoint> * data = &(probe->m_data);
+
+ if ( data->allocatedUpTo() == 0 )
+ continue;
+
+ const int midHeight = Oscilloscope::self()->probePositioner->probePosition(probe);
+ const llong timeOffset = Oscilloscope::self()->scrollTime();
+
+ // Draw the horizontal line indicating the midpoint of our output
+ p.setPen( QColor( 228, 228, 228 ) );
+ p.drawLine( 0, midHeight, width(), midHeight );
+
+ // Set the pen colour according to the colour the user has selected for the probe
+ p.setPen( probe->color() );
+
+ // The smallest time step that will display in our oscilloscope
+ const int minTimeStep = int(LOGIC_UPDATE_RATE/pixelsPerSecond);
+
+ llong at = probe->findPos(timeOffset);
+ const llong maxAt = probe->insertPos();
+ llong prevTime = data->dataAt(at).time;
+ int prevX = (at > 0) ? 0 : int((prevTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
+ bool prevHigh = data->dataAt(at).value;
+ int prevY = midHeight + int(prevHigh ? -m_halfOutputHeight : +m_halfOutputHeight);
+ while ( at < maxAt )
+ {
+ // Search for the next pos which will show up at our zoom level
+ llong previousAt = at;
+ llong dAt = deltaAt / totalDeltaAt;
+
+ while ( (dAt > 1) && (at < maxAt) && ( (llong(data->dataAt(at).time) - prevTime) != minTimeStep ) )
+ {
+ // Search forwards until we overshoot
+ while ( at < maxAt && ( llong(data->dataAt(at).time) - prevTime ) < minTimeStep )
+ at += dAt;
+ dAt /= 2;
+
+ // Search backwards until we undershoot
+ while ( (at < maxAt) && ( llong(data->dataAt(at).time) - prevTime ) > minTimeStep )
+ {
+ at -= dAt;
+ if ( at < 0 )
+ at = 0;
+ }
+ dAt /= 2;
+ }
+
+ // Possibly increment the value of at found by one (or more if this is the first go)
+ while ( (previousAt == at) || ((at < maxAt) && ( llong(data->dataAt(at).time) - prevTime ) < minTimeStep) )
+ at++;
+
+ if ( at >= maxAt )
+ break;
+
+ // Update the average values
+ deltaAt += at - previousAt;
+ totalDeltaAt++;
+
+ bool nextHigh = data->dataAt(at).value;
+ if ( nextHigh == prevHigh )
+ continue;
+ llong nextTime = data->dataAt(at).time;
+ int nextX = int((nextTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
+ int nextY = midHeight + int(nextHigh ? -m_halfOutputHeight : +m_halfOutputHeight);
+
+ p.drawLine( prevX, prevY, nextX, prevY );
+ p.drawLine( nextX, prevY, nextX, nextY );
+
+ prevHigh = nextHigh;
+ prevTime = nextTime;
+ prevX = nextX;
+ prevY = nextY;
+
+ if ( nextX > width() )
+ break;
+ };
+
+ // If we could not draw right to the end; it is because we exceeded
+ // maxAt
+ if ( prevX < width() )
+ p.drawLine( prevX, prevY, width(), prevY );
+ }
+}
+
+
+#define v_to_y int(midHeight - (logarithmic ? ( (v>0) ? log(v/lowerAbsValue) : -log(-v/lowerAbsValue) ) : v) * sf)
+
+
+void OscilloscopeView::drawFloatingData( QPainter & p )
+{
+ const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
+
+ const FloatingProbeDataMap::iterator end = Oscilloscope::self()->m_floatingProbeDataMap.end();
+ for ( FloatingProbeDataMap::iterator it = Oscilloscope::self()->m_floatingProbeDataMap.begin(); it != end; ++it )
+ {
+ FloatingProbeData * probe = it.data();
+ StoredData<float> * data = &(probe->m_data);
+
+ if ( data->allocatedUpTo() == 0 )
+ continue;
+
+ bool logarithmic = probe->scaling() == FloatingProbeData::Logarithmic;
+ double lowerAbsValue = probe->lowerAbsValue();
+ double sf = m_halfOutputHeight / (logarithmic ? log(probe->upperAbsValue()/lowerAbsValue) : probe->upperAbsValue());
+
+ const int midHeight = Oscilloscope::self()->probePositioner->probePosition(probe);
+ const llong timeOffset = Oscilloscope::self()->scrollTime();
+
+ // Draw the horizontal line indicating the midpoint of our output
+ p.setPen( QColor( 228, 228, 228 ) );
+ p.drawLine( 0, midHeight, width(), midHeight );
+
+ // Set the pen colour according to the colour the user has selected for the probe
+ p.setPen( probe->color() );
+
+ llong at = probe->findPos(timeOffset);
+ const llong maxAt = probe->insertPos();
+ llong prevTime = probe->toTime(at);
+
+ double v = data->dataAt((at>0)?at:0);
+ int prevY = v_to_y;
+ int prevX = int((prevTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
+
+ while ( at < maxAt-1 )
+ {
+ at++;
+
+ ullong nextTime = prevTime + ullong(LOGIC_UPDATE_RATE/LINEAR_UPDATE_RATE);
+
+ double v = data->dataAt((at>0)?at:0);
+ int nextY = v_to_y;
+ int nextX = int((nextTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
+
+ p.drawLine( prevX, prevY, nextX, nextY );
+
+ prevTime = nextTime;
+ prevX = nextX;
+ prevY = nextY;
+
+ if ( nextX > width() )
+ break;
+ };
+
+ // If we could not draw right to the end; it is because we exceeded
+ // maxAt
+ if ( prevX < width() )
+ p.drawLine( prevX, prevY, width(), prevY );
+ }
+}
+
+
+#include "oscilloscopeview.moc"