diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index edebe38..2defa82 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -45,6 +45,8 @@ MainWindow::MainWindow() // Connect dock inputs connect(dock, &SpectrogramControls::openFile, this, &MainWindow::openFile); connect(dock->sampleRate, static_cast(&QLineEdit::textChanged), this, static_cast(&MainWindow::setSampleRate)); + connect(dock->timePointerCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableTimePointer); + connect(dock->frequencyPointerCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableFrequencyPointer); connect(dock, static_cast(&SpectrogramControls::fftOrZoomChanged), plots, &PlotView::setFFTAndZoom); connect(dock->powerMaxSlider, &QSlider::valueChanged, plots, &PlotView::setPowerMax); connect(dock->powerMinSlider, &QSlider::valueChanged, plots, &PlotView::setPowerMin); diff --git a/src/plotview.cpp b/src/plotview.cpp index 798d2ab..e731987 100644 --- a/src/plotview.cpp +++ b/src/plotview.cpp @@ -50,6 +50,8 @@ PlotView::PlotView(InputSource *input) : cursors(this), viewRange({0, 0}) auto tunerOutput = std::dynamic_pointer_cast>>(spectrogramPlot->output()); enableScales(true); + timePointerEnabled = false; + frequencyPointerEnabled = false; enableAnnotations(true); enableAnnotationCommentsTooltips(true); @@ -223,6 +225,21 @@ void PlotView::enableCursors(bool enabled) viewport()->update(); } +void PlotView::enableTimePointer(bool enabled) +{ + timePointerEnabled = enabled; + spectrogramPlot->enableTimeFrequencyPointers(timePointerEnabled, frequencyPointerEnabled); + + viewport()->update(); +} + +void PlotView::enableFrequencyPointer(bool enabled) +{ + frequencyPointerEnabled = enabled; + spectrogramPlot->enableTimeFrequencyPointers(timePointerEnabled, frequencyPointerEnabled); + + viewport()->update(); +} bool PlotView::viewportEvent(QEvent *event) { // Handle wheel events for zooming (before the parent's handler to stop normal scrolling) if (event->type() == QEvent::Wheel) { @@ -286,6 +303,9 @@ bool PlotView::viewportEvent(QEvent *event) { if (cursorsEnabled) if (cursors.mouseEvent(event->type(), mouseEvent)) return true; + + if (timePointerEnabled || frequencyPointerEnabled) + updateView(); } if (event->type() == QEvent::Leave) { diff --git a/src/plotview.h b/src/plotview.h index 326d547..5ddfa6a 100644 --- a/src/plotview.h +++ b/src/plotview.h @@ -44,6 +44,8 @@ class PlotView : public QGraphicsView, Subscriber public slots: void cursorsMoved(); + void enableTimePointer(bool enabled); + void enableFrequencyPointer(bool enabled); void enableCursors(bool enabled); void enableScales(bool enabled); void enableAnnotations(bool enabled); @@ -79,6 +81,8 @@ public slots: int zoomLevel = 1; int powerMin; int powerMax; + bool timePointerEnabled; + bool frequencyPointerEnabled; bool cursorsEnabled; double sampleRate = 0.0; bool timeScaleEnabled; diff --git a/src/spectrogramcontrols.cpp b/src/spectrogramcontrols.cpp index 0e0f9aa..5c88118 100644 --- a/src/spectrogramcontrols.cpp +++ b/src/spectrogramcontrols.cpp @@ -41,6 +41,12 @@ SpectrogramControls::SpectrogramControls(const QString & title, QWidget * parent sampleRate->setValidator(double_validator); layout->addRow(new QLabel(tr("Sample rate:")), sampleRate); + // Frequency and time pointer settings + frequencyPointerCheckBox = new QCheckBox(widget); + layout->addRow(new QLabel(tr("Enable frequency pointer:")), frequencyPointerCheckBox); + timePointerCheckBox = new QCheckBox(widget); + layout->addRow(new QLabel(tr("Enable time pointer:")), timePointerCheckBox); + // Spectrogram settings layout->addRow(new QLabel()); // TODO: find a better way to add an empty row? layout->addRow(new QLabel(tr("Spectrogram"))); diff --git a/src/spectrogramcontrols.h b/src/spectrogramcontrols.h index 69e7d60..fa8cb42 100644 --- a/src/spectrogramcontrols.h +++ b/src/spectrogramcontrols.h @@ -63,6 +63,8 @@ private slots: public: QPushButton *fileOpenButton; QLineEdit *sampleRate; + QCheckBox *timePointerCheckBox; + QCheckBox *frequencyPointerCheckBox; QSlider *fftSizeSlider; QSlider *zoomLevelSlider; QSlider *powerMaxSlider; diff --git a/src/spectrogramplot.cpp b/src/spectrogramplot.cpp index c71763e..8281f1a 100644 --- a/src/spectrogramplot.cpp +++ b/src/spectrogramplot.cpp @@ -42,6 +42,8 @@ SpectrogramPlot::SpectrogramPlot(std::shared_ptr if (sigmfAnnotationsEnabled) paintAnnotations(painter, rect, sampleRange); + + if (timePointerEnabled || frequencyPointerEnabled) + paintTimeFrequencyPointers(painter, rect, sampleRange); +} + +void SpectrogramPlot::paintTimeFrequencyPointers(QPainter &painter, QRect &rect, range_t sampleRange) +{ + if (sampleRate == 0) { + return; + } + + if (sampleRate / 2 > UINT64_MAX) { + return; + } + + int plotHeight = rect.height(); + if (inputSource->realSignal()) + plotHeight *= 2; + + double bwPerPixel = (double)sampleRate / plotHeight; + + painter.save(); + + QPen pen(Qt::white, 1, Qt::SolidLine); + painter.setPen(pen); + QFontMetrics fm(painter.font()); + + + char buf[128]; + if (frequencyPointerEnabled) { + int freqHz = ((plotHeight / 2) - mouseY) * bwPerPixel; + snprintf(buf, sizeof(buf), "Frequency: %d Hz", freqHz); + + painter.drawLine(0, mouseY, mouseX, mouseY); + painter.drawText(mouseX + 15, mouseY, buf); + } + if (timePointerEnabled) { + float timeStart = sampleRange.minimum / sampleRate; + float timeEnd = sampleRange.maximum / sampleRate; + float secondsPerPixel = (timeEnd - timeStart) / rect.width(); + float timeAtPointer = timeStart + mouseX * secondsPerPixel; + snprintf(buf, sizeof(buf), "Time: %.6f s", timeAtPointer); + + painter.drawText(mouseX + 15, mouseY + fm.height(), buf); + painter.drawLine(mouseX, 0, mouseX, mouseY); + } + + painter.restore(); } void SpectrogramPlot::paintFrequencyScale(QPainter &painter, QRect &rect) @@ -356,6 +406,11 @@ bool SpectrogramPlot::mouseEvent(QEvent::Type type, QMouseEvent *event) if (tunerEnabled()) return tuner.mouseEvent(type, event); + if (timePointerEnabled || frequencyPointerEnabled) { + mouseX = event->x(); + mouseY = event->y(); + } + return false; } @@ -448,6 +503,11 @@ void SpectrogramPlot::tunerMoved() emit repaint(); } +void SpectrogramPlot::enableTimeFrequencyPointers(bool timePointer, bool frequencyPointer) { + timePointerEnabled = timePointer; + frequencyPointerEnabled = frequencyPointer; +} + uint qHash(const TileCacheKey &key, uint seed) { return key.fftSize ^ key.zoomLevel ^ key.sample ^ seed; diff --git a/src/spectrogramplot.h b/src/spectrogramplot.h index c75f193..ed7d59e 100644 --- a/src/spectrogramplot.h +++ b/src/spectrogramplot.h @@ -74,6 +74,7 @@ class SpectrogramPlot : public Plot bool tunerEnabled(); void enableScales(bool enabled); void enableAnnotations(bool enabled); + void enableTimeFrequencyPointers(bool timePointer, bool frequencyPointer); bool isAnnotationsEnabled(); QString *mouseAnnotationComment(const QMouseEvent *event); @@ -103,6 +104,11 @@ public slots: double sampleRate; bool frequencyScaleEnabled; bool sigmfAnnotationsEnabled; + bool timePointerEnabled; + bool frequencyPointerEnabled; + + int mouseX; + int mouseY; Tuner tuner; std::shared_ptr tunerTransform; @@ -116,6 +122,7 @@ public slots: int linesPerTile(); void paintFrequencyScale(QPainter &painter, QRect &rect); void paintAnnotations(QPainter &painter, QRect &rect, range_t sampleRange); + void paintTimeFrequencyPointers(QPainter &painter, QRect &rect, range_t sampleRange); }; class AnnotationLocation