From 0eff8d451180c8c1328ff0d33de4e2f477147e16 Mon Sep 17 00:00:00 2001 From: Denis Dovzhenko Date: Tue, 20 Mar 2018 15:47:35 +0300 Subject: [PATCH] Cursor measurements (#177) --- openhantek/src/configdialog/DsoConfigScopePage.cpp | 15 +++++++++++++++ openhantek/src/configdialog/DsoConfigScopePage.h | 5 +++++ openhantek/src/dsowidget.cpp | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------- openhantek/src/dsowidget.h | 13 ++++++++++--- openhantek/src/exporting/legacyexportdrawer.cpp | 6 ++++-- openhantek/src/glscope.cpp | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------- openhantek/src/glscope.h | 21 +++++++++++++++------ openhantek/src/mainwindow.cpp | 14 ++++++++++++++ openhantek/src/mainwindow.ui | 13 ++++++++++++- openhantek/src/scopesettings.h | 40 ++++++++++++++++++++++++++++++---------- openhantek/src/settings.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- openhantek/src/viewsettings.h | 2 ++ openhantek/src/widgets/datagrid.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/widgets/datagrid.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ openhantek/src/widgets/sispinbox.cpp | 25 +++++++++++++------------ 15 files changed, 699 insertions(+), 129 deletions(-) create mode 100644 openhantek/src/widgets/datagrid.cpp create mode 100644 openhantek/src/widgets/datagrid.h diff --git a/openhantek/src/configdialog/DsoConfigScopePage.cpp b/openhantek/src/configdialog/DsoConfigScopePage.cpp index 2c7fdcf..c01a150 100644 --- a/openhantek/src/configdialog/DsoConfigScopePage.cpp +++ b/openhantek/src/configdialog/DsoConfigScopePage.cpp @@ -27,8 +27,22 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : graphGroup = new QGroupBox(tr("Graph")); graphGroup->setLayout(graphLayout); + cursorsLabel = new QLabel(tr("Position")); + cursorsComboBox = new QComboBox(); + cursorsComboBox->addItem("Left", Qt::LeftToolBarArea); + cursorsComboBox->addItem("Right", Qt::RightToolBarArea); + cursorsComboBox->setCurrentIndex(settings->view.cursorGridPosition == Qt::LeftToolBarArea ? 0 : 1); + + cursorsLayout = new QGridLayout(); + cursorsLayout->addWidget(cursorsLabel, 0, 0); + cursorsLayout->addWidget(cursorsComboBox, 0, 1); + + cursorsGroup = new QGroupBox(tr("Cursors")); + cursorsGroup->setLayout(cursorsLayout); + mainLayout = new QVBoxLayout(); mainLayout->addWidget(graphGroup); + mainLayout->addWidget(cursorsGroup); mainLayout->addStretch(1); setLayout(mainLayout); @@ -38,4 +52,5 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : void DsoConfigScopePage::saveSettings() { settings->view.interpolation = (Dso::InterpolationMode)interpolationComboBox->currentIndex(); settings->view.digitalPhosphorDepth = digitalPhosphorDepthSpinBox->value(); + settings->view.cursorGridPosition = (Qt::ToolBarArea)cursorsComboBox->currentData().toUInt(); } diff --git a/openhantek/src/configdialog/DsoConfigScopePage.h b/openhantek/src/configdialog/DsoConfigScopePage.h index 77f33bd..a185a35 100644 --- a/openhantek/src/configdialog/DsoConfigScopePage.h +++ b/openhantek/src/configdialog/DsoConfigScopePage.h @@ -37,4 +37,9 @@ class DsoConfigScopePage : public QWidget { QSpinBox *digitalPhosphorDepthSpinBox; QLabel *interpolationLabel; QComboBox *interpolationComboBox; + + QGroupBox *cursorsGroup; + QGridLayout *cursorsLayout; + QLabel *cursorsLabel; + QComboBox *cursorsComboBox; }; diff --git a/openhantek/src/dsowidget.cpp b/openhantek/src/dsowidget.cpp index 0d1da5f..f8c43fc 100644 --- a/openhantek/src/dsowidget.cpp +++ b/openhantek/src/dsowidget.cpp @@ -22,6 +22,7 @@ #include "viewconstants.h" #include "viewsettings.h" #include "widgets/levelslider.h" +#include "widgets/datagrid.h" static int zoomScopeRow = 0; @@ -38,12 +39,14 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: setupSliders(mainSliders); setupSliders(zoomSliders); - connect(mainScope, &GlScope::markerMoved, [this](int marker, double position) { - double value = std::round(position / MARKER_STEP) * MARKER_STEP; - - this->scope->horizontal.marker[marker] = value; - this->mainSliders.markerSlider->setValue(marker, value); - this->mainScope->markerUpdated(); + connect(mainScope, &GlScope::markerMoved, [this](unsigned cursorIndex, unsigned marker) { + mainSliders.markerSlider->setValue(marker, this->scope->getMarker(marker)); + mainScope->updateCursor(cursorIndex); + zoomScope->updateCursor(cursorIndex); + }); + connect(zoomScope, &GlScope::markerMoved, [this](unsigned cursorIndex, unsigned marker) { + mainScope->updateCursor(cursorIndex); + zoomScope->updateCursor(cursorIndex); }); // The table for the settings @@ -142,40 +145,88 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: updateSpectrumDetails((unsigned)channel); } + // Cursors + cursorDataGrid = new DataGrid(this); + cursorDataGrid->setBackgroundColor(view->screen.background); + cursorDataGrid->addItem(tr("Markers"), view->screen.text); + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { + cursorDataGrid->addItem(scope->voltage[channel].name, view->screen.voltage[channel]); + } + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel) { + cursorDataGrid->addItem(scope->spectrum[channel].name, view->screen.spectrum[channel]); + } + cursorDataGrid->selectItem(0); + + connect(cursorDataGrid, &DataGrid::itemSelected, [this] (unsigned index) { + mainScope->cursorSelected(index); + zoomScope->cursorSelected(index); + }); + connect(cursorDataGrid, &DataGrid::itemUpdated, [this, scope] (unsigned index) { + unsigned channelCount = scope->countChannels(); + if (0 < index && index < channelCount + 1) { + ChannelID channel = index - 1; + if (scope->voltage[channel].used) { + unsigned shape = (unsigned)scope->voltage[channel].cursor.shape; + if (shape == DsoSettingsScopeCursor::NONE) { + scope->voltage[channel].cursor.shape = DsoSettingsScopeCursor::RECTANGULAR; + } else { + scope->voltage[channel].cursor.shape = DsoSettingsScopeCursor::NONE; + } + } + } else if (channelCount < index && index < 2 * channelCount + 1) { + ChannelID channel = index - channelCount - 1; + if (scope->spectrum[channel].used) { + unsigned shape = (unsigned)scope->spectrum[channel].cursor.shape; + if (shape == DsoSettingsScopeCursor::NONE) { + scope->spectrum[channel].cursor.shape = DsoSettingsScopeCursor::RECTANGULAR; + } else { + scope->spectrum[channel].cursor.shape = DsoSettingsScopeCursor::NONE; + } + } + } + updateMarkerDetails(); + mainScope->updateCursor(index); + zoomScope->updateCursor(index); + }); + + scope->horizontal.cursor.shape = DsoSettingsScopeCursor::VERTICAL; + // The layout for the widgets mainLayout = new QGridLayout(); - mainLayout->setColumnStretch(2, 1); // Scopes increase their size + mainLayout->setColumnStretch(3, 1); // Scopes increase their size // Bars around the scope, needed because the slider-drawing-area is outside // the scope at min/max - mainLayout->setColumnMinimumWidth(1, mainSliders.triggerPositionSlider->preMargin()); - mainLayout->setColumnMinimumWidth(3, mainSliders.triggerPositionSlider->postMargin()); + mainLayout->setColumnMinimumWidth(2, mainSliders.triggerPositionSlider->preMargin()); + mainLayout->setColumnMinimumWidth(4, mainSliders.triggerPositionSlider->postMargin()); mainLayout->setSpacing(0); int row = 0; - mainLayout->addLayout(settingsLayout, row++, 0, 1, 5); + mainLayout->addLayout(settingsLayout, row++, 1, 1, 5); // 5x5 box for mainScope & mainSliders mainLayout->setRowMinimumHeight(row + 1, mainSliders.offsetSlider->preMargin()); mainLayout->setRowMinimumHeight(row + 3, mainSliders.offsetSlider->postMargin()); mainLayout->setRowStretch(row + 2, 1); - mainLayout->addWidget(mainScope, row + 2, 2); - mainLayout->addWidget(mainSliders.offsetSlider, row + 1, 0, 3, 2, Qt::AlignRight); - mainLayout->addWidget(mainSliders.triggerPositionSlider, row, 1, 2, 3, Qt::AlignBottom); - mainLayout->addWidget(mainSliders.triggerLevelSlider, row + 1, 3, 3, 2, Qt::AlignLeft); - mainLayout->addWidget(mainSliders.markerSlider, row + 3, 1, 2, 3, Qt::AlignTop); + mainLayout->addWidget(mainScope, row + 2, 3); + mainLayout->addWidget(mainSliders.offsetSlider, row + 1, 1, 3, 2, Qt::AlignRight); + mainLayout->addWidget(mainSliders.triggerPositionSlider, row, 2, 2, 3, Qt::AlignBottom); + mainLayout->addWidget(mainSliders.triggerLevelSlider, row + 1, 4, 3, 2, Qt::AlignLeft); + mainLayout->addWidget(mainSliders.markerSlider, row + 3, 2, 2, 3, Qt::AlignTop); row += 5; // Separators and markerLayout - mainLayout->setRowMinimumHeight(row++, 4); - mainLayout->addLayout(markerLayout, row++, 0, 1, 5); + mainLayout->setRowMinimumHeight(row++, 5); + mainLayout->addLayout(markerLayout, row++, 1, 1, 5); mainLayout->setRowMinimumHeight(row++, 4); // 5x5 box for zoomScope & zoomSliders zoomScopeRow = row + 2; - mainLayout->addWidget(zoomScope, zoomScopeRow, 2); - mainLayout->addWidget(zoomSliders.offsetSlider, row + 1, 0, 3, 2, Qt::AlignRight); - mainLayout->addWidget(zoomSliders.triggerPositionSlider, row, 1, 2, 3, Qt::AlignBottom); - mainLayout->addWidget(zoomSliders.triggerLevelSlider, row + 1, 3, 3, 2, Qt::AlignLeft); + mainLayout->addWidget(zoomScope, zoomScopeRow, 3); + mainLayout->addWidget(zoomSliders.offsetSlider, row + 1, 1, 3, 2, Qt::AlignRight); + mainLayout->addWidget(zoomSliders.triggerPositionSlider, row, 2, 2, 3, Qt::AlignBottom); + mainLayout->addWidget(zoomSliders.triggerLevelSlider, row + 1, 4, 3, 2, Qt::AlignLeft); row += 5; // Separator and embedded measurementLayout mainLayout->setRowMinimumHeight(row++, 8); - mainLayout->addLayout(measurementLayout, row++, 0, 1, 5); + mainLayout->addLayout(measurementLayout, row++, 1, 1, 5); + + updateCursorGrid(view->cursorsVisible); // The widget itself setPalette(palette); @@ -187,20 +238,54 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: connect(mainSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset); connect(zoomSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset); - connect(mainSliders.triggerPositionSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerPosition); - zoomSliders.triggerPositionSlider->setEnabled(false); + connect(mainSliders.triggerPositionSlider, &LevelSlider::valueChanged, [this](int index, double value) { + updateTriggerPosition(index, value, true); + }); + connect(zoomSliders.triggerPositionSlider, &LevelSlider::valueChanged, [this](int index, double value) { + updateTriggerPosition(index, value, false); + }); connect(mainSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel); connect(zoomSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel); connect(mainSliders.markerSlider, &LevelSlider::valueChanged, [this](int index, double value) { updateMarker(index, value); - mainScope->update(); - zoomScope->update(); + mainScope->updateCursor(); + zoomScope->updateCursor(); }); zoomSliders.markerSlider->setEnabled(false); } +void DsoWidget::updateCursorGrid(bool enabled) { + if (!enabled) { + cursorDataGrid->selectItem(0); + cursorDataGrid->setParent(nullptr); + mainScope->cursorSelected(0); + zoomScope->cursorSelected(0); + return; + } + + switch (view->cursorGridPosition) { + case Qt::LeftToolBarArea: + if (mainLayout->itemAtPosition(0, 0) == nullptr) { + cursorDataGrid->setParent(nullptr); + mainLayout->addWidget(cursorDataGrid, 0, 0, mainLayout->rowCount(), 1); + } + break; + case Qt::RightToolBarArea: + if (mainLayout->itemAtPosition(0, 6) == nullptr) { + cursorDataGrid->setParent(nullptr); + mainLayout->addWidget(cursorDataGrid, 0, 6, mainLayout->rowCount(), 1); + } + break; + default: + if (cursorDataGrid->parent() != nullptr) { + cursorDataGrid->setParent(nullptr); + } + break; + } +} + void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) { // The offset sliders for all possible channels sliders.offsetSlider = new LevelSlider(Qt::RightArrow); @@ -248,7 +333,7 @@ void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) { sliders.markerSlider->addSlider(QString::number(marker + 1), marker); sliders.markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2); sliders.markerSlider->setStep(marker, MARKER_STEP); - sliders.markerSlider->setValue(marker, scope->horizontal.marker[marker]); + sliders.markerSlider->setValue(marker, scope->horizontal.cursor.pos[marker].x()); sliders.markerSlider->setIndexVisible(marker, true); } } @@ -285,7 +370,7 @@ void DsoWidget::setMeasurementVisible(ChannelID channel) { } static QString markerToString(DsoSettingsScope *scope, unsigned index) { - double value = (DIVS_TIME * (0.5 - scope->trigger.position) + scope->horizontal.marker[index]) * scope->horizontal.timebase; + double value = (DIVS_TIME * (0.5 - scope->trigger.position) + scope->getMarker(index)) * scope->horizontal.timebase; int precision = 3 - (int)floor(log10(fabs(value))); if (scope->horizontal.timebase < 1e-9) @@ -302,8 +387,9 @@ static QString markerToString(DsoSettingsScope *scope, unsigned index) { /// \brief Update the label about the marker measurements void DsoWidget::updateMarkerDetails() { - double divs = fabs(scope->horizontal.marker[1] - scope->horizontal.marker[0]); + double divs = fabs(scope->horizontal.cursor.pos[1].x() - scope->horizontal.cursor.pos[0].x()); double time = divs * scope->horizontal.timebase; + double freq = divs * scope->horizontal.frequencybase; QString prefix(tr("Markers")); if (view->zoom) { @@ -315,6 +401,36 @@ void DsoWidget::updateMarkerDetails() { markerInfoLabel->setText(prefix.append(": %1 %2").arg(markerToString(scope, 0)).arg(markerToString(scope, 1))); markerTimeLabel->setText(valueToString(time, UNIT_SECONDS, 4)); markerFrequencyLabel->setText(valueToString(1.0 / time, UNIT_HERTZ, 4)); + + int index = 0; + cursorDataGrid->updateInfo(index++, true, QString(), valueToString(time, UNIT_SECONDS, 4), valueToString(freq, UNIT_HERTZ, 4)); + + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { + if (scope->voltage[channel].used) { + QPointF p0 = scope->voltage[channel].cursor.pos[0]; + QPointF p1 = scope->voltage[channel].cursor.pos[1]; + cursorDataGrid->updateInfo(index, true, + scope->voltage[channel].cursor.shape != DsoSettingsScopeCursor::NONE ? tr("ON") : tr("OFF"), + valueToString(fabs(p1.x() - p0.x()) * scope->horizontal.timebase, UNIT_SECONDS, 4), + valueToString(fabs(p1.y() - p0.y()) * scope->gain(channel), UNIT_VOLTS, 4)); + } else { + cursorDataGrid->updateInfo(index, false); + } + ++index; + } + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel) { + if (scope->spectrum[channel].used) { + QPointF p0 = scope->spectrum[channel].cursor.pos[0]; + QPointF p1 = scope->spectrum[channel].cursor.pos[1]; + cursorDataGrid->updateInfo(index, true, + scope->spectrum[channel].cursor.shape != DsoSettingsScopeCursor::NONE ? tr("ON") : tr("OFF"), + valueToString(fabs(p1.x() - p0.x()) * scope->horizontal.frequencybase, UNIT_HERTZ, 4), + valueToString(fabs(p1.y() - p0.y()) * scope->spectrum[channel].magnitude, UNIT_DECIBEL, 4)); + } else { + cursorDataGrid->updateInfo(index, false); + } + ++index; + } } /// \brief Update the label about the trigger settings @@ -385,10 +501,13 @@ void DsoWidget::updateSpectrumMagnitude(ChannelID channel) { updateSpectrumDetai void DsoWidget::updateSpectrumUsed(ChannelID channel, bool used) { if (channel >= (unsigned int)scope->voltage.size()) return; +// if (!used && cursorDataGrid->spectrumCursors[channel].selector->isChecked()) cursorDataGrid->selectItem(0); + mainSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used); zoomSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used); updateSpectrumDetails(channel); + updateMarkerDetails(); } /// \brief Handles modeChanged signal from the trigger dock. @@ -452,6 +571,8 @@ void DsoWidget::updateVoltageGain(ChannelID channel) { void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) { if (channel >= (unsigned int)scope->voltage.size()) return; +// if (!used && cursorDataGrid->voltageCursors[channel].selector->isChecked()) cursorDataGrid->selectItem(0); + mainSliders.offsetSlider->setIndexVisible(channel, used); zoomSliders.offsetSlider->setIndexVisible(channel, used); @@ -460,6 +581,7 @@ void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) { setMeasurementVisible(channel); updateVoltageDetails(channel); + updateMarkerDetails(); } /// \brief Change the record length. @@ -549,11 +671,11 @@ void DsoWidget::updateOffset(ChannelID channel, double value) { if (channel < scope->voltage.size() * 2) { if (mainSliders.offsetSlider->value(channel) != value) { - QSignalBlocker blocker(mainSliders.offsetSlider); + const QSignalBlocker blocker(mainSliders.offsetSlider); mainSliders.offsetSlider->setValue(channel, value); } if (zoomSliders.offsetSlider->value(channel) != value) { - QSignalBlocker blocker(zoomSliders.offsetSlider); + const QSignalBlocker blocker(zoomSliders.offsetSlider); zoomSliders.offsetSlider->setValue(channel, value); } } @@ -561,18 +683,38 @@ void DsoWidget::updateOffset(ChannelID channel, double value) { emit offsetChanged(channel, value); } +/// \brief Translate horizontal position (0..1) from main view to zoom view. +double DsoWidget::mainToZoom(double position) const { + double m1 = scope->getMarker(0); + double m2 = scope->getMarker(1); + if (m1 > m2) std::swap(m1, m2); + return ((position - 0.5) * DIVS_TIME - m1) / (m2 - m1); +} + +/// \brief Translate horizontal position (0..1) from zoom view to main view. +double DsoWidget::zoomToMain(double position) const { + double m1 = scope->getMarker(0); + double m2 = scope->getMarker(1); + if (m1 > m2) std::swap(m1, m2); + return 0.5 + (m1 + position * (m2 - m1)) / DIVS_TIME; +} + /// \brief Handles signals affecting trigger position in the zoom view. void DsoWidget::adaptTriggerPositionSlider() { - double m1 = scope->horizontal.marker[0]; - double m2 = scope->horizontal.marker[1]; + double value = mainToZoom(scope->trigger.position); - if (m1 > m2) std::swap(m1, m2); - double value = (scope->trigger.position - 0.5) * DIVS_TIME; - if (m1 != m2 && m1 <= value && value <= m2) { - zoomSliders.triggerPositionSlider->setIndexVisible(0, true); - zoomSliders.triggerPositionSlider->setValue(0, (value - m1) / (m2 - m1)); + LevelSlider &slider = *zoomSliders.triggerPositionSlider; + const QSignalBlocker blocker(slider); + if (slider.minimum(0) <= value && value <= slider.maximum(0)) { + slider.setEnabled(true); + slider.setValue(0, value); } else { - zoomSliders.triggerPositionSlider->setIndexVisible(0, false); + slider.setEnabled(false); + if (value < slider.minimum(0)) { + slider.setValue(0, slider.minimum(0)); + } else { + slider.setValue(0, slider.maximum(0)); + } } } @@ -580,16 +722,22 @@ void DsoWidget::adaptTriggerPositionSlider() { /// \param index The index of the slider. /// \param value The new triggerPosition in seconds relative to the first /// sample. -void DsoWidget::updateTriggerPosition(int index, double value) { +void DsoWidget::updateTriggerPosition(int index, double value, bool mainView) { if (index != 0) return; - scope->trigger.position = value; - adaptTriggerPositionSlider(); + if (mainView) { + scope->trigger.position = value; + adaptTriggerPositionSlider(); + } else { + scope->trigger.position = zoomToMain(value); + const QSignalBlocker blocker(mainSliders.triggerPositionSlider); + mainSliders.triggerPositionSlider->setValue(index, scope->trigger.position); + } updateTriggerDetails(); updateMarkerDetails(); - emit triggerPositionChanged(value * scope->horizontal.timebase * DIVS_TIME); + emit triggerPositionChanged(scope->trigger.position * scope->horizontal.timebase * DIVS_TIME); } /// \brief Handles valueChanged signal from the trigger level slider. @@ -599,11 +747,11 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) { scope->voltage[channel].trigger = value; if (mainSliders.triggerLevelSlider->value(channel) != value) { - QSignalBlocker blocker(mainSliders.triggerLevelSlider); + const QSignalBlocker blocker(mainSliders.triggerLevelSlider); mainSliders.triggerLevelSlider->setValue(channel, value); } if (zoomSliders.triggerLevelSlider->value(channel) != value) { - QSignalBlocker blocker(zoomSliders.triggerLevelSlider); + const QSignalBlocker blocker(zoomSliders.triggerLevelSlider); zoomSliders.triggerLevelSlider->setValue(channel, value); } @@ -616,10 +764,7 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) { /// \param marker The index of the slider. /// \param value The new marker position. void DsoWidget::updateMarker(int marker, double value) { - scope->horizontal.marker[marker] = value; - + scope->setMarker(marker, value); adaptTriggerPositionSlider(); updateMarkerDetails(); - - emit markerChanged(marker, value); } diff --git a/openhantek/src/dsowidget.h b/openhantek/src/dsowidget.h index 6b63b93..e1aca1f 100644 --- a/openhantek/src/dsowidget.h +++ b/openhantek/src/dsowidget.h @@ -15,12 +15,15 @@ class SpectrumGenerator; struct DsoSettingsScope; struct DsoSettingsView; +class DataGrid; /// \brief The widget for the oszilloscope-screen /// This widget contains the scopes and all level sliders. class DsoWidget : public QWidget { Q_OBJECT + public: + struct Sliders { LevelSlider *offsetSlider; ///< The sliders for the graph offsets LevelSlider *triggerPositionSlider; ///< The slider for the pretrigger @@ -28,7 +31,6 @@ class DsoWidget : public QWidget { LevelSlider *markerSlider; ///< The sliders for the markers }; - public: /// \brief Initializes the components of the oszilloscope-screen. /// \param settings The settings object containing the oscilloscope settings. /// \param dataAnalyzer The data analyzer that should be used as data source. @@ -50,6 +52,9 @@ class DsoWidget : public QWidget { void updateTriggerDetails(); void updateVoltageDetails(ChannelID channel); + double mainToZoom(double position) const; + double zoomToMain(double position) const; + Sliders mainSliders; Sliders zoomSliders; @@ -79,6 +84,8 @@ class DsoWidget : public QWidget { std::vector measurementAmplitudeLabel; ///< Amplitude of the signal (V) std::vector measurementFrequencyLabel; ///< Frequency of the signal (Hz) + DataGrid *cursorDataGrid; + DsoSettingsScope* scope; DsoSettingsView* view; const Dso::ControlSpecification* spec; @@ -113,11 +120,12 @@ class DsoWidget : public QWidget { // Scope control void updateZoom(bool enabled); + void updateCursorGrid(bool enabled); private slots: // Sliders void updateOffset(ChannelID channel, double value); - void updateTriggerPosition(int index, double value); + void updateTriggerPosition(int index, double value, bool mainView = true); void updateTriggerLevel(ChannelID channel, double value); void updateMarker(int marker, double value); @@ -126,5 +134,4 @@ class DsoWidget : public QWidget { void offsetChanged(ChannelID channel, double value); ///< A graph offset has been changed void triggerPositionChanged(double value); ///< The pretrigger has been changed void triggerLevelChanged(ChannelID channel, double value); ///< A trigger level has been changed - void markerChanged(unsigned int marker, double value); ///< A marker position has been changed }; diff --git a/openhantek/src/exporting/legacyexportdrawer.cpp b/openhantek/src/exporting/legacyexportdrawer.cpp index 094619c..20cb7ba 100644 --- a/openhantek/src/exporting/legacyexportdrawer.cpp +++ b/openhantek/src/exporting/legacyexportdrawer.cpp @@ -115,10 +115,12 @@ bool LegacyExportDrawer::exportSamples(const PPresult *result, QPaintDevice* pai painter.setPen(colorValues->text); // Calculate variables needed for zoomed scope - double divs = fabs(settings->scope.horizontal.marker[1] - settings->scope.horizontal.marker[0]); + double m1 = settings->scope.getMarker(0); + double m2 = settings->scope.getMarker(1); + double divs = fabs(m2 - m1); double time = divs * settings->scope.horizontal.timebase; double zoomFactor = DIVS_TIME / divs; - double zoomOffset = (settings->scope.horizontal.marker[0] + settings->scope.horizontal.marker[1]) / 2; + double zoomOffset = (m1 + m2) / 2; if (settings->view.zoom) { scopeHeight = (double)(paintDevice->height() - (channelCount + 5) * lineHeight) / 2; diff --git a/openhantek/src/glscope.cpp b/openhantek/src/glscope.cpp index e5990d1..f6b0e71 100644 --- a/openhantek/src/glscope.cpp +++ b/openhantek/src/glscope.cpp @@ -54,50 +54,106 @@ void GlScope::fixOpenGLversion(QSurfaceFormat::RenderableType t) { GlScope::GlScope(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent) : QOpenGLWidget(parent), scope(scope), view(view) { - vaMarker.resize(MARKER_COUNT); + cursorInfo.clear(); + cursorInfo.push_back(&scope->horizontal.cursor); + selectedCursor = 0; + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { + cursorInfo.push_back(&scope->voltage[channel].cursor); + } + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel) { + cursorInfo.push_back(&scope->spectrum[channel].cursor); + } + vaMarker.resize(cursorInfo.size()); } GlScope::~GlScope() {/* virtual destructor necessary */} +QPointF GlScope::eventToPosition(QMouseEvent *event) { + QPointF position((double)(event->x() - width() / 2) * DIVS_TIME / (double)width(), + (double)(height() / 2 - event->y()) * DIVS_VOLTAGE / (double)height()); + if (zoomed) { + double m1 = scope->getMarker(0); + double m2 = scope->getMarker(1); + if (m1 > m2) std::swap(m1, m2); + position.setX(m1 + (0.5 + (position.x() / DIVS_TIME)) * (m2 - m1)); + } + return position; +} + void GlScope::mousePressEvent(QMouseEvent *event) { - if (!zoomed && event->button() == Qt::LeftButton) { - double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width(); - double distance = DIVS_TIME; + if (!(zoomed && selectedCursor == 0) && event->button() == Qt::LeftButton) { + QPointF position = eventToPosition(event); selectedMarker = NO_MARKER; + DsoSettingsScopeCursor *cursor = cursorInfo[selectedCursor]; // Capture nearest marker located within snap area (+/- 1% of full scale). - for (unsigned marker = 0; marker < MARKER_COUNT; ++marker) { - if (!scope->horizontal.marker_visible[marker]) continue; - if (fabs(scope->horizontal.marker[marker] - position) < std::min(distance, DIVS_TIME / 100.0)) { - distance = fabs(scope->horizontal.marker[marker] - position); - selectedMarker = marker; + double dX0 = fabs(cursor->pos[0].x() - position.x()); + double dX1 = fabs(cursor->pos[1].x() - position.x()); + double dY0 = fabs(cursor->pos[0].y() - position.y()); + double dY1 = fabs(cursor->pos[1].y() - position.y()); + + switch (cursor->shape) { + case DsoSettingsScopeCursor::RECTANGULAR: + if (std::min(dX0, dX1) < 1.0 / DIVS_SUB && std::min(dY0, dY1) < 1.0 / DIVS_SUB) { + // Do we need to swap Y-coords? + if ((dX0 < dX1 && dY0 > dY1) || (dX0 > dX1 && dY0 < dY1)) { + std::swap(cursor->pos[0].ry(), cursor->pos[1].ry()); + } + selectedMarker = (dX0 < dX1) ? 0 : 1; } + break; + case DsoSettingsScopeCursor::VERTICAL: + if (dX0 < dX1) { + if (dX0 < 1.0 / DIVS_SUB) selectedMarker = 0; + } else { + if (dX1 < 1.0 / DIVS_SUB) selectedMarker = 1; + } + break; + case DsoSettingsScopeCursor::HORIZONTAL: + if (dY0 < dY1) { + if (dY0 < 1.0 / DIVS_SUB) selectedMarker = 0; + } else { + if (dY1 < 1.0 / DIVS_SUB) selectedMarker = 1; + } + break; + case DsoSettingsScopeCursor::NONE: + break; + default: + break; + } + if (selectedMarker != NO_MARKER) { + cursorInfo[selectedCursor]->pos[selectedMarker] = position; + if (selectedCursor == 0) emit markerMoved(selectedCursor, selectedMarker); } - if (selectedMarker != NO_MARKER) { emit markerMoved(selectedMarker, position); } } event->accept(); } void GlScope::mouseMoveEvent(QMouseEvent *event) { - if (!zoomed && (event->buttons() & Qt::LeftButton) != 0) { - double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width(); + if (!(zoomed && selectedCursor == 0) && (event->buttons() & Qt::LeftButton) != 0) { + QPointF position = eventToPosition(event); if (selectedMarker == NO_MARKER) { // User started draging outside the snap area of any marker: // move all markers to current position and select last marker in the array. for (unsigned marker = 0; marker < MARKER_COUNT; ++marker) { - emit markerMoved(marker, position); + cursorInfo[selectedCursor]->pos[marker] = position; + emit markerMoved(selectedCursor, marker); selectedMarker = marker; } } else if (selectedMarker < MARKER_COUNT) { - emit markerMoved(selectedMarker, position); + cursorInfo[selectedCursor]->pos[selectedMarker] = position; + emit markerMoved(selectedCursor, selectedMarker); } } event->accept(); } void GlScope::mouseReleaseEvent(QMouseEvent *event) { - if (!zoomed && event->button() == Qt::LeftButton) { - double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width(); - if (selectedMarker < MARKER_COUNT) { emit markerMoved(selectedMarker, position); } + if (!(zoomed && selectedCursor == 0) && event->button() == Qt::LeftButton) { + QPointF position = eventToPosition(event); + if (selectedMarker < MARKER_COUNT) { + cursorInfo[selectedCursor]->pos[selectedMarker] = position; + emit markerMoved(selectedCursor, selectedMarker); + } selectedMarker = NO_MARKER; } event->accept(); @@ -209,12 +265,11 @@ void GlScope::initializeGL() { m_marker.create(); m_marker.bind(); m_marker.setUsagePattern(QOpenGLBuffer::StaticDraw); - m_marker.allocate(int(vaMarker.size() * sizeof(Line))); + m_marker.allocate(int(vaMarker.size() * sizeof(Vertices))); program->enableAttributeArray(vertexLocation); program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 0); } - - markerUpdated(); + updateCursor(); m_program = std::move(program); shaderCompileSuccess = true; @@ -239,18 +294,65 @@ void GlScope::showData(std::shared_ptr data) { update(); } -void GlScope::markerUpdated() { - - for (unsigned marker = 0; marker < vaMarker.size(); ++marker) { - if (!scope->horizontal.marker_visible[marker]) continue; - vaMarker[marker] = {QVector3D((GLfloat)scope->horizontal.marker[marker], -DIVS_VOLTAGE, 0.0f), - QVector3D((GLfloat)scope->horizontal.marker[marker], DIVS_VOLTAGE, 0.0f)}; +void GlScope::generateVertices(unsigned marker, const DsoSettingsScopeCursor &cursor) { + const float Z_ORDER = 1.0f; + switch (cursor.shape) { + case DsoSettingsScopeCursor::NONE: + vaMarker[marker] = { + QVector3D(-DIVS_TIME, -DIVS_VOLTAGE, Z_ORDER), + QVector3D(-DIVS_TIME, DIVS_VOLTAGE, Z_ORDER), + QVector3D( DIVS_TIME, DIVS_VOLTAGE, Z_ORDER), + QVector3D( DIVS_TIME, -DIVS_VOLTAGE, Z_ORDER) + }; + break; + case DsoSettingsScopeCursor::VERTICAL: + vaMarker[marker] = { + QVector3D(cursor.pos[0].x(), -DIVS_VOLTAGE, Z_ORDER), + QVector3D(cursor.pos[0].x(), DIVS_VOLTAGE, Z_ORDER), + QVector3D(cursor.pos[1].x(), DIVS_VOLTAGE, Z_ORDER), + QVector3D(cursor.pos[1].x(), -DIVS_VOLTAGE, Z_ORDER) + }; + break; + case DsoSettingsScopeCursor::HORIZONTAL: + vaMarker[marker] = { + QVector3D(-DIVS_TIME, cursor.pos[0].y(), Z_ORDER), + QVector3D( DIVS_TIME, cursor.pos[0].y(), Z_ORDER), + QVector3D( DIVS_TIME, cursor.pos[1].y(), Z_ORDER), + QVector3D(-DIVS_TIME, cursor.pos[1].y(), Z_ORDER) + }; + break; + case DsoSettingsScopeCursor::RECTANGULAR: + if ((cursor.pos[1].x() - cursor.pos[0].x()) * (cursor.pos[1].y() - cursor.pos[0].y()) > 0.0) { + vaMarker[marker] = { + QVector3D(cursor.pos[0].x(), cursor.pos[0].y(), Z_ORDER), + QVector3D(cursor.pos[1].x(), cursor.pos[0].y(), Z_ORDER), + QVector3D(cursor.pos[1].x(), cursor.pos[1].y(), Z_ORDER), + QVector3D(cursor.pos[0].x(), cursor.pos[1].y(), Z_ORDER) + }; + } else { + vaMarker[marker] = { + QVector3D(cursor.pos[0].x(), cursor.pos[0].y(), Z_ORDER), + QVector3D(cursor.pos[0].x(), cursor.pos[1].y(), Z_ORDER), + QVector3D(cursor.pos[1].x(), cursor.pos[1].y(), Z_ORDER), + QVector3D(cursor.pos[1].x(), cursor.pos[0].y(), Z_ORDER) + }; + } + break; + default: + break; } +} +void GlScope::updateCursor(unsigned index) { + if (index > 0) { + generateVertices(index, *cursorInfo[index]); + } else for (index = 0; index < cursorInfo.size(); ++index) { + generateVertices(index, *cursorInfo[index]); + } // Write coordinates to GPU makeCurrent(); m_marker.bind(); - m_marker.write(0, vaMarker.data(), vaMarker.size() * sizeof(Line)); + m_marker.write(0, vaMarker.data(), vaMarker.size() * sizeof(Vertices)); } void GlScope::paintGL() { @@ -268,12 +370,13 @@ void GlScope::paintGL() { // Apply zoom settings via matrix transformation if (zoomed) { QMatrix4x4 m; - m.scale(QVector3D(DIVS_TIME / (GLfloat)fabs(scope->horizontal.marker[1] - scope->horizontal.marker[0]), 1.0f, - 1.0f)); - m.translate((GLfloat) - (scope->horizontal.marker[0] + scope->horizontal.marker[1]) / 2, 0.0f, 0.0f); + m.scale(QVector3D(DIVS_TIME / (GLfloat)fabs(scope->getMarker(1) - scope->getMarker(0)), 1.0f, 1.0f)); + m.translate((GLfloat) - (scope->getMarker(0) + scope->getMarker(1)) / 2, 0.0f, 0.0f); m_program->setUniformValue(matrixLocation, pmvMatrix * m); } + drawMarkers(); + unsigned historyIndex = 0; for (Graph &graph : m_GraphHistory) { for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { @@ -287,8 +390,6 @@ void GlScope::paintGL() { if (zoomed) { m_program->setUniformValue(matrixLocation, pmvMatrix); } - if (!this->zoomed) drawMarkers(); - drawGrid(); m_program->release(); } @@ -432,21 +533,38 @@ void GlScope::drawGrid() { m_vaoGrid[2].release(); } +void GlScope::drawVertices(QOpenGLFunctions *gl, unsigned marker, QColor color) { + m_program->setUniformValue(colorLocation, (marker == selectedCursor) ? color : color.darker()); + gl->glDrawArrays(GL_LINE_LOOP, GLint(marker * VERTICES_ARRAY_SIZE), VERTICES_ARRAY_SIZE); + if (cursorInfo[marker]->shape == DsoSettingsScopeCursor::RECTANGULAR) { + color.setAlphaF(0.25); + m_program->setUniformValue(colorLocation, color.darker()); + gl->glDrawArrays(GL_TRIANGLE_FAN, GLint(marker * VERTICES_ARRAY_SIZE), VERTICES_ARRAY_SIZE); + } +} + void GlScope::drawMarkers() { auto *gl = context()->functions(); - QColor trColor = view->screen.markers; - m_program->setUniformValue(colorLocation, trColor); m_vaoMarker.bind(); - // Draw all - gl->glLineWidth(1); - gl->glDrawArrays(GL_LINES, 0, (GLsizei)vaMarker.size() * 2); + unsigned marker = 0; + drawVertices(gl, marker, view->screen.markers); + ++marker; - // Draw selected - if (selectedMarker != NO_MARKER) { - gl->glLineWidth(3); - gl->glDrawArrays(GL_LINES, selectedMarker * 2, (GLsizei)2); + if (view->cursorsVisible) { + gl->glDepthMask(GL_FALSE); + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel, ++marker) { + if (scope->voltage[channel].used) { + drawVertices(gl, marker, view->screen.voltage[channel]); + } + } + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel, ++marker) { + if (scope->spectrum[channel].used) { + drawVertices(gl, marker, view->screen.spectrum[channel]); + } + } + gl->glDepthMask(GL_TRUE); } m_vaoMarker.release(); diff --git a/openhantek/src/glscope.h b/openhantek/src/glscope.h index 3f8f2ca..4d32e32 100644 --- a/openhantek/src/glscope.h +++ b/openhantek/src/glscope.h @@ -18,6 +18,7 @@ struct DsoSettingsView; struct DsoSettingsScope; +struct DsoSettingsScopeCursor; class PPresult; /// \brief OpenGL accelerated widget that displays the oscilloscope screen. @@ -40,7 +41,8 @@ class GlScope : public QOpenGLWidget { * @param data */ void showData(std::shared_ptr data); - void markerUpdated(); + void updateCursor(unsigned index = 0); + void cursorSelected(unsigned index) { selectedCursor = index; updateCursor(index); } protected: /// \brief Initializes the scope widget. @@ -70,11 +72,14 @@ class GlScope : public QOpenGLWidget { void drawGrid(); /// Draw vertical lines at marker positions void drawMarkers(); + void generateVertices(unsigned marker, const DsoSettingsScopeCursor &cursor); + void drawVertices(QOpenGLFunctions *gl, unsigned marker, QColor color); void drawVoltageChannelGraph(ChannelID channel, Graph &graph, int historyIndex); void drawSpectrumChannelGraph(ChannelID channel, Graph &graph, int historyIndex); + QPointF eventToPosition(QMouseEvent *event); signals: - void markerMoved(unsigned marker, double position); + void markerMoved(unsigned cursorIndex, unsigned marker); private: // User settings @@ -85,16 +90,20 @@ class GlScope : public QOpenGLWidget { // Marker const unsigned NO_MARKER = UINT_MAX; #pragma pack(push, 1) - struct Line { - QVector3D x; - QVector3D y; + struct Vertices { + QVector3D a, b, c, d; }; #pragma pack(pop) - std::vector vaMarker; + const unsigned VERTICES_ARRAY_SIZE = sizeof(Vertices) / sizeof(QVector3D); + std::vector vaMarker; unsigned selectedMarker = NO_MARKER; QOpenGLBuffer m_marker; QOpenGLVertexArrayObject m_vaoMarker; + // Cursors + std::vector cursorInfo; + unsigned selectedCursor = 0; + // Grid QOpenGLBuffer m_grid; QOpenGLVertexArrayObject m_vaoGrid[3]; diff --git a/openhantek/src/mainwindow.cpp b/openhantek/src/mainwindow.cpp index 3c277a4..411195b 100644 --- a/openhantek/src/mainwindow.cpp +++ b/openhantek/src/mainwindow.cpp @@ -39,6 +39,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo ui->actionManualCommand->setIcon(iconFont->icon(fa::edit)); ui->actionDigital_phosphor->setIcon(QIcon(":/images/digitalphosphor.svg")); ui->actionZoom->setIcon(iconFont->icon(fa::crop)); + ui->actionCursors->setIcon(iconFont->icon(fa::crosshairs)); // Window title setWindowIcon(QIcon(":openhantek.png")); @@ -160,6 +161,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo auto usedChanged = [this, dsoControl, spec](ChannelID channel, bool used) { if (channel >= (unsigned int)mSettings->scope.voltage.size()) return; +// if (!used) dsoWidget-> bool mathUsed = mSettings->scope.anyUsed(spec->channels); // Normal channel, check if voltage/spectrum or math channel is used @@ -267,6 +269,18 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo }); ui->actionZoom->setChecked(mSettings->view.zoom); + connect(ui->actionCursors, &QAction::toggled, [this](bool enabled) { + mSettings->view.cursorsVisible = enabled; + + if (mSettings->view.cursorsVisible) + this->ui->actionCursors->setStatusTip(tr("Hide measurements")); + else + this->ui->actionCursors->setStatusTip(tr("Show measurements")); + + this->dsoWidget->updateCursorGrid(enabled); + }); + ui->actionCursors->setChecked(mSettings->view.cursorsVisible); + connect(ui->actionAbout, &QAction::triggered, [this]() { QMessageBox::about( this, tr("About OpenHantek %1").arg(VERSION), diff --git a/openhantek/src/mainwindow.ui b/openhantek/src/mainwindow.ui index e9eb479..ae00679 100644 --- a/openhantek/src/mainwindow.ui +++ b/openhantek/src/mainwindow.ui @@ -20,7 +20,7 @@ 0 0 800 - 25 + 28 @@ -39,6 +39,8 @@ + + @@ -84,6 +86,7 @@ + @@ -167,6 +170,14 @@ Manual command + + + true + + + Cursors + + diff --git a/openhantek/src/scopesettings.h b/openhantek/src/scopesettings.h index daa50ce..c46ef2b 100644 --- a/openhantek/src/scopesettings.h +++ b/openhantek/src/scopesettings.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include "hantekdso/controlspecification.h" #include "hantekdso/enums.h" @@ -12,12 +13,22 @@ #define MARKER_COUNT 2 ///< Number of markers #define MARKER_STEP (DIVS_TIME / 100.0) +/// \brief Holds the cursor parameters +struct DsoSettingsScopeCursor { + enum CursorShape { + NONE, + HORIZONTAL, + VERTICAL, + RECTANGULAR + } shape = NONE; + QPointF pos[MARKER_COUNT] = {{-1.0, -1.0}, {1.0, 1.0}}; ///< Position in div +}; + /// \brief Holds the settings for the horizontal axis. struct DsoSettingsScopeHorizontal { Dso::GraphFormat format = Dso::GraphFormat::TY; ///< Graph drawing mode of the scope double frequencybase = 1e3; ///< Frequencybase in Hz/div - double marker[MARKER_COUNT] = {-1.0, 1.0}; ///< Marker positions in div - bool marker_visible[MARKER_COUNT] = {true, true}; + DsoSettingsScopeCursor cursor; unsigned int recordLength = 0; ///< Sample count @@ -39,25 +50,27 @@ struct DsoSettingsScopeTrigger { unsigned swTriggerSampleSet = 11; ///< Software trigger, sample set }; +/// \brief Base for DsoSettingsScopeSpectrum and DsoSettingsScopeVoltage +struct DsoSettingsScopeChannel { + QString name; ///< Name of this channel + bool used = false; ///< true if the channel is turned on + DsoSettingsScopeCursor cursor; +}; + /// \brief Holds the settings for the spectrum analysis. -struct DsoSettingsScopeSpectrum { - ChannelID channel; - double magnitude = 20.0; ///< The vertical resolution in dB/div - QString name; ///< Name of this channel +struct DsoSettingsScopeSpectrum : public DsoSettingsScopeChannel { double offset = 0.0; ///< Vertical offset in divs - bool used = false; ///< true if the spectrum is turned on + double magnitude = 20.0; ///< The vertical resolution in dB/div }; /// \brief Holds the settings for the normal voltage graphs. /// TODO Use ControlSettingsVoltage -struct DsoSettingsScopeVoltage { +struct DsoSettingsScopeVoltage : public DsoSettingsScopeChannel { double offset = 0.0; ///< Vertical offset in divs double trigger = 0.0; ///< Trigger level in V unsigned gainStepIndex = 6; ///< The vertical resolution in V/div (default = 1.0) unsigned couplingOrMathIndex = 0; ///< Different index: coupling for real- and mode for math-channels - QString name; ///< Name of this channel bool inverted = false; ///< true if the channel is inverted (mirrored on cross-axis) - bool used = false; ///< true if this channel is enabled }; /// \brief Holds the settings for the oscilloscope. @@ -77,4 +90,11 @@ struct DsoSettingsScope { } // Channels, including math channels unsigned countChannels() const { return (unsigned)voltage.size(); } + + double getMarker(unsigned int marker) const { + return marker < MARKER_COUNT ? horizontal.cursor.pos[marker].x() : 0.0; + } + void setMarker(unsigned int marker, double value) { + if (marker < MARKER_COUNT) horizontal.cursor.pos[marker].setX(value); + } }; diff --git a/openhantek/src/settings.cpp b/openhantek/src/settings.cpp index b73eb41..d7f1d47 100644 --- a/openhantek/src/settings.cpp +++ b/openhantek/src/settings.cpp @@ -75,10 +75,10 @@ void DsoSettings::load() { if (store->contains("format")) scope.horizontal.format = (Dso::GraphFormat)store->value("format").toInt(); if (store->contains("frequencybase")) scope.horizontal.frequencybase = store->value("frequencybase").toDouble(); - for (int marker = 0; marker < 2; ++marker) { + for (int marker = 0; marker < MARKER_COUNT; ++marker) { QString name; name = QString("marker%1").arg(marker); - if (store->contains(name)) scope.horizontal.marker[marker] = store->value(name).toDouble(); + if (store->contains(name)) scope.setMarker(marker, store->value(name).toDouble()); } if (store->contains("timebase")) scope.horizontal.timebase = store->value("timebase").toDouble(); if (store->contains("recordLength")) scope.horizontal.recordLength = store->value("recordLength").toUInt(); @@ -100,6 +100,17 @@ void DsoSettings::load() { scope.spectrum[channel].magnitude = store->value("magnitude").toDouble(); if (store->contains("offset")) scope.spectrum[channel].offset = store->value("offset").toDouble(); if (store->contains("used")) scope.spectrum[channel].used = store->value("used").toBool(); + store->beginGroup("cursor"); + if (store->contains("shape")) scope.spectrum[channel].cursor.shape = + DsoSettingsScopeCursor::CursorShape(store->value("shape").toUInt()); + for (int marker = 0; marker < MARKER_COUNT; ++marker) { + QString name; + name = QString("x%1").arg(marker); + if (store->contains(name)) scope.spectrum[channel].cursor.pos[marker].setX(store->value(name).toDouble()); + name = QString("y%1").arg(marker); + if (store->contains(name)) scope.spectrum[channel].cursor.pos[marker].setY(store->value(name).toDouble()); + } + store->endGroup(); store->endGroup(); } // Vertical axis @@ -112,6 +123,17 @@ void DsoSettings::load() { if (store->contains("offset")) scope.voltage[channel].offset = store->value("offset").toDouble(); if (store->contains("trigger")) scope.voltage[channel].trigger = store->value("trigger").toDouble(); if (store->contains("used")) scope.voltage[channel].used = store->value("used").toBool(); + store->beginGroup("cursor"); + if (store->contains("shape")) scope.voltage[channel].cursor.shape = + DsoSettingsScopeCursor::CursorShape(store->value("shape").toUInt()); + for (int marker = 0; marker < MARKER_COUNT; ++marker) { + QString name; + name = QString("x%1").arg(marker); + if (store->contains(name)) scope.voltage[channel].cursor.pos[marker].setX(store->value(name).toDouble()); + name = QString("y%1").arg(marker); + if (store->contains(name)) scope.voltage[channel].cursor.pos[marker].setY(store->value(name).toDouble()); + } + store->endGroup(); store->endGroup(); } @@ -159,7 +181,10 @@ void DsoSettings::load() { if (store->contains("interpolation")) view.interpolation = (Dso::InterpolationMode)store->value("interpolation").toInt(); if (store->contains("screenColorImages")) view.screenColorImages = store->value("screenColorImages").toBool(); - if (store->contains("zoom")) view.zoom = (Dso::InterpolationMode)store->value("zoom").toBool(); + if (store->contains("zoom")) view.zoom = store->value("zoom").toBool(); + if (store->contains("cursorGridPosition")) + view.cursorGridPosition = (Qt::ToolBarArea)store->value("cursorGridPosition").toUInt(); + if (store->contains("cursorsVisible")) view.cursorsVisible = store->value("cursorsVisible").toBool(); store->endGroup(); store->beginGroup("window"); @@ -184,8 +209,8 @@ void DsoSettings::save() { store->beginGroup("horizontal"); store->setValue("format", scope.horizontal.format); store->setValue("frequencybase", scope.horizontal.frequencybase); - for (int marker = 0; marker < 2; ++marker) - store->setValue(QString("marker%1").arg(marker), scope.horizontal.marker[marker]); + for (int marker = 0; marker < MARKER_COUNT; ++marker) + store->setValue(QString("marker%1").arg(marker), scope.getMarker(marker)); store->setValue("timebase", scope.horizontal.timebase); store->setValue("recordLength", scope.horizontal.recordLength); store->setValue("samplerate", scope.horizontal.samplerate); @@ -205,6 +230,16 @@ void DsoSettings::save() { store->setValue("magnitude", scope.spectrum[channel].magnitude); store->setValue("offset", scope.spectrum[channel].offset); store->setValue("used", scope.spectrum[channel].used); + store->beginGroup("cursor"); + store->setValue("shape", scope.spectrum[channel].cursor.shape); + for (int marker = 0; marker < MARKER_COUNT; ++marker) { + QString name; + name = QString("x%1").arg(marker); + store->setValue(name, scope.spectrum[channel].cursor.pos[marker].x()); + name = QString("y%1").arg(marker); + store->setValue(name, scope.spectrum[channel].cursor.pos[marker].y()); + } + store->endGroup(); store->endGroup(); } // Vertical axis @@ -216,6 +251,16 @@ void DsoSettings::save() { store->setValue("offset", scope.voltage[channel].offset); store->setValue("trigger", scope.voltage[channel].trigger); store->setValue("used", scope.voltage[channel].used); + store->beginGroup("cursor"); + store->setValue("shape", scope.voltage[channel].cursor.shape); + for (int marker = 0; marker < MARKER_COUNT; ++marker) { + QString name; + name = QString("x%1").arg(marker); + store->setValue(name, scope.voltage[channel].cursor.pos[marker].x()); + name = QString("y%1").arg(marker); + store->setValue(name, scope.voltage[channel].cursor.pos[marker].y()); + } + store->endGroup(); store->endGroup(); } @@ -259,6 +304,8 @@ void DsoSettings::save() { store->setValue("interpolation", view.interpolation); store->setValue("screenColorImages", view.screenColorImages); store->setValue("zoom", view.zoom); + store->setValue("cursorGridPosition", view.cursorGridPosition); + store->setValue("cursorsVisible", view.cursorsVisible); store->endGroup(); store->beginGroup("window"); diff --git a/openhantek/src/viewsettings.h b/openhantek/src/viewsettings.h index 20e7179..c72d63a 100644 --- a/openhantek/src/viewsettings.h +++ b/openhantek/src/viewsettings.h @@ -42,6 +42,8 @@ struct DsoSettingsView { Dso::InterpolationMode interpolation = Dso::INTERPOLATION_LINEAR; ///< Interpolation mode for the graph bool screenColorImages = false; ///< true exports images with screen colors bool zoom = false; ///< true if the magnified scope is enabled + Qt::ToolBarArea cursorGridPosition = Qt::RightToolBarArea; + bool cursorsVisible = false; unsigned digitalPhosphorDraws() const { return digitalPhosphor ? digitalPhosphorDepth : 1; diff --git a/openhantek/src/widgets/datagrid.cpp b/openhantek/src/widgets/datagrid.cpp new file mode 100644 index 0000000..bb38f0a --- /dev/null +++ b/openhantek/src/widgets/datagrid.cpp @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include "datagrid.h" + +#include +#include +#include +#include + +DataGrid::DataGrid(QWidget *parent) : QGroupBox(parent) +{ + cursorsLayout = new QGridLayout(); + cursorsLayout->setSpacing(5); + cursorsSelectorGroup = new QButtonGroup(); + cursorsSelectorGroup->setExclusive(true); + + connect(cursorsSelectorGroup, + static_cast(&QButtonGroup::buttonPressed), [this] (int index) { + emit itemSelected(index); + }); + + setLayout(cursorsLayout); + setFixedWidth(180); +} + +DataGrid::CursorInfo::CursorInfo() { + selector = new QPushButton(); + selector->setCheckable(true); + shape = new QPushButton(); + deltaXLabel = new QLabel(); + deltaXLabel->setAlignment(Qt::AlignRight); + deltaYLabel = new QLabel(); + deltaYLabel->setAlignment(Qt::AlignRight); +} + +void DataGrid::CursorInfo::configure(const QString &text, const QColor &bgColor, const QColor &fgColor) { + palette.setColor(QPalette::Background, bgColor); + palette.setColor(QPalette::WindowText, fgColor); + + selector->setText(text); + selector->setStyleSheet(QString(R"( + QPushButton { + color: %2; + background-color: %1; + border: 1px solid %2; + } + QPushButton:checked { + color: %1; + background-color: %2; + } + QPushButton:disabled { + color: %3; + border: 1px dotted %2; + } + )").arg(bgColor.name(QColor::HexArgb)) + .arg(fgColor.name(QColor::HexArgb)) + .arg(fgColor.darker().name(QColor::HexArgb))); + + shape->setStyleSheet(QString(R"( + QPushButton { + color: %2; + background-color: %1; + border: none + } + )").arg(bgColor.name(QColor::HexArgb)) + .arg(fgColor.name(QColor::HexArgb))); + + deltaXLabel->setPalette(palette); + deltaYLabel->setPalette(palette); +} + +void DataGrid::setBackgroundColor(const QColor &bgColor) { + backgroundColor = bgColor; + for (auto it : items) { + it.configure(it.selector->text(), bgColor, it.palette.color(QPalette::WindowText)); + } +} + +void DataGrid::configureItem(unsigned index, const QColor &fgColor) { + if (index < items.size()) { + items[index].configure(items[index].selector->text(), backgroundColor, fgColor); + } +} + +unsigned DataGrid::addItem(const QString &text, const QColor &fgColor) { + unsigned index = items.size(); + items.resize(index + 1); + + CursorInfo& info = items.at(index); + info.configure(text, backgroundColor, fgColor); + cursorsSelectorGroup->addButton(info.selector, index); + + connect(info.shape, &QPushButton::clicked, [this, index] () { + emit itemUpdated(index); + }); + + cursorsLayout->addWidget(info.selector, 3 * index, 0); + cursorsLayout->addWidget(info.shape, 3 * index, 1); + cursorsLayout->addWidget(info.deltaXLabel, 3 * index + 1, 0); + cursorsLayout->addWidget(info.deltaYLabel, 3 * index + 1, 1); + cursorsLayout->setRowMinimumHeight(3 * index + 2, 10); + cursorsLayout->setRowStretch(3 * index, 0); + cursorsLayout->setRowStretch(3 * index + 3, 1); + + return index; +} + +void DataGrid::updateInfo(unsigned index, bool visible, const QString &strShape, const QString &strX, const QString &strY) { + if (index >= items.size()) return; + CursorInfo &info = items.at(index); + info.selector->setEnabled(visible); + if (visible) { + info.shape->setText(strShape); + info.deltaXLabel->setText(strX); + info.deltaYLabel->setText(strY); + } else { + info.shape->setText(QString()); + info.deltaXLabel->setText(QString()); + info.deltaYLabel->setText(QString()); + } +} + +void DataGrid::selectItem(unsigned index) { + if (index >= items.size()) return; + items[index].selector->setChecked(true); +} diff --git a/openhantek/src/widgets/datagrid.h b/openhantek/src/widgets/datagrid.h new file mode 100644 index 0000000..b5169ba --- /dev/null +++ b/openhantek/src/widgets/datagrid.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include +#include + +class QPushButton; +class QButtonGroup; +class QLabel; +class QGridLayout; + +class DataGrid : public QGroupBox +{ + Q_OBJECT +public: + explicit DataGrid(QWidget *parent = nullptr); + + struct CursorInfo { + QPalette palette; ///< The widget's palette + QPushButton *selector; ///< The name of the channel + QPushButton *shape; ///< The cursor shape + QLabel *deltaXLabel; ///< The horizontal distance between cursors + QLabel *deltaYLabel; ///< The vertical distance between cursors + + CursorInfo(); + void configure(const QString &text, const QColor &bgColor, const QColor &fgColor); + }; + + unsigned addItem(const QString &text, const QColor &fgColor); + void setBackgroundColor(const QColor &bgColor); + void configureItem(unsigned index, const QColor &fgColor); + void updateInfo(unsigned index, bool visible, const QString &strShape = QString(), + const QString &strX = QString(), const QString &strY = QString()); + +signals: + void itemSelected(unsigned index); + void itemUpdated(unsigned index); + +public slots: + void selectItem(unsigned index); + +private: + QColor backgroundColor; + QButtonGroup *cursorsSelectorGroup; + QGridLayout *cursorsLayout; + std::vector items; +}; diff --git a/openhantek/src/widgets/sispinbox.cpp b/openhantek/src/widgets/sispinbox.cpp index f02470d..e15ed38 100644 --- a/openhantek/src/widgets/sispinbox.cpp +++ b/openhantek/src/widgets/sispinbox.cpp @@ -160,18 +160,19 @@ void SiSpinBox::setMode(const int mode) { this->mode = mode; } /// \brief Generic initializations. void SiSpinBox::init() { - this->setMinimum(1e-12); - this->setMaximum(1e12); - this->setValue(1.0); - this->setDecimals(DBL_MAX_10_EXP + DBL_DIG); // Disable automatic rounding - this->unit = unit; - this->steps << 1.0 << 2.0 << 5.0 << 10.0; - - this->steppedTo = false; - this->stepId = 0; - this->mode = 0; - - connect(this, static_cast(&QDoubleSpinBox::valueChanged), this, &SiSpinBox::resetSteppedTo); + setMinimum(1e-12); + setMaximum(1e12); + setValue(1.0); + setDecimals(DBL_MAX_10_EXP + DBL_DIG); // Disable automatic rounding + setFocusPolicy(Qt::NoFocus); + steps << 1.0 << 2.0 << 5.0 << 10.0; + + steppedTo = false; + stepId = 0; + mode = 0; + + connect(this, static_cast(&QDoubleSpinBox::valueChanged), + this, &SiSpinBox::resetSteppedTo); } /// \brief Resets the ::steppedTo flag after the value has been changed. -- libgit2 0.21.4