File indexing completed on 2024-12-08 11:06:57
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"