oscilloscopeview.cpp

00001 /***************************************************************************
00002  *   Copyright (C) 2005 by David Saxton                                    *
00003  *   david@bluehaze.org                                                    *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  ***************************************************************************/
00010 
00011 #include "oscilloscope.h"
00012 #include "oscilloscopedata.h"
00013 #include "oscilloscopeview.h"
00014 #include "probepositioner.h"
00015 #include "simulator.h"
00016 
00017 #include <kconfig.h>
00018 #include <kdebug.h>
00019 #include <klocale.h>
00020 #include <kglobal.h>
00021 #include <kpopupmenu.h>
00022 #include <qcheckbox.h>
00023 #include <qcursor.h>
00024 #include <qevent.h>
00025 #include <qlabel.h>
00026 #include <qpainter.h>
00027 #include <qpixmap.h>
00028 #include <qscrollbar.h>
00029 #include <qtimer.h>
00030 
00031 #include <algorithm>
00032 #include <cmath>
00033 
00034 inline uint64_t min( uint64_t a, uint64_t b )
00035 {
00036         return a < b ? a : b;
00037 }
00038 
00039 
00040 OscilloscopeView::OscilloscopeView( QWidget *parent, const char *name )
00041         : QFrame( parent, name, WNoAutoErase ),
00042         b_needRedraw(true),
00043         m_pixmap(0),
00044         m_fps(10),
00045         m_sliderValueAtClick(-1),
00046         m_clickOffsetPos(-1),
00047         m_pSimulator( Simulator::self() ),
00048         m_halfOutputHeight(0.0)
00049 {
00050         KGlobal::config()->setGroup("Oscilloscope");
00051         m_fps = KGlobal::config()->readNumEntry( "FPS", 25 );
00052 
00053         setBackgroundMode(NoBackground);
00054         setMouseTracking(true);
00055 
00056         m_updateViewTmr = new QTimer(this);
00057         connect( m_updateViewTmr, SIGNAL(timeout()), this, SLOT(updateViewTimeout()) );
00058 }
00059 
00060 OscilloscopeView::~OscilloscopeView()
00061 {
00062         delete m_pixmap;
00063 }
00064 
00065 void OscilloscopeView::updateView()
00066 {
00067         if (m_updateViewTmr->isActive() ) return;
00068 
00069         m_updateViewTmr->start( 1000/m_fps, true );
00070 }
00071 
00072 void OscilloscopeView::updateViewTimeout()
00073 {
00074         b_needRedraw = true;
00075         repaint(false);
00076         updateTimeLabel();
00077 }
00078 
00079 
00080 void OscilloscopeView::updateTimeLabel()
00081 {
00082         if ( hasMouse() ) {
00083                 int x = mapFromGlobal( QCursor::pos() ).x();
00084                 double time = (double(Oscilloscope::self()->scrollTime()) / LOGIC_UPDATE_RATE) + (x / Oscilloscope::self()->pixelsPerSecond());
00085                 Oscilloscope::self()->timeLabel->setText( QString::number( time, 'f', 6 ) );
00086         } else Oscilloscope::self()->timeLabel->setText( QString::null );
00087 }
00088 
00089 
00090 void OscilloscopeView::resizeEvent( QResizeEvent *e )
00091 {
00092         delete m_pixmap;
00093         m_pixmap = new QPixmap( e->size() );
00094         b_needRedraw = true;
00095         QFrame::resizeEvent(e);
00096 }
00097 
00098 
00099 void OscilloscopeView::mousePressEvent( QMouseEvent *event )
00100 {
00101         switch ( event->button() )
00102         {
00103                 case Qt::LeftButton:
00104                 {
00105                         event->accept();
00106                         m_clickOffsetPos = event->pos().x();
00107                         m_sliderValueAtClick = Oscilloscope::self()->horizontalScroll->value();
00108                         setCursor( Qt::SizeAllCursor );
00109                         return;
00110                 }
00111                 
00112                 case Qt::RightButton:
00113                 {
00114                         event->accept();
00115         
00116                         KPopupMenu fpsMenu;
00117                         fpsMenu.insertTitle( i18n("Framerate") );
00118         
00119                         const int fps[] = { 10, 25, 50, 75, 100 };
00120         
00121                         for ( uint i=0; i<5; ++i )
00122                         {
00123                                 const int num = fps[i];
00124                                 fpsMenu.insertItem( i18n("%1 fps").arg(num), num );
00125                                 fpsMenu.setItemChecked( num, num == m_fps );
00126                         }
00127         
00128                         connect( &fpsMenu, SIGNAL(activated(int )), this, SLOT(slotSetFrameRate(int )) );
00129                         fpsMenu.exec( event->globalPos() );
00130                         return;
00131                 }
00132                 
00133                 default:
00134                 {
00135                         QFrame::mousePressEvent(event);
00136                         return;
00137                 }
00138         }
00139 }
00140 
00141 
00142 void OscilloscopeView::mouseMoveEvent( QMouseEvent *event )
00143 {
00144         event->accept();
00145         updateTimeLabel();
00146         
00147         if ( m_sliderValueAtClick != -1 )
00148         {
00149                 int dx = event->pos().x() - m_clickOffsetPos;
00150                 int dTick = int( dx * Oscilloscope::self()->sliderTicksPerSecond() / Oscilloscope::self()->pixelsPerSecond() );
00151                 Oscilloscope::self()->horizontalScroll->setValue( m_sliderValueAtClick - dTick );
00152         }
00153 }
00154 
00155 
00156 void OscilloscopeView::mouseReleaseEvent( QMouseEvent *event )
00157 {
00158         if ( m_sliderValueAtClick == -1 )
00159                 return QFrame::mouseReleaseEvent(event);
00160         
00161         event->accept();
00162         m_sliderValueAtClick = -1;
00163         setCursor( Qt::ArrowCursor );
00164 }
00165 
00166 
00167 void OscilloscopeView::slotSetFrameRate( int fps )
00168 {
00169         m_fps = fps;
00170         KGlobal::config()->setGroup("Oscilloscope");
00171         KGlobal::config()->writeEntry( "FPS", m_fps );
00172 }
00173 
00174 
00175 // returns a % b
00176 static double lld_modulus( int64_t a, double b )
00177 {
00178         return double(a) - int64_t(a/b)*b;
00179 }
00180 
00181 
00182 void OscilloscopeView::paintEvent( QPaintEvent *e )
00183 {
00184         QRect r = e->rect();
00185         
00186         if (b_needRedraw)
00187         {
00188                 updateOutputHeight();
00189                 const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
00190                 
00191                 QPainter p;
00192                 m_pixmap->fill( paletteBackgroundColor() );
00193                 p.begin(m_pixmap);
00194                 p.setClipRegion(e->region());
00195                 
00196                 //BEGIN Draw vertical marker lines
00197                 const double divisions = 5.0;
00198                 const double min_sep = 10.0;
00199                 
00200                 double spacing = pixelsPerSecond/(std::pow( divisions, std::floor(std::log(pixelsPerSecond/min_sep)/std::log(divisions)) ));
00201                 
00202                 // Pixels offset is the number of pixels that the view is scrolled along
00203                 const int64_t pixelsOffset = int64_t(Oscilloscope::self()->scrollTime()*pixelsPerSecond/LOGIC_UPDATE_RATE);
00204                 double linesOffset = - lld_modulus( pixelsOffset, spacing );
00205                 
00206                 int blackness = 256 - int(184.0 * spacing / (min_sep*divisions*divisions));
00207                 p.setPen( QColor( blackness, blackness, blackness ) );
00208                 
00209                 for ( double i = linesOffset; i <= frameRect().width(); i += spacing )
00210                         p.drawLine( int(i), 1, int(i), frameRect().height()-2 );
00211                 
00212                 
00213                 
00214                 spacing *= divisions;
00215                 linesOffset = - lld_modulus( pixelsOffset, spacing );
00216                 
00217                 blackness = 256 - int(184.0 * spacing / (min_sep*divisions*divisions));
00218                 p.setPen( QColor( blackness, blackness, blackness ) );
00219                 
00220                 for ( double i = linesOffset; i <= frameRect().width(); i += spacing )
00221                         p.drawLine( int(i), 1, int(i), frameRect().height()-2 );
00222                 
00223                 
00224                 
00225                 spacing *= divisions;
00226                 linesOffset = - lld_modulus( pixelsOffset, spacing );
00227                 
00228                 blackness = 256 - int(184.0);
00229                 p.setPen( QColor( blackness, blackness, blackness ) );
00230                 
00231                 for ( double i = linesOffset; i <= frameRect().width(); i += spacing )
00232                         p.drawLine( int(i), 1, int(i), frameRect().height()-2 );
00233                 //END Draw vertical marker lines
00234                 
00235                 drawLogicData(p);
00236                 drawFloatingData(p);
00237                 
00238                 p.setPen(Qt::black);
00239                 p.drawRect( frameRect() );
00240                 
00241                 b_needRedraw = false;
00242         }
00243         
00244         bitBlt( this, r.x(), r.y(), m_pixmap, r.x(), r.y(), r.width(), r.height() );
00245 }
00246 
00247 
00248 void OscilloscopeView::updateOutputHeight()
00249 {
00250         m_halfOutputHeight = int((Oscilloscope::self()->probePositioner->probeOutputHeight() - (probeArrowWidth/Oscilloscope::self()->numberOfProbes()))/2)-1;
00251 }
00252 
00253 
00254 void OscilloscopeView::drawLogicData( QPainter & p )
00255 {
00256         const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
00257         
00258         const LogicProbeDataMap::iterator end = Oscilloscope::self()->m_logicProbeDataMap.end();
00259         for ( LogicProbeDataMap::iterator it = Oscilloscope::self()->m_logicProbeDataMap.begin(); it != end; ++it )
00260         {
00261                 // When searching for the next logic value to display, we look along
00262                 // until there is a recorded point which is at least one pixel along
00263                 // If we are zoomed out far, there might be thousands of data points
00264                 // between each pixel. It is time consuming searching for the next point
00265                 // to display one at a time, so we record the average number of data points
00266                 // between pixels ( = deltaAt / totalDeltaAt )
00267                 int64_t deltaAt = 1;
00268                 int totalDeltaAt = 1;
00269                 
00270                 LogicProbeData * probe = it.data();
00271 
00272                 vector<LogicDataPoint> *data = probe->m_data;
00273                 if(!data->size()) continue;
00274                 
00275                 const int midHeight = Oscilloscope::self()->probePositioner->probePosition(probe);
00276                 const int64_t timeOffset = Oscilloscope::self()->scrollTime();
00277                 
00278                 // Draw the horizontal line indicating the midpoint of our output
00279                 p.setPen( QColor( 228, 228, 228 ) );
00280                 p.drawLine( 0, midHeight, width(), midHeight );
00281                 
00282                 // Set the pen colour according to the colour the user has selected for the probe
00283                 p.setPen( probe->color() );
00284                 
00285                 // The smallest time step that will display in our oscilloscope
00286                 const int minTimeStep = int(LOGIC_UPDATE_RATE/pixelsPerSecond);
00287                 
00288                 int64_t at = probe->findPos(timeOffset);
00289                 const int64_t maxAt = probe->m_data->size();
00290                 int64_t prevTime = (*data)[at].time;
00291                 int prevX = (at > 0) ? 0 : int((prevTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
00292                 bool prevHigh = (*data)[at].value;
00293                 int prevY = midHeight + int(prevHigh ? -m_halfOutputHeight : +m_halfOutputHeight);
00294 
00295                 while ( at < maxAt ) {
00296                         // Search for the next pos which will show up at our zoom level
00297                         int64_t previousAt = at;
00298                         int64_t dAt = deltaAt / totalDeltaAt;
00299                         
00300                         while ( (dAt > 1) && (at < maxAt) && ( (int64_t((*data)[at].time) - prevTime) != minTimeStep ) )
00301                         {
00302                                 // Search forwards until we overshoot
00303                                 while ( at < maxAt && ( int64_t((*data)[at].time) - prevTime ) < minTimeStep )
00304                                         at += dAt;
00305                                 dAt /= 2;
00306                                 
00307                                 // Search backwards until we undershoot
00308                                 while ( (at < maxAt) && ( int64_t((*data)[at].time) - prevTime ) > minTimeStep )
00309                                 {
00310                                         at -= dAt;
00311                                         if ( at < 0 )
00312                                                 at = 0;
00313                                 }
00314                                 dAt /= 2;
00315                         }
00316                         
00317                         // Possibly increment the value of at found by one (or more if this is the first go)
00318                         while ( (previousAt == at) || ((at < maxAt) && ( int64_t((*data)[at].time) - prevTime ) < minTimeStep) )
00319                                 at++;
00320 
00321                         if ( at >= maxAt ) break;
00322 
00323                         // Update the average values
00324                         deltaAt += at - previousAt;
00325                         totalDeltaAt++;
00326                         
00327                         bool nextHigh = (*data)[at].value;
00328                         if ( nextHigh == prevHigh ) continue;
00329 
00330                         int64_t nextTime = (*data)[at].time;
00331                         int nextX = int((nextTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
00332                         int nextY = midHeight + int(nextHigh ? -m_halfOutputHeight : +m_halfOutputHeight);
00333                         
00334                         p.drawLine( prevX, prevY, nextX, prevY );
00335                         p.drawLine( nextX, prevY, nextX, nextY );
00336 
00337                         prevHigh = nextHigh;
00338                         prevTime = nextTime;
00339                         prevX = nextX;
00340                         prevY = nextY;
00341 
00342                         if ( nextX > width() ) break;
00343                 };
00344                 
00345                 // If we could not draw right to the end; it is because we exceeded
00346                 // maxAt
00347                 if ( prevX < width() )
00348                         p.drawLine( prevX, prevY, width(), prevY );
00349         }
00350 }
00351 
00352 #define v_to_y int(midHeight - (logarithmic ? ( (v>0) ? log(v/lowerAbsValue) : -log(-v/lowerAbsValue) ) : v) * sf)
00353 
00354 void OscilloscopeView::drawFloatingData(QPainter &p)
00355 {
00356         const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
00357 
00358         const FloatingProbeDataMap::iterator end = Oscilloscope::self()->m_floatingProbeDataMap.end();
00359         for(FloatingProbeDataMap::iterator it = Oscilloscope::self()->m_floatingProbeDataMap.begin(); it != end; ++it ) {
00360                 FloatingProbeData * probe = it.data();
00361                 vector<float> *data = probe->m_data;
00362 
00363                 if(!data->size()) continue;
00364 
00365                 bool logarithmic = probe->scaling() == FloatingProbeData::Logarithmic;
00366                 double lowerAbsValue = probe->lowerAbsValue();
00367                 double sf = m_halfOutputHeight / (logarithmic ? log(probe->upperAbsValue()/lowerAbsValue) : probe->upperAbsValue());
00368 
00369                 const int midHeight = Oscilloscope::self()->probePositioner->probePosition(probe);
00370                 const int64_t timeOffset = Oscilloscope::self()->scrollTime();
00371 
00372                 // Draw the horizontal line indicating the midpoint of our output
00373                 p.setPen( QColor( 228, 228, 228 ) );
00374                 p.drawLine( 0, midHeight, width(), midHeight );
00375 
00376                 // Set the pen colour according to the colour the user has selected for the probe
00377                 p.setPen( probe->color() );
00378 
00379                 int64_t at = probe->findPos(timeOffset);
00380                 const int64_t maxAt = probe->m_data->size();
00381                 if(at > maxAt) at = maxAt;
00382                 int64_t prevTime = probe->toTime(at);
00383 
00384                 double v = (*data)[(at>0)?at:0];
00385                 int prevY = v_to_y;
00386                 int prevX = int((prevTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
00387 
00388                 while ( at < maxAt ) {
00389                         at++;
00390 
00391                         uint64_t nextTime = prevTime + uint64_t(LOGIC_UPDATE_RATE/LINEAR_UPDATE_RATE);
00392 
00393                         double v = (*data)[(at>0)?at:0];
00394                         int nextY = v_to_y;
00395                         int nextX = int((nextTime - timeOffset)*(pixelsPerSecond/LOGIC_UPDATE_RATE));
00396 
00397                         p.drawLine( prevX, prevY, nextX, nextY );
00398 
00399                         prevTime = nextTime;
00400                         prevX = nextX;
00401                         prevY = nextY;
00402 
00403                         if ( nextX > width() ) break;
00404                 };
00405 
00406                 // If we could not draw right to the end; it is because we exceeded
00407                 // maxAt
00408                 if ( prevX < width() )
00409                         p.drawLine( prevX, prevY, width(), prevY );
00410         }
00411 }
00412 
00413 #include "oscilloscopeview.moc"

Generated on Tue May 8 17:05:31 2007 for KTechLab by  doxygen 1.5.1