File indexing completed on 2024-07-14 05:32:50

0001 /***************************************************************************
0002  *   Copyright (C) 2005 by David Saxton                                    *
0003  *   david@bluehaze.org                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include "oscilloscopeview.h"
0012 #include "oscilloscope.h"
0013 #include "oscilloscopedata.h"
0014 #include "probepositioner.h"
0015 #include "simulator.h"
0016 
0017 #include <KConfigGroup>
0018 #include <KLocalizedString>
0019 #include <KSharedConfig>
0020 // #include <k3popupmenu.h>
0021 
0022 #include <QCheckBox>
0023 #include <QCursor>
0024 #include <QLabel>
0025 #include <QMenu>
0026 #include <QPaintEvent>
0027 #include <QPainter>
0028 #include <QPixmap>
0029 #include <QScrollBar>
0030 #include <QTimer>
0031 
0032 #include <algorithm>
0033 #include <cmath>
0034 
0035 #include <ktechlab_debug.h>
0036 
0037 using namespace std;
0038 
0039 inline uint64_t min(uint64_t a, uint64_t b)
0040 {
0041     return a < b ? a : b;
0042 }
0043 
0044 OscilloscopeView::OscilloscopeView(QWidget *parent)
0045     : QFrame(parent /*, Qt::WNoAutoErase */)
0046     , b_needRedraw(true)
0047     , m_pixmap(nullptr)
0048     , m_fps(10)
0049     , m_sliderValueAtClick(-1)
0050     , m_clickOffsetPos(-1)
0051     , m_pSimulator(Simulator::self())
0052     , m_halfOutputHeight(0.0)
0053 {
0054     // KGlobal::config()->setGroup("Oscilloscope");
0055     KConfigGroup grOscill(KSharedConfig::openConfig(), "Oscilloscope");
0056     m_fps = grOscill.readEntry("FPS", 25);
0057 
0058     // setBackgroundMode(Qt::NoBackground); // 2018.12.07
0059     setBackgroundRole(QPalette::NoRole);
0060     setMouseTracking(true);
0061 
0062     m_updateViewTmr = new QTimer(this);
0063     connect(m_updateViewTmr, SIGNAL(timeout()), this, SLOT(updateViewTimeout()));
0064 }
0065 
0066 OscilloscopeView::~OscilloscopeView()
0067 {
0068     delete m_pixmap;
0069 }
0070 
0071 void OscilloscopeView::updateView()
0072 {
0073     if (m_updateViewTmr->isActive())
0074         return;
0075 
0076     m_updateViewTmr->setSingleShot(true);
0077     m_updateViewTmr->start(1000 / m_fps /*, true */);
0078 }
0079 
0080 void OscilloscopeView::updateViewTimeout()
0081 {
0082     b_needRedraw = true;
0083     repaint(/* false  - 2018.12.07 */);
0084     updateTimeLabel();
0085 }
0086 
0087 void OscilloscopeView::updateTimeLabel()
0088 {
0089     if (testAttribute(Qt::WA_UnderMouse)) {
0090         int x = mapFromGlobal(QCursor::pos()).x();
0091         double time = (double(Oscilloscope::self()->scrollTime()) / LOGIC_UPDATE_RATE) + (x / Oscilloscope::self()->pixelsPerSecond());
0092         Oscilloscope::self()->timeLabel->setText(QString::number(time, 'f', 6));
0093     } else
0094         Oscilloscope::self()->timeLabel->setText(QString());
0095 }
0096 
0097 void OscilloscopeView::resizeEvent(QResizeEvent *e)
0098 {
0099     delete m_pixmap;
0100     m_pixmap = new QPixmap(e->size());
0101     b_needRedraw = true;
0102     QFrame::resizeEvent(e);
0103 }
0104 
0105 void OscilloscopeView::mousePressEvent(QMouseEvent *event)
0106 {
0107     switch (event->button()) {
0108     case Qt::LeftButton: {
0109         event->accept();
0110         m_clickOffsetPos = event->pos().x();
0111         m_sliderValueAtClick = Oscilloscope::self()->horizontalScroll->value();
0112         setCursor(Qt::SizeAllCursor);
0113         return;
0114     }
0115 
0116     case Qt::RightButton: {
0117         event->accept();
0118 
0119         QMenu fpsMenu;
0120         // fpsMenu.insertTitle( i18n("Framerate")); // 2017.12.27 - use setTitle
0121         // fpsMenu.insertItem( i18n("Framerate"), 1 );   // 2018.12.07 - use actions
0122         // fpsMenu.setItemEnabled(1, false);
0123         {
0124             QAction *a = fpsMenu.addAction(i18n("Framerate"));
0125             a->setData(1);
0126             a->setEnabled(false);
0127         }
0128         // fpsMenu.insertSeparator(); // 2018.12.07
0129         fpsMenu.addSeparator();
0130 
0131         const int fps[] = {10, 25, 50, 75, 100};
0132 
0133         for (uint i = 0; i < 5; ++i) {
0134             const int num = fps[i];
0135             // fpsMenu.insertItem( i18n("%1 fps", QString::number(num)), num);   // 2018.12.07
0136             // fpsMenu.setItemChecked( num, num == m_fps);
0137             QAction *a = fpsMenu.addAction(i18n("%1 fps", QString::number(num)));
0138             a->setData(num);
0139         }
0140 
0141         connect(&fpsMenu, SIGNAL(triggered(QAction *)), this, SLOT(slotSetFrameRate(QAction *)));
0142         fpsMenu.exec(event->globalPos());
0143         return;
0144     }
0145 
0146     default: {
0147         QFrame::mousePressEvent(event);
0148         return;
0149     }
0150     }
0151 }
0152 
0153 void OscilloscopeView::mouseMoveEvent(QMouseEvent *event)
0154 {
0155     event->accept();
0156     updateTimeLabel();
0157 
0158     if (m_sliderValueAtClick != -1) {
0159         int dx = event->pos().x() - m_clickOffsetPos;
0160         int dTick = int(dx * Oscilloscope::self()->sliderTicksPerSecond() / Oscilloscope::self()->pixelsPerSecond());
0161         Oscilloscope::self()->horizontalScroll->setValue(m_sliderValueAtClick - dTick);
0162     }
0163 }
0164 
0165 void OscilloscopeView::mouseReleaseEvent(QMouseEvent *event)
0166 {
0167     if (m_sliderValueAtClick == -1)
0168         return QFrame::mouseReleaseEvent(event);
0169 
0170     event->accept();
0171     m_sliderValueAtClick = -1;
0172     setCursor(Qt::ArrowCursor);
0173 }
0174 
0175 void OscilloscopeView::slotSetFrameRate(QAction *action)
0176 {
0177     int fps = action->data().toInt();
0178     m_fps = fps;
0179     // KGlobal::config()->setGroup("Oscilloscope");
0180     KConfigGroup grOscill(KSharedConfig::openConfig(), "Oscilloscope");
0181     grOscill.writeEntry("FPS", m_fps);
0182 }
0183 
0184 // returns a % b
0185 static double lld_modulus(int64_t a, double b)
0186 {
0187     return double(a) - int64_t(a / b) * b;
0188 }
0189 
0190 void OscilloscopeView::paintEvent(QPaintEvent *e)
0191 {
0192     QRect r = e->rect();
0193 
0194     if (b_needRedraw) {
0195         updateOutputHeight();
0196         const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
0197 
0198         if (!m_pixmap) {
0199             qCWarning(KTL_LOG) << " unexpected null m_pixmap in " << this;
0200             return;
0201         }
0202 
0203         QPainter p;
0204         // m_pixmap->fill( paletteBackgroundColor());
0205         m_pixmap->fill(palette().color(backgroundRole()));
0206         const bool startSuccess = p.begin(m_pixmap);
0207         if ((!startSuccess) || (!p.isActive())) {
0208             qCWarning(KTL_LOG) << " painter is not active";
0209         }
0210 
0211         p.setClipRegion(e->region());
0212 
0213         // BEGIN Draw vertical marker lines
0214         const double divisions = 5.0;
0215         const double min_sep = 10.0;
0216 
0217         double spacing = pixelsPerSecond / (std::pow(divisions, std::floor(std::log(pixelsPerSecond / min_sep) / std::log(divisions))));
0218 
0219         // Pixels offset is the number of pixels that the view is scrolled along
0220         const int64_t pixelsOffset = int64_t(Oscilloscope::self()->scrollTime() * pixelsPerSecond / LOGIC_UPDATE_RATE);
0221         double linesOffset = -lld_modulus(pixelsOffset, spacing);
0222 
0223         int blackness = 256 - int(184.0 * spacing / (min_sep * divisions * divisions));
0224         p.setPen(QColor(blackness, blackness, blackness));
0225 
0226         for (double i = linesOffset; i <= frameRect().width(); i += spacing)
0227             p.drawLine(int(i), 1, int(i), frameRect().height() - 2);
0228 
0229         spacing *= divisions;
0230         linesOffset = -lld_modulus(pixelsOffset, spacing);
0231 
0232         blackness = 256 - int(184.0 * spacing / (min_sep * divisions * divisions));
0233         p.setPen(QColor(blackness, blackness, blackness));
0234 
0235         for (double i = linesOffset; i <= frameRect().width(); i += spacing)
0236             p.drawLine(int(i), 1, int(i), frameRect().height() - 2);
0237 
0238         spacing *= divisions;
0239         linesOffset = -lld_modulus(pixelsOffset, spacing);
0240 
0241         blackness = 256 - int(184.0);
0242         p.setPen(QColor(blackness, blackness, blackness));
0243 
0244         for (double i = linesOffset; i <= frameRect().width(); i += spacing)
0245             p.drawLine(int(i), 1, int(i), frameRect().height() - 2);
0246         // END Draw vertical marker lines
0247 
0248         drawLogicData(p);
0249         drawFloatingData(p);
0250 
0251         p.setPen(Qt::black);
0252         p.drawRect(frameRect());
0253 
0254         b_needRedraw = false;
0255     }
0256 
0257     // bitBlt( this, r.x(), r.y(), m_pixmap, r.x(), r.y(), r.width(), r.height()); // 2018.12.07
0258     QPainter p;
0259     const bool paintStarted = p.begin(this);
0260     if (!paintStarted) {
0261         qCWarning(KTL_LOG) << " failed to start painting ";
0262     }
0263     p.drawImage(r, m_pixmap->toImage(), r);
0264 }
0265 
0266 void OscilloscopeView::updateOutputHeight()
0267 {
0268     m_halfOutputHeight = int((Oscilloscope::self()->probePositioner->probeOutputHeight() - (probeArrowWidth / Oscilloscope::self()->numberOfProbes())) / 2) - 1;
0269 }
0270 
0271 void OscilloscopeView::drawLogicData(QPainter &p)
0272 {
0273     const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
0274 
0275     const LogicProbeDataMap::iterator end = Oscilloscope::self()->m_logicProbeDataMap.end();
0276     for (LogicProbeDataMap::iterator it = Oscilloscope::self()->m_logicProbeDataMap.begin(); it != end; ++it) {
0277         // When searching for the next logic value to display, we look along
0278         // until there is a recorded point which is at least one pixel along
0279         // If we are zoomed out far, there might be thousands of data points
0280         // between each pixel. It is time consuming searching for the next point
0281         // to display one at a time, so we record the average number of data points
0282         // between pixels ( = deltaAt / totalDeltaAt)
0283         int64_t deltaAt = 1;
0284         int totalDeltaAt = 1;
0285 
0286         LogicProbeData *probe = it.value();
0287 
0288         vector<LogicDataPoint> *data = probe->m_data;
0289         if (!data->size())
0290             continue;
0291 
0292         const int midHeight = Oscilloscope::self()->probePositioner->probePosition(probe);
0293         const int64_t timeOffset = Oscilloscope::self()->scrollTime();
0294 
0295         // Draw the horizontal line indicating the midpoint of our output
0296         p.setPen(QColor(228, 228, 228));
0297         p.drawLine(0, midHeight, width(), midHeight);
0298 
0299         // Set the pen colour according to the colour the user has selected for the probe
0300         p.setPen(probe->color());
0301 
0302         // The smallest time step that will display in our oscilloscope
0303         const int minTimeStep = int(LOGIC_UPDATE_RATE / pixelsPerSecond);
0304 
0305         int64_t at = probe->findPos(timeOffset);
0306         const int64_t maxAt = probe->m_data->size();
0307         int64_t prevTime = (*data)[at].time;
0308         int prevX = (at > 0) ? 0 : int((prevTime - timeOffset) * (pixelsPerSecond / LOGIC_UPDATE_RATE));
0309         bool prevHigh = (*data)[at].value;
0310         int prevY = midHeight + int(prevHigh ? -m_halfOutputHeight : +m_halfOutputHeight);
0311 
0312         while (at < maxAt) {
0313             // Search for the next pos which will show up at our zoom level
0314             int64_t previousAt = at;
0315             int64_t dAt = deltaAt / totalDeltaAt;
0316 
0317             while ((dAt > 1) && (at < maxAt) && ((int64_t((*data)[at].time) - prevTime) != minTimeStep)) {
0318                 // Search forwards until we overshoot
0319                 while (at < maxAt && (int64_t((*data)[at].time) - prevTime) < minTimeStep)
0320                     at += dAt;
0321                 dAt /= 2;
0322 
0323                 // Search backwards until we undershoot
0324                 while ((at < maxAt) && (int64_t((*data)[at].time) - prevTime) > minTimeStep) {
0325                     at -= dAt;
0326                     if (at < 0)
0327                         at = 0;
0328                 }
0329                 dAt /= 2;
0330             }
0331 
0332             // Possibly increment the value of at found by one (or more if this is the first go)
0333             while ((previousAt == at) || ((at < maxAt) && (int64_t((*data)[at].time) - prevTime) < minTimeStep))
0334                 at++;
0335 
0336             if (at >= maxAt)
0337                 break;
0338 
0339             // Update the average values
0340             deltaAt += at - previousAt;
0341             totalDeltaAt++;
0342 
0343             bool nextHigh = (*data)[at].value;
0344             if (nextHigh == prevHigh)
0345                 continue;
0346 
0347             int64_t nextTime = (*data)[at].time;
0348             int nextX = int((nextTime - timeOffset) * (pixelsPerSecond / LOGIC_UPDATE_RATE));
0349             int nextY = midHeight + int(nextHigh ? -m_halfOutputHeight : +m_halfOutputHeight);
0350 
0351             p.drawLine(prevX, prevY, nextX, prevY);
0352             p.drawLine(nextX, prevY, nextX, nextY);
0353 
0354             prevHigh = nextHigh;
0355             prevTime = nextTime;
0356             prevX = nextX;
0357             prevY = nextY;
0358 
0359             if (nextX > width())
0360                 break;
0361         };
0362 
0363         // If we could not draw right to the end; it is because we exceeded
0364         // maxAt
0365         if (prevX < width())
0366             p.drawLine(prevX, prevY, width(), prevY);
0367     }
0368 }
0369 
0370 #define v_to_y int(midHeight - (logarithmic ? ((v > 0) ? log(v / lowerAbsValue) : -log(-v / lowerAbsValue)) : v) * sf)
0371 
0372 void OscilloscopeView::drawFloatingData(QPainter &p)
0373 {
0374     const double pixelsPerSecond = Oscilloscope::self()->pixelsPerSecond();
0375 
0376     const FloatingProbeDataMap::iterator end = Oscilloscope::self()->m_floatingProbeDataMap.end();
0377     for (FloatingProbeDataMap::iterator it = Oscilloscope::self()->m_floatingProbeDataMap.begin(); it != end; ++it) {
0378         FloatingProbeData *probe = it.value();
0379         vector<float> *data = probe->m_data;
0380 
0381         if (!data->size())
0382             continue;
0383 
0384         bool logarithmic = probe->scaling() == FloatingProbeData::Logarithmic;
0385         double lowerAbsValue = probe->lowerAbsValue();
0386         double sf = m_halfOutputHeight / (logarithmic ? log(probe->upperAbsValue() / lowerAbsValue) : probe->upperAbsValue());
0387 
0388         const int midHeight = Oscilloscope::self()->probePositioner->probePosition(probe);
0389         const int64_t timeOffset = Oscilloscope::self()->scrollTime();
0390 
0391         // Draw the horizontal line indicating the midpoint of our output
0392         p.setPen(QColor(228, 228, 228));
0393         p.drawLine(0, midHeight, width(), midHeight);
0394 
0395         // Set the pen colour according to the colour the user has selected for the probe
0396         p.setPen(probe->color());
0397 
0398         int64_t at = probe->findPos(timeOffset);
0399         const int64_t atEnd = probe->m_data->size();
0400         if (at > atEnd)
0401             at = atEnd;
0402         int64_t prevTime = probe->toTime(at);
0403 
0404         double v = 0;
0405         if (at < atEnd) {
0406             v = (*data)[(at > 0) ? at : 0];
0407         }
0408         int prevY = v_to_y;
0409         int prevX = int((prevTime - timeOffset) * (pixelsPerSecond / LOGIC_UPDATE_RATE));
0410 
0411         while (at < atEnd - 1) {
0412             at++;
0413 
0414             uint64_t nextTime = prevTime + uint64_t(LOGIC_UPDATE_RATE * LINEAR_UPDATE_PERIOD);
0415 
0416             double v = (*data)[(at > 0) ? at : 0];
0417             int nextY = v_to_y;
0418             int nextX = int((nextTime - timeOffset) * (pixelsPerSecond / LOGIC_UPDATE_RATE));
0419 
0420             p.drawLine(prevX, prevY, nextX, nextY);
0421 
0422             prevTime = nextTime;
0423             prevX = nextX;
0424             prevY = nextY;
0425 
0426             if (nextX > width())
0427                 break;
0428         };
0429 
0430         // If we could not draw right to the end; it is because we exceeded
0431         // maxAt
0432         if (prevX < width())
0433             p.drawLine(prevX, prevY, width(), prevY);
0434     }
0435 }
0436 
0437 #include "moc_oscilloscopeview.cpp"