diff --git a/openhantek/src/dsowidget.cpp b/openhantek/src/dsowidget.cpp index 1fb0af7..3521e3a 100644 --- a/openhantek/src/dsowidget.cpp +++ b/openhantek/src/dsowidget.cpp @@ -10,22 +10,24 @@ #include "dsowidget.h" -#include "exporter.h" -#include "glgenerator.h" +#include "post/graphgenerator.h" +#include "post/ppresult.h" + +#include "utils/dsoStrings.h" +#include "utils/printutils.h" + #include "glscope.h" #include "scopesettings.h" +#include "viewconstants.h" #include "viewsettings.h" -#include "utils/dsoStrings.h" -#include "utils/printutils.h" #include "widgets/levelslider.h" -#include "viewconstants.h" -#include "analyse/dataanalyzerresult.h" static int zoomScopeRow = 0; -DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso::ControlSpecification *spec, QWidget *parent, Qt::WindowFlags flags) - : QWidget(parent, flags), scope(scope), view(view), spec(spec), generator(new GlGenerator()), - mainScope(GlScope::createNormal(scope, view, generator)), zoomScope(GlScope::createZoomed(scope, view, generator)) { +DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso::ControlSpecification *spec, + QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags), scope(scope), view(view), spec(spec), mainScope(GlScope::createNormal(scope, view)), + zoomScope(GlScope::createZoomed(scope, view)) { // Palette for this widget QPalette palette; @@ -37,6 +39,7 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: connect(mainScope, &GlScope::markerMoved, [this] (int marker, double position) { double step = this->mainSliders.markerSlider->step(marker); + this->scope->horizontal.marker[marker] = this->mainSliders.markerSlider->setValue(marker, std::round(position / step) * step); }); @@ -62,6 +65,7 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: swTriggerStatus->setText(tr("TR")); swTriggerStatus->setAlignment(Qt::AlignCenter); swTriggerStatus->setAutoFillBackground(true); + swTriggerStatus->setVisible(false); settingsLayout = new QHBoxLayout(); settingsLayout->addWidget(swTriggerStatus); settingsLayout->addWidget(settingsTriggerLabel); @@ -259,14 +263,7 @@ void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) { } } -void DsoWidget::showNewData(std::unique_ptr data) { - if (!data) return; - this->data = std::move(data); - emit doShowNewData(); -} - -void DsoWidget::setExporterForNextFrame(std::unique_ptr exporter) -{ +void DsoWidget::setExporterForNextFrame(std::unique_ptr exporter) { this->exportNextFrame = std::move(exporter); } @@ -317,9 +314,12 @@ void DsoWidget::updateMarkerDetails() { markerFrequencybaseLabel->setText( valueToString(divs * scope->horizontal.frequencybase / DIVS_TIME, UNIT_HERTZ, 4) + tr("/div")); } - markerInfoLabel->setText(infoLabelPrefix.append(": %1 %2") - .arg(valueToString(0.5 + scope->horizontal.marker[0] / DIVS_TIME - scope->trigger.position, UNIT_SECONDS, 4)) - .arg(valueToString(0.5 + scope->horizontal.marker[1] / DIVS_TIME - scope->trigger.position, UNIT_SECONDS, 4))); + markerInfoLabel->setText( + infoLabelPrefix.append(": %1 %2") + .arg( + valueToString(0.5 + scope->horizontal.marker[0] / DIVS_TIME - scope->trigger.position, UNIT_SECONDS, 4)) + .arg(valueToString(0.5 + scope->horizontal.marker[1] / DIVS_TIME - scope->trigger.position, UNIT_SECONDS, + 4))); markerTimeLabel->setText(valueToString(time, UNIT_SECONDS, 4)); markerFrequencyLabel->setText(valueToString(1.0 / time, UNIT_HERTZ, 4)); @@ -330,8 +330,8 @@ void DsoWidget::updateSpectrumDetails(ChannelID channel) { setMeasurementVisible(channel); if (scope->spectrum[channel].used) - measurementMagnitudeLabel[channel]->setText( - valueToString(scope->spectrum[channel].magnitude, UNIT_DECIBEL, 3) + tr("/div")); + measurementMagnitudeLabel[channel]->setText(valueToString(scope->spectrum[channel].magnitude, UNIT_DECIBEL, 3) + + tr("/div")); else measurementMagnitudeLabel[channel]->setText(QString()); } @@ -346,8 +346,7 @@ void DsoWidget::updateTriggerDetails() { QString pretriggerString = tr("%L1%").arg((int)(scope->trigger.position * 100 + 0.5)); settingsTriggerLabel->setText(tr("%1 %2 %3 %4") .arg(scope->voltage[scope->trigger.source].name, - Dso::slopeString(scope->trigger.slope), levelString, - pretriggerString)); + Dso::slopeString(scope->trigger.slope), levelString, pretriggerString)); /// \todo This won't work for special trigger sources } @@ -359,8 +358,7 @@ void DsoWidget::updateVoltageDetails(ChannelID channel) { setMeasurementVisible(channel); if (scope->voltage[channel].used) - measurementGainLabel[channel]->setText(valueToString(scope->gain(channel), UNIT_VOLTS, 3) + - tr("/div")); + measurementGainLabel[channel]->setText(valueToString(scope->gain(channel), UNIT_VOLTS, 3) + tr("/div")); else measurementGainLabel[channel]->setText(QString()); } @@ -435,13 +433,12 @@ void DsoWidget::updateTriggerSource() { void DsoWidget::updateVoltageCoupling(ChannelID channel) { if (channel >= (unsigned int)scope->voltage.size()) return; - measurementMiscLabel[channel]->setText(Dso::couplingString(scope->coupling(channel,spec))); + measurementMiscLabel[channel]->setText(Dso::couplingString(scope->coupling(channel, spec))); } /// \brief Handles modeChanged signal from the voltage dock. void DsoWidget::updateMathMode() { - measurementMiscLabel[spec->channels]->setText( - Dso::mathModeString(scope->voltage[spec->channels].math)); + measurementMiscLabel[spec->channels]->setText(Dso::mathModeString(scope->voltage[spec->channels].math)); } /// \brief Handles gainChanged signal from the voltage dock. @@ -505,26 +502,32 @@ void DsoWidget::updateZoom(bool enabled) { } /// \brief Prints analyzed data. -void DsoWidget::doShowNewData() { +void DsoWidget::showNew(std::shared_ptr data) { if (exportNextFrame) { exportNextFrame->exportSamples(data.get()); exportNextFrame.reset(nullptr); } - bool triggered = generator->generateGraphs(data.get(), view->digitalPhosphorDraws(), scope, spec); - - QPalette triggerLabelPalette = palette(); - triggerLabelPalette.setColor(QPalette::WindowText, Qt::black); - triggerLabelPalette.setColor(QPalette::Background, triggered ? Qt::green : Qt::red); - swTriggerStatus->setPalette(triggerLabelPalette); + mainScope->showData(data.get()); + mainScope->update(); + zoomScope->showData(data.get()); + zoomScope->update(); + + if (spec->isSoftwareTriggerDevice) { + QPalette triggerLabelPalette = palette(); + triggerLabelPalette.setColor(QPalette::WindowText, Qt::black); + triggerLabelPalette.setColor(QPalette::Background, data->softwareTriggerTriggered ? Qt::green : Qt::red); + swTriggerStatus->setPalette(triggerLabelPalette); + swTriggerStatus->setVisible(true); + } - updateRecordLength(data.get()->getMaxSamples()); + updateRecordLength(data.get()->sampleCount()); for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { if (scope->voltage[channel].used && data.get()->data(channel)) { // Amplitude string representation (4 significant digits) measurementAmplitudeLabel[channel]->setText( - valueToString(data.get()->data(channel)->amplitude, UNIT_VOLTS, 4)); + valueToString(data.get()->data(channel)->computeAmplitude(), UNIT_VOLTS, 4)); // Frequency string representation (5 significant digits) measurementFrequencyLabel[channel]->setText( valueToString(data.get()->data(channel)->frequency, UNIT_HERTZ, 5)); diff --git a/openhantek/src/dsowidget.h b/openhantek/src/dsowidget.h index b0b80e3..982bd22 100644 --- a/openhantek/src/dsowidget.h +++ b/openhantek/src/dsowidget.h @@ -8,16 +8,15 @@ #include #include -#include "exporter.h" +#include "exporting/exporter.h" #include "glscope.h" #include "levelslider.h" #include "hantekdso/controlspecification.h" -class DataAnalyzer; +class SpectrumGenerator; struct DsoSettingsScope; struct DsoSettingsView; -/// \class DsoWidget /// \brief The widget for the oszilloscope-screen /// This widget contains the scopes and all level sliders. class DsoWidget : public QWidget { @@ -37,9 +36,11 @@ class DsoWidget : public QWidget { /// \param parent The parent widget. /// \param flags Flags for the window manager. DsoWidget(DsoSettingsScope* scope, DsoSettingsView* view, const Dso::ControlSpecification* spec, QWidget *parent = 0, Qt::WindowFlags flags = 0); - void showNewData(std::unique_ptr data); void setExporterForNextFrame(std::unique_ptr exporter); + // Data arrived + void showNew(std::shared_ptr data); + protected: void setupSliders(Sliders &sliders); void adaptTriggerLevelSlider(DsoWidget::Sliders &sliders, ChannelID channel); @@ -83,11 +84,10 @@ class DsoWidget : public QWidget { DsoSettingsView* view; const Dso::ControlSpecification* spec; - GlGenerator *generator; ///< The generator for the OpenGL vertex arrays GlScope *mainScope; ///< The main scope screen GlScope *zoomScope; ///< The optional magnified scope screen std::unique_ptr exportNextFrame; - std::unique_ptr data; + std::shared_ptr data; public slots: // Horizontal axis @@ -117,9 +117,6 @@ class DsoWidget : public QWidget { // Scope control void updateZoom(bool enabled); - // Data analyzer - void doShowNewData(); - private slots: // Sliders void updateOffset(ChannelID channel, double value); diff --git a/openhantek/src/glgenerator.cpp b/openhantek/src/glgenerator.cpp deleted file mode 100644 index feaa926..0000000 --- a/openhantek/src/glgenerator.cpp +++ /dev/null @@ -1,286 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -#include -#include - -#include "glgenerator.h" -#include "utils/printutils.h" -#include "settings.h" -#include "analyse/dataanalyzerresult.h" -#include "viewconstants.h" -#include "hantekdso/softwaretrigger.h" -#include "hantekdso/controlspecification.h" - -static const SampleValues& useSamplesOf(Dso::ChannelMode mode, ChannelID channel, const DataAnalyzerResult *result, const DsoSettingsScope *scope) { - static SampleValues emptyDefault; - if (mode == Dso::ChannelMode::Voltage) { - if (!scope->voltage[channel].used || !result->data(channel)) return emptyDefault; - return result->data(channel)->voltage; - } else { - if (!scope->spectrum[channel].used || !result->data(channel)) return emptyDefault; - return result->data(channel)->spectrum; - } -} - -GlGenerator::GlGenerator() { - // Grid - const int DIVS_TIME_S2 = (int)DIVS_TIME - 2; - const int DIVS_VOLTAGE_S2 = (int)DIVS_VOLTAGE - 2; - const int vaGrid0Size = (int) ((DIVS_TIME * DIVS_SUB - 2) * DIVS_VOLTAGE_S2 + - (DIVS_VOLTAGE * DIVS_SUB - 2) * DIVS_TIME_S2 - - (DIVS_TIME_S2 * DIVS_VOLTAGE_S2)) * 2; - - vaGrid[0].resize(vaGrid0Size); - std::vector::iterator glIterator = vaGrid[0].begin(); - // Draw vertical lines - for (int div = 1; div < DIVS_TIME / 2; ++div) { - for (int dot = 1; dot < DIVS_VOLTAGE / 2 * DIVS_SUB; ++dot) { - float dotPosition = (float)dot / DIVS_SUB; - *(glIterator++) = -div; - *(glIterator++) = -dotPosition; - *(glIterator++) = -div; - *(glIterator++) = dotPosition; - *(glIterator++) = div; - *(glIterator++) = -dotPosition; - *(glIterator++) = div; - *(glIterator++) = dotPosition; - } - } - // Draw horizontal lines - for (int div = 1; div < DIVS_VOLTAGE / 2; ++div) { - for (int dot = 1; dot < DIVS_TIME / 2 * DIVS_SUB; ++dot) { - if (dot % DIVS_SUB == 0) continue; // Already done by vertical lines - float dotPosition = (float)dot / DIVS_SUB; - *(glIterator++) = -dotPosition; - *(glIterator++) = -div; - *(glIterator++) = dotPosition; - *(glIterator++) = -div; - *(glIterator++) = -dotPosition; - *(glIterator++) = div; - *(glIterator++) = dotPosition; - *(glIterator++) = div; - } - } - - // Axes - vaGrid[1].resize((int)(2 + (DIVS_TIME * DIVS_SUB - 2) + (DIVS_VOLTAGE * DIVS_SUB - 2)) * 4); - glIterator = vaGrid[1].begin(); - // Horizontal axis - *(glIterator++) = -DIVS_TIME / 2; - *(glIterator++) = 0; - *(glIterator++) = DIVS_TIME / 2; - *(glIterator++) = 0; - // Vertical axis - *(glIterator++) = 0; - *(glIterator++) = -DIVS_VOLTAGE / 2; - *(glIterator++) = 0; - *(glIterator++) = DIVS_VOLTAGE / 2; - // Subdiv lines on horizontal axis - for (int line = 1; line < DIVS_TIME / 2 * DIVS_SUB; ++line) { - float linePosition = (float)line / DIVS_SUB; - *(glIterator++) = linePosition; - *(glIterator++) = -0.05f; - *(glIterator++) = linePosition; - *(glIterator++) = 0.05f; - *(glIterator++) = -linePosition; - *(glIterator++) = -0.05f; - *(glIterator++) = -linePosition; - *(glIterator++) = 0.05f; - } - // Subdiv lines on vertical axis - for (int line = 1; line < DIVS_VOLTAGE / 2 * DIVS_SUB; ++line) { - float linePosition = (float)line / DIVS_SUB; - *(glIterator++) = -0.05f; - *(glIterator++) = linePosition; - *(glIterator++) = 0.05f; - *(glIterator++) = linePosition; - *(glIterator++) = -0.05f; - *(glIterator++) = -linePosition; - *(glIterator++) = 0.05f; - *(glIterator++) = -linePosition; - } - - // Border - vaGrid[2].resize(4 * 2); - glIterator = vaGrid[2].begin(); - *(glIterator++) = -DIVS_TIME / 2; - *(glIterator++) = -DIVS_VOLTAGE / 2; - *(glIterator++) = DIVS_TIME / 2; - *(glIterator++) = -DIVS_VOLTAGE / 2; - *(glIterator++) = DIVS_TIME / 2; - *(glIterator++) = DIVS_VOLTAGE / 2; - *(glIterator++) = -DIVS_TIME / 2; - *(glIterator++) = DIVS_VOLTAGE / 2; -} - -const std::vector &GlGenerator::channel(Dso::ChannelMode mode, ChannelID channel, unsigned index) const { - return vaChannel[(unsigned)mode][channel][index]; -} - -const std::vector &GlGenerator::grid(int a) const { return vaGrid[a]; } - -bool GlGenerator::isReady() const { return ready; } - -bool GlGenerator::generateGraphs(const DataAnalyzerResult *result, unsigned digitalPhosphorDepth, - const DsoSettingsScope *scope, const Dso::ControlSpecification* spec) { - - // Handle all digital phosphor related list manipulations - for (Dso::ChannelMode mode: Dso::ChannelModeEnum) { - DrawLinesWithHistoryPerChannel& d = vaChannel[(unsigned)mode]; - // Resize to the number of channels - d.resize(scope->voltage.size()); - - for (ChannelID channel = 0; channel < vaChannel[(unsigned)mode].size(); ++channel) { - DrawLinesWithHistory& drawLinesHistory = d[channel]; - // Move the last list element to the front - if (digitalPhosphorDepth > 1 && drawLinesHistory.size()) - drawLinesHistory.push_front(drawLinesHistory.back()); - - // Resize lists for vector array to fit the digital phosphor depth - drawLinesHistory.resize(digitalPhosphorDepth); - } - } - - ready = true; - - unsigned preTrigSamples=0; - unsigned postTrigSamples=0; - unsigned swTriggerStart=0; - bool triggered = false; - - switch (scope->horizontal.format) { - case Dso::GraphFormat::TY: - // check trigger point for software trigger - if (spec->isSoftwareTriggerDevice && scope->trigger.source < spec->channels) - std::tie(preTrigSamples, postTrigSamples, swTriggerStart) = SoftwareTrigger::compute(result, scope); - triggered = postTrigSamples > preTrigSamples; - - // Add graphs for channels - for (Dso::ChannelMode mode: Dso::ChannelModeEnum) { - DrawLinesWithHistoryPerChannel& dPerChannel = vaChannel[(unsigned)mode]; - for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { - DrawLinesWithHistory& withHistory = dPerChannel[channel]; - const SampleValues& samples = useSamplesOf(mode, channel, result, scope); - - // Check if this channel is used and available at the data analyzer - if (samples.sample.empty()) { - // Delete all vector arrays - for (unsigned index = 0; index < digitalPhosphorDepth; ++index) - withHistory[index].clear(); - continue; - } - // Check if the sample count has changed - size_t sampleCount = samples.sample.size(); - if (sampleCount>500000) { - qWarning() << "Sample count too high!"; - throw new std::runtime_error("Sample count too high!"); - } - if (mode == Dso::ChannelMode::Voltage) - sampleCount -= (swTriggerStart - preTrigSamples); - size_t neededSize = sampleCount * 2; - -#if 0 - for(unsigned int index = 0; index < digitalPhosphorDepth; ++index) { - if(vaChannel[mode][channel][index].size() != neededSize) - vaChannel[mode][channel][index].clear(); // Something was changed, drop old traces - } -#endif - - // Set size directly to avoid reallocations - withHistory.front().resize(neededSize); - - // Iterator to data for direct access - DrawLines::iterator glIterator = withHistory.front().begin(); - - // What's the horizontal distance between sampling points? - float horizontalFactor; - if (mode == Dso::ChannelMode::Voltage) - horizontalFactor = (float)(samples.interval / scope->horizontal.timebase); - else - horizontalFactor = (float)(samples.interval / scope->horizontal.frequencybase); - - // Fill vector array - if (mode == Dso::ChannelMode::Voltage) { - std::vector::const_iterator dataIterator = samples.sample.begin(); - const float gain = (float) scope->gain(channel); - const float offset = (float) scope->voltage[channel].offset; - const float invert = scope->voltage[channel].inverted ? -1.0f : 1.0f; - - std::advance(dataIterator, swTriggerStart - preTrigSamples); - - for (unsigned int position = 0; position < sampleCount; ++position) { - *(glIterator++) = position * horizontalFactor - DIVS_TIME / 2; - *(glIterator++) = (float)*(dataIterator++) / gain * invert + offset; - } - } else { - std::vector::const_iterator dataIterator = samples.sample.begin(); - const float magnitude = (float)scope->spectrum[channel].magnitude; - const float offset = (float)scope->spectrum[channel].offset; - - for (unsigned int position = 0; position < sampleCount; ++position) { - *(glIterator++) = position * horizontalFactor - DIVS_TIME / 2; - *(glIterator++) = (float)*(dataIterator++) / magnitude + offset; - } - } - } - } - break; - - case Dso::GraphFormat::XY: - triggered = true; - for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { - // For even channel numbers check if this channel is used and this and the - // following channel are available at the data analyzer - if (channel % 2 == 0 && channel + 1 < scope->voltage.size() && scope->voltage[channel].used && - result->data(channel) && !result->data(channel)->voltage.sample.empty() && result->data(channel + 1) && - !result->data(channel + 1)->voltage.sample.empty()) { - // Check if the sample count has changed - const size_t sampleCount = std::min(result->data(channel)->voltage.sample.size(), - result->data(channel + 1)->voltage.sample.size()); - const size_t neededSize = sampleCount * 2; - DrawLinesWithHistory& withHistory = vaChannel[(unsigned)Dso::ChannelMode::Voltage][(size_t)channel]; - for (unsigned index = 0; index < digitalPhosphorDepth; ++index) { - if (withHistory[index].size() != neededSize) - withHistory[index].clear(); // Something was changed, drop old traces - } - - // Set size directly to avoid reallocations - DrawLines& drawLines = withHistory.front(); - drawLines.resize(neededSize); - - // Iterator to data for direct access - std::vector::iterator glIterator = drawLines.begin(); - - // Fill vector array - unsigned int xChannel = channel; - unsigned int yChannel = channel + 1; - std::vector::const_iterator xIterator = result->data(xChannel)->voltage.sample.begin(); - std::vector::const_iterator yIterator = result->data(yChannel)->voltage.sample.begin(); - const double xGain = scope->gain(xChannel); - const double yGain = scope->gain(yChannel); - const double xOffset = scope->voltage[xChannel].offset; - const double yOffset = scope->voltage[yChannel].offset; - const double xInvert = scope->voltage[xChannel].inverted ? -1.0 : 1.0; - const double yInvert = scope->voltage[yChannel].inverted ? -1.0 : 1.0; - - for (unsigned int position = 0; position < sampleCount; ++position) { - *(glIterator++) = (GLfloat)( *(xIterator++) / xGain * xInvert + xOffset); - *(glIterator++) = (GLfloat)( *(yIterator++) / yGain * yInvert + yOffset); - } - } else { - // Delete all vector arrays - for (unsigned index = 0; index < digitalPhosphorDepth; ++index) - vaChannel[(unsigned)Dso::ChannelMode::Voltage][channel][index].clear(); - } - - // Delete all spectrum graphs - for (unsigned index = 0; index < digitalPhosphorDepth; ++index) - vaChannel[(unsigned)Dso::ChannelMode::Spectrum][channel][index].clear(); - } - break; - } - - emit graphsGenerated(); - - return triggered; -} diff --git a/openhantek/src/glgenerator.h b/openhantek/src/glgenerator.h deleted file mode 100644 index 20fd022..0000000 --- a/openhantek/src/glgenerator.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -#pragma once - -#include - -#include -#include - -#include "hantekdso/enums.h" -#include "hantekprotocol/definitions.h" - -struct DsoSettingsScope; -class DataAnalyzerResult; -namespace Dso { -struct ControlSpecification; -} - -//////////////////////////////////////////////////////////////////////////////// -/// \class GlGenerator -/// \brief Generates the vertex arrays for the GlScope classes. -class GlGenerator : public QObject { - Q_OBJECT - - public: - /// \brief Initializes the scope widget. - /// \param settings The target settings object. - /// \param parent The parent widget. - - GlGenerator(); - bool generateGraphs(const DataAnalyzerResult *result, unsigned digitalPhosphorDepth, const DsoSettingsScope *scope, - const Dso::ControlSpecification *spec); - const std::vector &channel(Dso::ChannelMode mode, ChannelID channel, unsigned index) const; - - const std::vector &grid(int a) const; - bool isReady() const; - - private: - typedef std::vector DrawLines; - typedef std::deque DrawLinesWithHistory; - typedef std::vector DrawLinesWithHistoryPerChannel; - DrawLinesWithHistoryPerChannel vaChannel[Dso::ChannelModes]; - std::vector vaGrid[3]; - bool ready = false; - signals: - void graphsGenerated(); ///< The graphs are ready to be drawn -}; diff --git a/openhantek/src/glscope.cpp b/openhantek/src/glscope.cpp index 24dacf2..2134372 100644 --- a/openhantek/src/glscope.cpp +++ b/openhantek/src/glscope.cpp @@ -1,117 +1,46 @@ // SPDX-License-Identifier: GPL-2.0+ #include +#include #include +#include +#include #include +#include +#include #include -#include -#include #include "glscope.h" -#include "glgenerator.h" +#include "post/graphgenerator.h" +#include "post/ppresult.h" #include "scopesettings.h" -#include "viewsettings.h" #include "viewconstants.h" +#include "viewsettings.h" -GlScope *GlScope::createNormal(DsoSettingsScope *scope, DsoSettingsView *view, const GlGenerator *generator, QWidget *parent) -{ - GlScope* s = new GlScope(scope, view, generator, parent); +// typedef QOpenGLFunctions_ES2 OPENGL_VER; +typedef QOpenGLFunctions_3_3_Core OPENGL_VER; + +GlScope *GlScope::createNormal(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent) { + GlScope *s = new GlScope(scope, view, parent); s->zoomed = false; return s; } -GlScope *GlScope::createZoomed(DsoSettingsScope *scope, DsoSettingsView *view, const GlGenerator *generator, QWidget *parent) -{ - GlScope* s = new GlScope(scope, view, generator, parent); +GlScope *GlScope::createZoomed(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent) { + GlScope *s = new GlScope(scope, view, parent); s->zoomed = true; return s; } -GlScope::GlScope(DsoSettingsScope *scope, DsoSettingsView *view, const GlGenerator *generator, QWidget *parent) - : QOpenGLWidget(parent), scope(scope), view(view), generator(generator) { - connect(generator, &GlGenerator::graphsGenerated, [this]() { update(); }); -} - -void GlScope::initializeGL() { - // TODO: Migrate to OpenGL ES2 - // QOpenGLFunctions_ES2 *localFunctions = context()->versionFunctions(); - // QOpenGLFunctions_3_2_Core *localFunctions = context()->versionFunctions(); - QOpenGLFunctions_1_3 *localFunctions = context()->versionFunctions(); - - localFunctions->glDisable(GL_DEPTH_TEST); - localFunctions->glEnable(GL_BLEND); - localFunctions->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - localFunctions->glPointSize(1); - - QColor bg = view->screen.background; - localFunctions->glClearColor((GLfloat)bg.redF(), (GLfloat)bg.greenF(), (GLfloat)bg.blueF(), (GLfloat)bg.alphaF()); - - localFunctions->glShadeModel(GL_SMOOTH /*GL_FLAT*/); - localFunctions->glLineStipple(1, 0x3333); - - localFunctions->glEnableClientState(GL_VERTEX_ARRAY); -} - -void GlScope::paintGL() { - // TODO: Migrate to OpenGL ES2 - // QOpenGLFunctions_ES2 *localFunctions = context()->versionFunctions(); - // QOpenGLFunctions_3_2_Core *localFunctions = context()->versionFunctions(); - QOpenGLFunctions_1_3 *localFunctions = context()->versionFunctions(); - - // Clear OpenGL buffer and configure settings - localFunctions->glClear(GL_COLOR_BUFFER_BIT); - localFunctions->glLineWidth(1); - - if (generator->isReady()) { drawGraph(view->digitalPhosphorDraws()); } - - if (!this->zoomed) { - // Draw vertical lines at marker positions - localFunctions->glEnable(GL_LINE_STIPPLE); - QColor trColor = view->screen.markers; - localFunctions->glColor4f((GLfloat)trColor.redF(), (GLfloat)trColor.greenF(), (GLfloat)trColor.blueF(), (GLfloat)trColor.alphaF()); - - for (int marker = 0; marker < MARKER_COUNT; ++marker) { - if (!scope->horizontal.marker_visible[marker]) continue; - if (vaMarker[marker].size() != 4) { - vaMarker[marker].resize(2 * 2); - vaMarker[marker][1] = -DIVS_VOLTAGE; - vaMarker[marker][3] = DIVS_VOLTAGE; - } - - vaMarker[marker][0] = (GLfloat)scope->horizontal.marker[marker]; - vaMarker[marker][2] = (GLfloat)scope->horizontal.marker[marker]; - - localFunctions->glLineWidth((marker == selectedMarker) ? 3 : 1); - localFunctions->glVertexPointer(2, GL_FLOAT, 0, &vaMarker[marker].front()); - localFunctions->glDrawArrays(GL_LINES, 0, (GLsizei)vaMarker[marker].size() / 2); - } - - localFunctions->glDisable(GL_LINE_STIPPLE); - } - - // Draw grid - this->drawGrid(); +GlScope::GlScope(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent) + : QOpenGLWidget(parent), scope(scope), view(view) { + vaMarker.resize(MARKER_COUNT); } -void GlScope::resizeGL(int width, int height) { - glViewport(0, 0, (GLint)width, (GLint)height); - - glMatrixMode(GL_PROJECTION); - - // Set axes to div-scale and apply correction for exact pixelization - glLoadIdentity(); - GLdouble pixelizationWidthCorrection = (width > 0) ? (GLdouble)width / (width - 1) : 1; - GLdouble pixelizationHeightCorrection = (height > 0) ? (GLdouble)height / (height - 1) : 1; - glOrtho(-(DIVS_TIME / 2.0) * pixelizationWidthCorrection, (DIVS_TIME / 2.0) * pixelizationWidthCorrection, - -(DIVS_VOLTAGE / 2.0) * pixelizationHeightCorrection, (DIVS_VOLTAGE / 2.0) * pixelizationHeightCorrection, -1.0, - 1.0); - - glMatrixMode(GL_MODELVIEW); -} +GlScope::~GlScope() {} void GlScope::mousePressEvent(QMouseEvent *event) { if (!zoomed && event->button() == Qt::LeftButton) { @@ -126,9 +55,7 @@ void GlScope::mousePressEvent(QMouseEvent *event) { selectedMarker = marker; } } - if (selectedMarker != NO_MARKER) { - emit markerMoved(selectedMarker, position); - } + if (selectedMarker != NO_MARKER) { emit markerMoved(selectedMarker, position); } } event->accept(); } @@ -139,12 +66,11 @@ void GlScope::mouseMoveEvent(QMouseEvent *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 (int marker = 0; marker < MARKER_COUNT; ++marker) { + for (unsigned marker = 0; marker < MARKER_COUNT; ++marker) { emit markerMoved(marker, position); selectedMarker = marker; } - } - else if (selectedMarker < MARKER_COUNT) { + } else if (selectedMarker < MARKER_COUNT) { emit markerMoved(selectedMarker, position); } } @@ -154,106 +80,339 @@ void GlScope::mouseMoveEvent(QMouseEvent *event) { void GlScope::mouseReleaseEvent(QMouseEvent *event) { if (!zoomed && event->button() == Qt::LeftButton) { double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width(); - if (selectedMarker != NO_MARKER && selectedMarker < MARKER_COUNT) { - emit markerMoved(selectedMarker, position); - } + if (selectedMarker < MARKER_COUNT) { emit markerMoved(selectedMarker, position); } selectedMarker = NO_MARKER; } event->accept(); } -void GlScope::drawGrid() { - glDisable(GL_POINT_SMOOTH); - glDisable(GL_LINE_SMOOTH); +void GlScope::initializeGL() { + auto *gl = context()->versionFunctions(); + + m_program = std::unique_ptr(new QOpenGLShaderProgram(context())); + + const char *vshaderES = R"( + #version 330 + attribute highp vec3 vertex; + uniform mat4 matrix; + void main() + { + gl_Position = matrix * vec4(vertex, 1.0); + gl_PointSize = 1.0; + } + )"; + + const char *vshaderCore = R"( + #version 330 + in highp vec3 vertex; + uniform mat4 matrix; + void main() + { + gl_Position = matrix * vec4(vertex, 1.0); + gl_PointSize = 1.0; + } + )"; + const char *fshaderCore = R"( + #version 330 + uniform highp vec4 colour; + void main() { gl_FragColor = colour; } + )"; + + qDebug() << "compile shaders"; + // Compile vertex shader + bool coreShaders = QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile; + if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, coreShaders ? vshaderCore : vshaderES) || + !m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fshaderCore)) { + qWarning() << "Failed to compile OpenGL shader programs.\n" << m_program->log(); + return; + } - QColor color; - // Grid - color = view->screen.grid; - glColor4f((GLfloat)color.redF(), (GLfloat)color.greenF(), (GLfloat)color.blueF(), (GLfloat)color.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &generator->grid(0).front()); - glDrawArrays(GL_POINTS, 0, (GLsizei) generator->grid(0).size() / 2); - // Axes - color = view->screen.axes; - glColor4f((GLfloat)color.redF(), (GLfloat)color.greenF(), (GLfloat)color.blueF(), (GLfloat)color.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &generator->grid(1).front()); - glDrawArrays(GL_LINES, 0, (GLsizei) generator->grid(1).size() / 2); - // Border - color = view->screen.border; - glColor4f((GLfloat)color.redF(), (GLfloat)color.greenF(), (GLfloat)color.blueF(), (GLfloat)color.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &generator->grid(2).front()); - glDrawArrays(GL_LINE_LOOP, 0, (GLsizei) generator->grid(2).size() / 2); + // Link shader pipeline + if (!m_program->link() || !m_program->bind()) { + qWarning() << "Failed to link/bind OpenGL shader programs"; + return; + } + + vertexLocation = m_program->attributeLocation("vertex"); + matrixLocation = m_program->uniformLocation("matrix"); + colorLocation = m_program->uniformLocation("colour"); + + if (vertexLocation == -1 || colorLocation == -1 || matrixLocation == -1) { + qWarning() << "Failed to locate shader variable"; + return; + } + + m_program->bind(); + + gl->glDisable(GL_DEPTH_TEST); + gl->glEnable(GL_BLEND); + // Enable depth buffer + gl->glEnable(GL_DEPTH_TEST); + + // Enable back face culling + gl->glEnable(GL_CULL_FACE); + gl->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + gl->glPointSize(1); + + QColor bg = view->screen.background; + gl->glClearColor((GLfloat)bg.redF(), (GLfloat)bg.greenF(), (GLfloat)bg.blueF(), (GLfloat)bg.alphaF()); + + generateGrid(); + + { + m_vaoMarker.create(); + QOpenGLVertexArrayObject::Binder b(&m_vaoMarker); + m_marker.create(); + m_marker.bind(); + m_marker.setUsagePattern(QOpenGLBuffer::StaticDraw); + m_marker.allocate(4 * sizeof(QVector3D)); + m_program->enableAttributeArray(vertexLocation); + m_program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 0); + } + + m_program->release(); } -void GlScope::drawGraphDepth(Dso::ChannelMode mode, ChannelID channel, unsigned index) { - if (generator->channel(mode, channel, index).empty()) return; - QColor trColor; - if (mode == Dso::ChannelMode::Voltage) - trColor = view->screen.voltage[channel].darker(fadingFactor[index]); - else - trColor = view->screen.spectrum[channel].darker(fadingFactor[index]); - glColor4f((GLfloat)trColor.redF(), (GLfloat)trColor.greenF(), (GLfloat)trColor.blueF(), (GLfloat)trColor.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &generator->channel(mode, channel, index).front()); - glDrawArrays((view->interpolation == Dso::INTERPOLATION_OFF) ? GL_POINTS : GL_LINE_STRIP, 0, - (GLsizei)generator->channel(mode, channel, index).size() / 2); +void GlScope::showData(PPresult *data) { + if (!m_program || !m_program->isLinked()) return; + makeCurrent(); + m_GraphHistory.resize(view->digitalPhosphorDraws()); + m_GraphHistory[currentGraphInHistory].writeData(data, m_program.get(), vertexLocation); + currentGraphInHistory = (currentGraphInHistory + 1) % m_GraphHistory.size(); + doneCurrent(); } -void GlScope::drawGraph(unsigned digitalPhosphorDepth) { +void GlScope::paintGL() { + if (!m_program->isLinked()) return; + + auto *gl = context()->versionFunctions(); + + // Clear OpenGL buffer and configure settings + // TODO Don't clear if view->digitalPhosphorDraws()>1 + gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + gl->glLineWidth(1); + + m_program->bind(); + if (view->antialiasing) { - glEnable(GL_POINT_SMOOTH); - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + gl->glEnable(GL_POINT_SMOOTH); + gl->glEnable(GL_LINE_SMOOTH); + gl->glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); } // Apply zoom settings via matrix transformation - if (this->zoomed) { - glPushMatrix(); - glScalef(DIVS_TIME / (GLfloat)fabs(scope->horizontal.marker[1] - scope->horizontal.marker[0]), 1.0f, - 1.0f); - glTranslatef((GLfloat)-(scope->horizontal.marker[0] + scope->horizontal.marker[1]) / 2, 0.0f, 0.0f); + 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_program->setUniformValue(matrixLocation, pmvMatrix * m); } - // Values we need for the fading of the digital phosphor - if (fadingFactor.size() != digitalPhosphorDepth) { - fadingFactor.resize(digitalPhosphorDepth); - fadingFactor[0] = 100; - double fadingRatio = pow(10.0, 2.0 / digitalPhosphorDepth); - for (size_t index = 1; index < (size_t)digitalPhosphorDepth; ++index) - fadingFactor[index] = int(fadingFactor[index - 1] * fadingRatio); + for (unsigned historyIndex = 0; historyIndex < m_GraphHistory.size(); ++historyIndex) { + unsigned graphID = (historyIndex + currentGraphInHistory) % m_GraphHistory.size(); + Graph &graph = m_GraphHistory[graphID]; + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { + drawSpectrumChannelGraph(channel, graph, (int)historyIndex); + drawVoltageChannelGraph(channel, graph, (int)historyIndex); + } } - switch (scope->horizontal.format) { - case Dso::GraphFormat::TY: - // Real and virtual channels - for (Dso::ChannelMode mode: Dso::ChannelModeEnum) { - for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { - if (!channelUsed(mode, channel)) continue; - - // Draw graph for all available depths - for (unsigned index = digitalPhosphorDepth; index > 0; index--) { - drawGraphDepth(mode, channel, index-1); - } - } + gl->glDisable(GL_POINT_SMOOTH); + gl->glDisable(GL_LINE_SMOOTH); + + if (zoomed) { m_program->setUniformValue(matrixLocation, pmvMatrix); } + + if (!this->zoomed) drawMarkers(); + + drawGrid(); + m_program->release(); +} + +void GlScope::resizeGL(int width, int height) { + auto *gl = context()->versionFunctions(); + gl->glViewport(0, 0, (GLint)width, (GLint)height); + + // Set axes to div-scale and apply correction for exact pixelization + float pixelizationWidthCorrection = (float)width / (width - 1); + float pixelizationHeightCorrection = (float)height / (height - 1); + + pmvMatrix.setToIdentity(); + pmvMatrix.ortho(-(DIVS_TIME / 2.0f) * pixelizationWidthCorrection, (DIVS_TIME / 2.0f) * pixelizationWidthCorrection, + -(DIVS_VOLTAGE / 2.0f) * pixelizationHeightCorrection, + (DIVS_VOLTAGE / 2.0f) * pixelizationHeightCorrection, -1.0f, 1.0f); + + m_program->bind(); + m_program->setUniformValue(matrixLocation, pmvMatrix); + m_program->release(); +} + +void GlScope::drawGrid() { + auto *gl = context()->versionFunctions(); + gl->glDisable(GL_POINT_SMOOTH); + gl->glDisable(GL_LINE_SMOOTH); + gl->glLineWidth(1); + + // Grid + m_vaoGrid[0].bind(); + m_program->setUniformValue(colorLocation, view->screen.grid); + gl->glDrawArrays(GL_POINTS, 0, gridDrawCounts[0]); + m_vaoGrid[0].release(); + + // Axes + m_vaoGrid[1].bind(); + m_program->setUniformValue(colorLocation, view->screen.axes); + gl->glDrawArrays(GL_LINES, 0, gridDrawCounts[1]); + m_vaoGrid[1].release(); + + // Border + m_vaoGrid[2].bind(); + m_program->setUniformValue(colorLocation, view->screen.border); + gl->glDrawArrays(GL_LINE_LOOP, 0, gridDrawCounts[2]); + m_vaoGrid[2].release(); +} + +void GlScope::generateGrid() { + gridDrawCounts[0] = 0; + gridDrawCounts[1] = 0; + gridDrawCounts[2] = 0; + + m_grid.create(); + m_grid.bind(); + m_grid.setUsagePattern(QOpenGLBuffer::StaticDraw); + + std::vector vaGrid; + + { // Bind draw vertical lines + m_vaoGrid[0].create(); + QOpenGLVertexArrayObject::Binder b(&m_vaoGrid[0]); + m_grid.bind(); + m_program->enableAttributeArray(vertexLocation); + m_program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 0); + } + + // Draw vertical lines + for (int div = 1; div < DIVS_TIME / 2; ++div) { + for (int dot = 1; dot < DIVS_VOLTAGE / 2 * DIVS_SUB; ++dot) { + float dotPosition = (float)dot / DIVS_SUB; + gridDrawCounts[0] += 4; + vaGrid.push_back(QVector3D(-div, -dotPosition, 0)); + vaGrid.push_back(QVector3D(-div, dotPosition, 0)); + vaGrid.push_back(QVector3D(div, -dotPosition, 0)); + vaGrid.push_back(QVector3D(div, dotPosition, 0)); } - break; - case Dso::GraphFormat::XY: - const Dso::ChannelMode mode = Dso::ChannelMode::Voltage; - // Real and virtual channels - for (ChannelID channel = 0; channel < scope->voltage.size() - 1; channel += 2) { - if (!channelUsed(mode, channel)) continue; - for (unsigned index = digitalPhosphorDepth; index > 0; index--) { - drawGraphDepth(mode, channel, index-1); - } + } + // Draw horizontal lines + for (int div = 1; div < DIVS_VOLTAGE / 2; ++div) { + for (int dot = 1; dot < DIVS_TIME / 2 * DIVS_SUB; ++dot) { + if (dot % DIVS_SUB == 0) continue; // Already done by vertical lines + float dotPosition = (float)dot / DIVS_SUB; + gridDrawCounts[0] += 4; + vaGrid.push_back(QVector3D(-dotPosition, -div, 0)); + vaGrid.push_back(QVector3D(dotPosition, -div, 0)); + vaGrid.push_back(QVector3D(-dotPosition, div, 0)); + vaGrid.push_back(QVector3D(dotPosition, div, 0)); } - break; } - glDisable(GL_POINT_SMOOTH); - glDisable(GL_LINE_SMOOTH); + { // Bind draw axes + m_vaoGrid[1].create(); + QOpenGLVertexArrayObject::Binder b(&m_vaoGrid[1]); + m_grid.bind(); + m_program->enableAttributeArray(vertexLocation); + m_program->setAttributeBuffer(vertexLocation, GL_FLOAT, int(vaGrid.size() * sizeof(QVector3D)), 3); + } - if (zoomed) glPopMatrix(); + // Axes + // Horizontal axis + gridDrawCounts[1] += 4; + vaGrid.push_back(QVector3D(-DIVS_TIME / 2, 0, 0)); + vaGrid.push_back(QVector3D(DIVS_TIME / 2, 0, 0)); + // Vertical axis + vaGrid.push_back(QVector3D(0, -DIVS_VOLTAGE / 2, 0)); + vaGrid.push_back(QVector3D(0, DIVS_VOLTAGE / 2, 0)); + // Subdiv lines on horizontal axis + for (int line = 1; line < DIVS_TIME / 2 * DIVS_SUB; ++line) { + float linePosition = (float)line / DIVS_SUB; + gridDrawCounts[1] += 4; + vaGrid.push_back(QVector3D(linePosition, -0.05f, 0)); + vaGrid.push_back(QVector3D(linePosition, 0.05f, 0)); + vaGrid.push_back(QVector3D(-linePosition, -0.05f, 0)); + vaGrid.push_back(QVector3D(-linePosition, 0.05f, 0)); + } + // Subdiv lines on vertical axis + for (int line = 1; line < DIVS_VOLTAGE / 2 * DIVS_SUB; ++line) { + float linePosition = (float)line / DIVS_SUB; + gridDrawCounts[1] += 4; + vaGrid.push_back(QVector3D(-0.05f, linePosition, 0)); + vaGrid.push_back(QVector3D(0.05f, linePosition, 0)); + vaGrid.push_back(QVector3D(-0.05f, -linePosition, 0)); + vaGrid.push_back(QVector3D(0.05f, -linePosition, 0)); + } + + { + m_vaoGrid[2].create(); + QOpenGLVertexArrayObject::Binder b(&m_vaoGrid[2]); + m_grid.bind(); + m_program->enableAttributeArray(vertexLocation); + m_program->setAttributeBuffer(vertexLocation, GL_FLOAT, int(vaGrid.size() * sizeof(QVector3D)), 3); + } + + // Border + gridDrawCounts[2] += 4; + vaGrid.push_back(QVector3D(-DIVS_TIME / 2, -DIVS_VOLTAGE / 2, 0)); + vaGrid.push_back(QVector3D(DIVS_TIME / 2, -DIVS_VOLTAGE / 2, 0)); + vaGrid.push_back(QVector3D(DIVS_TIME / 2, DIVS_VOLTAGE / 2, 0)); + vaGrid.push_back(QVector3D(-DIVS_TIME / 2, DIVS_VOLTAGE / 2, 0)); + + m_grid.allocate(&vaGrid[0], int(vaGrid.size() * sizeof(QVector3D))); + m_grid.release(); } -bool GlScope::channelUsed(Dso::ChannelMode mode, ChannelID channel) { - return (mode == Dso::ChannelMode::Voltage) ? scope->voltage[channel].used - : scope->spectrum[channel].used; +void GlScope::drawMarkers() { + auto *gl = context()->versionFunctions(); + QColor trColor = view->screen.markers; + m_program->setUniformValue(colorLocation, trColor); + + m_vaoMarker.bind(); + + for (unsigned marker = 0; marker < MARKER_COUNT; ++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)}; + + m_marker.bind(); + auto ptr = m_marker.mapRange(0, sizeof(Line), QOpenGLBuffer::RangeInvalidateBuffer | QOpenGLBuffer::RangeWrite); + memcpy(ptr, &vaMarker[marker], sizeof(Line)); + m_marker.unmap(); + + gl->glLineWidth((marker == selectedMarker) ? 3 : 1); + gl->glDrawArrays(GL_LINES, 0, (GLsizei)2); + } + + m_vaoMarker.release(); +} + +void GlScope::drawVoltageChannelGraph(ChannelID channel, Graph &graph, int historyIndex) { + if (!scope->voltage[channel].used) return; + + m_program->setUniformValue(colorLocation, view->screen.voltage[channel].darker(100 + 10 * historyIndex)); + + Graph::VaoCount &v = graph.vaoVoltage[channel]; + + QOpenGLVertexArrayObject::Binder b(v.first); + const GLenum dMode = (view->interpolation == Dso::INTERPOLATION_OFF) ? GL_POINTS : GL_LINE_STRIP; + context()->versionFunctions()->glDrawArrays(dMode, 0, v.second); +} + +void GlScope::drawSpectrumChannelGraph(ChannelID channel, Graph &graph, int historyIndex) { + if (!scope->spectrum[channel].used) return; + + m_program->setUniformValue(colorLocation, view->screen.spectrum[channel].darker(100 + 10 * historyIndex)); + Graph::VaoCount &v = graph.vaoSpectrum[channel]; + + QOpenGLVertexArrayObject::Binder b(v.first); + const GLenum dMode = (view->interpolation == Dso::INTERPOLATION_OFF) ? GL_POINTS : GL_LINE_STRIP; + context()->versionFunctions()->glDrawArrays(dMode, 0, v.second); } diff --git a/openhantek/src/glscope.h b/openhantek/src/glscope.h index cf6a80e..eaa6efe 100644 --- a/openhantek/src/glscope.h +++ b/openhantek/src/glscope.h @@ -2,36 +2,50 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include +#include +#include #include + +#include "glscopegraph.h" #include "hantekdso/enums.h" -#include "hantekprotocol/definitions.h" +#include "hantekprotocol/types.h" -class GlGenerator; -struct DsoSettingsScope; struct DsoSettingsView; +struct DsoSettingsScope; +class PPresult; /// \brief OpenGL accelerated widget that displays the oscilloscope screen. class GlScope : public QOpenGLWidget { Q_OBJECT public: - static GlScope* createNormal(DsoSettingsScope *scope, DsoSettingsView *view, const GlGenerator *generator, QWidget *parent = 0); - static GlScope* createZoomed(DsoSettingsScope *scope, DsoSettingsView *view, const GlGenerator *generator, QWidget *parent = 0); + static GlScope *createNormal(DsoSettingsScope *scope, DsoSettingsView *view, + QWidget *parent = 0); + static GlScope *createZoomed(DsoSettingsScope *scope, DsoSettingsView *view, + QWidget *parent = 0); + + /** + * Show new post processed data + * @param data + */ + void showData(PPresult* data); protected: /// \brief Initializes the scope widget. /// \param settings The settings that should be used. /// \param parent The parent widget. - GlScope(DsoSettingsScope *scope, DsoSettingsView *view, const GlGenerator *generator, QWidget *parent = 0); + GlScope(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent = 0); + virtual ~GlScope(); /// \brief Initializes OpenGL output. void initializeGL() override; - /// \brief Draw the graphs and the grid. + /// \brief Draw the graphs, marker and the grid. void paintGL() override; /// \brief Resize the widget. @@ -45,28 +59,47 @@ class GlScope : public QOpenGLWidget { /// \brief Draw the grid. void drawGrid(); + /// Draw vertical lines at marker positions + void drawMarkers(); - void drawGraphDepth(Dso::ChannelMode mode, ChannelID channel, unsigned index); - void drawGraph(unsigned digitalPhosphorDepth); - - /** - * @brief Return true if the given channel with the given mode is used - * @param mode The channel mode (spectrum/voltage) - * @param channel The channel - */ - bool channelUsed(Dso::ChannelMode mode, ChannelID channel); - + void drawVoltageChannelGraph(ChannelID channel, Graph &graph, int historyIndex); + void drawSpectrumChannelGraph(ChannelID channel, Graph &graph, int historyIndex); signals: - void markerMoved(int marker, double position); + void markerMoved(unsigned marker, double position); private: - const int NO_MARKER = -1; + // User settings DsoSettingsScope *scope; DsoSettingsView *view; - const GlGenerator *generator; - std::vector fadingFactor; - - std::vector vaMarker[2]; bool zoomed = false; - int selectedMarker = NO_MARKER; + + // Marker + const unsigned NO_MARKER = UINT_MAX; + #pragma pack(push, 1) + struct Line { + QVector3D x; + QVector3D y; + }; + #pragma pack(pop) + std::vector vaMarker; + unsigned selectedMarker = NO_MARKER; + QOpenGLBuffer m_marker; + QOpenGLVertexArrayObject m_vaoMarker; + + // Grid + QOpenGLBuffer m_grid; + QOpenGLVertexArrayObject m_vaoGrid[3]; + GLsizei gridDrawCounts[3]; + void generateGrid(); + + // Graphs + std::vector m_GraphHistory; + unsigned currentGraphInHistory = 0; + + // OpenGL shader, matrix, var-locations + std::unique_ptr m_program; + QMatrix4x4 pmvMatrix; ///< projection, view matrix + int colorLocation; + int vertexLocation; + int matrixLocation; }; diff --git a/openhantek/src/glscopegraph.cpp b/openhantek/src/glscopegraph.cpp new file mode 100644 index 0000000..5548baa --- /dev/null +++ b/openhantek/src/glscopegraph.cpp @@ -0,0 +1,72 @@ +#include "glscopegraph.h" +#include + +Graph::Graph() : buffer(QOpenGLBuffer::VertexBuffer) { + buffer.create(); + buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); +} + +void Graph::writeData(PPresult *data, QOpenGLShaderProgram *program, int vertexLocation) { + // Determine memory + int neededMemory = 0; + for (ChannelGraph &cg : data->vaChannelVoltage) neededMemory += cg.size() * sizeof(QVector3D); + for (ChannelGraph &cg : data->vaChannelSpectrum) neededMemory += cg.size() * sizeof(QVector3D); + + buffer.bind(); + + // Allocate space if necessary + if (neededMemory > allocatedMem) { + buffer.allocate(neededMemory); + allocatedMem = neededMemory; + } + + qDebug() << data->data(0)->frequency; + + // Write data to buffer + int offset = 0; + vaoVoltage.resize(data->vaChannelVoltage.size()); + vaoSpectrum.resize(data->vaChannelSpectrum.size()); + for (ChannelID channel = 0; channel < vaoVoltage.size(); ++channel) { + VaoCount &v = vaoVoltage[channel]; + VaoCount &s = vaoSpectrum[channel]; + int dataSize; + + // Voltage channel + if (!v.first) v.first = new QOpenGLVertexArrayObject; + ChannelGraph &gVoltage = data->vaChannelVoltage[channel]; + v.first->bind(); + dataSize = int(gVoltage.size() * sizeof(QVector3D)); + buffer.write(offset, gVoltage.data(), dataSize); + program->enableAttributeArray(vertexLocation); + program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, 0); + v.first->release(); + v.second = (int)gVoltage.size(); + offset += dataSize; + + // Spectrum channel + if (!s.first) s.first = new QOpenGLVertexArrayObject; + ChannelGraph &gSpectrum = data->vaChannelSpectrum[channel]; + s.first->bind(); + dataSize = int(gSpectrum.size() * sizeof(QVector3D)); + buffer.write(offset, gSpectrum.data(), dataSize); + program->enableAttributeArray(vertexLocation); + program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, 0); + s.first->release(); + s.second = (int)gSpectrum.size(); + offset += dataSize; + } + + buffer.release(); +} + +Graph::~Graph() { + for (auto &vao : vaoVoltage) { + vao.first->destroy(); + delete vao.first; + } + for (auto &vao : vaoSpectrum) { + vao.first->destroy(); + delete vao.first; + } + if (buffer.isCreated()) { buffer.destroy(); } +} diff --git a/openhantek/src/glscopegraph.h b/openhantek/src/glscopegraph.h new file mode 100644 index 0000000..cae9190 --- /dev/null +++ b/openhantek/src/glscopegraph.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "post/ppresult.h" + +struct Graph { + Graph(); + ~Graph(); + void writeData(PPresult *data, QOpenGLShaderProgram* program, int vertexLocation); + typedef std::pair VaoCount; + + public: + int allocatedMem = 0; + QOpenGLBuffer buffer; + std::vector vaoVoltage; + std::vector vaoSpectrum; +}; diff --git a/openhantek/src/main.cpp b/openhantek/src/main.cpp index 284f911..5675ef3 100644 --- a/openhantek/src/main.cpp +++ b/openhantek/src/main.cpp @@ -1,23 +1,27 @@ // SPDX-License-Identifier: GPL-2.0+ -#include #include +#include #include #include -#include #include +#include #include -#include #include +#include + +#include "post/graphgenerator.h" +#include "post/mathchannelgenerator.h" +#include "post/postprocessing.h" +#include "post/spectrumgenerator.h" -#include "analyse/dataanalyzer.h" +#include "dsomodel.h" #include "hantekdsocontrol.h" #include "mainwindow.h" +#include "selectdevice/selectsupporteddevice.h" #include "settings.h" #include "usb/usbdevice.h" -#include "dsomodel.h" -#include "selectdevice/selectsupporteddevice.h" #include "viewconstants.h" #ifndef VERSION @@ -26,12 +30,12 @@ using namespace Hantek; - /// \brief Initialize the device with the current settings. -void applySettingsToDevice(HantekDsoControl* dsoControl, DsoSettingsScope* scope, const Dso::ControlSpecification* spec) { +void applySettingsToDevice(HantekDsoControl *dsoControl, DsoSettingsScope *scope, + const Dso::ControlSpecification *spec) { bool mathUsed = scope->anyUsed(spec->channels); for (ChannelID channel = 0; channel < spec->channels; ++channel) { - dsoControl->setCoupling(channel, scope->coupling(channel,spec)); + dsoControl->setCoupling(channel, scope->coupling(channel, spec)); dsoControl->setGain(channel, scope->gain(channel) * DIVS_VOLTAGE); dsoControl->setOffset(channel, (scope->voltage[channel].offset / DIVS_VOLTAGE) + 0.5); dsoControl->setTriggerLevel(channel, scope->voltage[channel].trigger); @@ -47,9 +51,9 @@ void applySettingsToDevice(HantekDsoControl* dsoControl, DsoSettingsScope* scope dsoControl->setRecordLength(scope->horizontal.recordLength); else { auto recLenVec = dsoControl->getAvailableRecordLengths(); - ptrdiff_t index = std::distance( - recLenVec.begin(), std::find(recLenVec.begin(), recLenVec.end(), scope->horizontal.recordLength)); - dsoControl->setRecordLength(index < 0 ? 1 : index); + ptrdiff_t index = std::distance(recLenVec.begin(), + std::find(recLenVec.begin(), recLenVec.end(), scope->horizontal.recordLength)); + dsoControl->setRecordLength(index < 0 ? 1 : (unsigned)index); } dsoControl->setTriggerMode(scope->trigger.mode); dsoControl->setPretriggerPosition(scope->trigger.position * scope->horizontal.timebase * DIVS_TIME); @@ -57,7 +61,6 @@ void applySettingsToDevice(HantekDsoControl* dsoControl, DsoSettingsScope* scope dsoControl->setTriggerSource(scope->trigger.special, scope->trigger.source); } - /// \brief Initialize resources and translations and show the main window. int main(int argc, char *argv[]) { //////// Set application information //////// @@ -69,6 +72,7 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + // Prefer full desktop OpenGL without fixed pipeline QSurfaceFormat format; format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); @@ -111,27 +115,36 @@ int main(int argc, char *argv[]) { QObject::connect(device.get(), &USBDevice::deviceDisconnected, QCoreApplication::instance(), &QCoreApplication::quit); - //////// Create data analyser object //////// - QThread dataAnalyzerThread; - dataAnalyzerThread.setObjectName("dataAnalyzerThread"); - DataAnalyzer dataAnalyser; - dataAnalyser.setSourceData(&dsoControl.getLastSamples()); - dataAnalyser.moveToThread(&dataAnalyzerThread); - QObject::connect(&dsoControl, &HantekDsoControl::samplesAvailable, &dataAnalyser, &DataAnalyzer::samplesAvailable); - //////// Create settings object //////// - DsoSettings settings(&device->getModel()->specification); - dataAnalyser.applySettings(&settings); + auto settings = std::unique_ptr(new DsoSettings(&device->getModel()->specification)); + + //////// Create post processing objects //////// + QThread postProcessingThread; + postProcessingThread.setObjectName("postProcessingThread"); + PostProcessing postProcessing(settings->scope.countChannels()); + + SpectrumGenerator spectrumGenerator(&settings->scope); + MathChannelGenerator mathchannelGenerator(&settings->scope, device->getModel()->specification.channels); + GraphGenerator graphGenerator(&settings->scope, device->getModel()->specification.isSoftwareTriggerDevice); + + postProcessing.registerProcessor(&mathchannelGenerator); + postProcessing.registerProcessor(&spectrumGenerator); + postProcessing.registerProcessor(&graphGenerator); + + postProcessing.moveToThread(&postProcessingThread); + QObject::connect(&dsoControl, &HantekDsoControl::samplesAvailable, &postProcessing, &PostProcessing::input); //////// Create main window //////// - MainWindow *openHantekMainWindow = new MainWindow(&dsoControl, &dataAnalyser, &settings); - openHantekMainWindow->show(); + MainWindow openHantekMainWindow(&dsoControl, settings.get()); + QObject::connect(&postProcessing, &PostProcessing::processingFinished, &openHantekMainWindow, + &MainWindow::showNewData); + openHantekMainWindow.show(); - applySettingsToDevice(&dsoControl,&settings.scope,&device->getModel()->specification); + applySettingsToDevice(&dsoControl, &settings->scope, &device->getModel()->specification); //////// Start DSO thread and go into GUI main loop dsoControl.startSampling(); - dataAnalyzerThread.start(); + postProcessingThread.start(); dsoControlThread.start(); int res = openHantekApplication.exec(); @@ -139,7 +152,7 @@ int main(int argc, char *argv[]) { dsoControlThread.quit(); dsoControlThread.wait(10000); - dataAnalyzerThread.quit(); - dataAnalyzerThread.wait(10000); + postProcessingThread.quit(); + postProcessingThread.wait(10000); return res; } diff --git a/openhantek/src/mainwindow.cpp b/openhantek/src/mainwindow.cpp index 75cbc89..bd2a4b8 100644 --- a/openhantek/src/mainwindow.cpp +++ b/openhantek/src/mainwindow.cpp @@ -6,15 +6,13 @@ #include "TriggerDock.h" #include "VoltageDock.h" #include "dockwindows.h" -#include "exporter.h" #include "configdialog.h" -#include "analyse/dataanalyzer.h" #include "dockwindows.h" +#include "dsomodel.h" #include "dsowidget.h" #include "hantekdsocontrol.h" #include "usb/usbdevice.h" -#include "dsomodel.h" #include "viewconstants.h" #include "settings.h" @@ -23,12 +21,10 @@ #include #include -MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, DsoSettings *settings, QWidget *parent) : - QMainWindow(parent), ui(new Ui::MainWindow), dsoControl(dsoControl), dataAnalyzer(dataAnalyser), settings(settings) -{ +MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), mSettings(settings) { ui->setupUi(this); - // Window title setWindowIcon(QIcon(":openhantek.png")); setWindowTitle(tr("OpenHantek - Device %1").arg(QString::fromStdString(dsoControl->getDevice()->getModel()->name))); @@ -38,40 +34,46 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, setDockOptions(dockOptions() | QMainWindow::GroupedDragging); #endif + DsoSettingsScope *scope = &(mSettings->scope); + const Dso::ControlSpecification *spec = &dsoControl->getDevice()->getModel()->specification; + registerDockMetaTypes(); - horizontalDock = new HorizontalDock(&settings->scope, this); - triggerDock = new TriggerDock(&settings->scope, settings->deviceSpecification, this); - spectrumDock = new SpectrumDock(&settings->scope, this); - voltageDock = new VoltageDock(&settings->scope, settings->deviceSpecification, this); - // Central oszilloscope widget - dsoWidget = new DsoWidget(&settings->scope, &settings->view, settings->deviceSpecification); - connect(dataAnalyzer, &DataAnalyzer::analyzed, - [this]() { dsoWidget->showNewData(this->dataAnalyzer->getNextResult()); }); - setCentralWidget(dsoWidget); + // Docking windows + HorizontalDock *horizontalDock; + TriggerDock *triggerDock; + SpectrumDock *spectrumDock; + VoltageDock *voltageDock; + horizontalDock = new HorizontalDock(scope, this); + triggerDock = new TriggerDock(scope, spec, this); + spectrumDock = new SpectrumDock(scope, this); + voltageDock = new VoltageDock(scope, spec, this); addDockWidget(Qt::RightDockWidgetArea, horizontalDock); addDockWidget(Qt::RightDockWidgetArea, triggerDock); addDockWidget(Qt::RightDockWidgetArea, voltageDock); addDockWidget(Qt::RightDockWidgetArea, spectrumDock); - restoreGeometry(settings->mainWindowGeometry); - restoreState(settings->mainWindowState); + restoreGeometry(mSettings->mainWindowGeometry); + restoreState(mSettings->mainWindowState); + + // Central oszilloscope widget + dsoWidget = new DsoWidget(&mSettings->scope, &mSettings->view, spec); + setCentralWidget(dsoWidget); // Command field inside the status bar - QLineEdit* commandEdit = new QLineEdit(this); + QLineEdit *commandEdit = new QLineEdit(this); commandEdit->hide(); statusBar()->addPermanentWidget(commandEdit, 1); connect(ui->actionManualCommand, &QAction::toggled, [this, commandEdit](bool checked) { commandEdit->setVisible(checked); - if (checked) - commandEdit->setFocus(); + if (checked) commandEdit->setFocus(); }); - connect(commandEdit, &QLineEdit::returnPressed, [this, commandEdit]() { - Dso::ErrorCode errorCode = this->dsoControl->stringCommand(commandEdit->text()); + connect(commandEdit, &QLineEdit::returnPressed, [this, commandEdit, dsoControl]() { + Dso::ErrorCode errorCode = dsoControl->stringCommand(commandEdit->text()); commandEdit->clear(); this->ui->actionManualCommand->setChecked(false); if (errorCode != Dso::ErrorCode::NONE) statusBar()->showMessage(tr("Invalid command"), 3000); @@ -81,38 +83,38 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, connect(dsoControl, &HantekDsoControl::statusMessage, statusBar(), &QStatusBar::showMessage); // Connect signals to DSO controller and widget - connect(horizontalDock, &HorizontalDock::samplerateChanged, [this]() { - this->dsoControl->setSamplerate(this->settings->scope.horizontal.samplerate); - this->dsoWidget->updateSamplerate(this->settings->scope.horizontal.samplerate); + connect(horizontalDock, &HorizontalDock::samplerateChanged, [dsoControl, this]() { + dsoControl->setSamplerate(mSettings->scope.horizontal.samplerate); + this->dsoWidget->updateSamplerate(mSettings->scope.horizontal.samplerate); }); - connect(horizontalDock, &HorizontalDock::timebaseChanged, [this](){ - this->dsoControl->setRecordTime(this->settings->scope.horizontal.timebase * DIVS_TIME); - this->dsoWidget->updateTimebase(this->settings->scope.horizontal.timebase); + connect(horizontalDock, &HorizontalDock::timebaseChanged, [dsoControl, this]() { + dsoControl->setRecordTime(mSettings->scope.horizontal.timebase * DIVS_TIME); + this->dsoWidget->updateTimebase(mSettings->scope.horizontal.timebase); }); connect(horizontalDock, &HorizontalDock::frequencybaseChanged, dsoWidget, &DsoWidget::updateFrequencybase); - connect(horizontalDock, &HorizontalDock::recordLengthChanged, [this](unsigned long recordLength) { - this->dsoControl->setRecordLength(recordLength); - }); - - connect(dsoControl, &HantekDsoControl::recordTimeChanged, [this](double duration) { - if (this->settings->scope.horizontal.samplerateSource == DsoSettingsScopeHorizontal::Samplerrate && - this->settings->scope.horizontal.recordLength != UINT_MAX) { - // The samplerate was set, let's adapt the timebase accordingly - this->settings->scope.horizontal.timebase = horizontalDock->setTimebase(duration / DIVS_TIME); - } - - // The trigger position should be kept at the same place but the timebase has - // changed - this->dsoControl->setPretriggerPosition(this->settings->scope.trigger.position * this->settings->scope.horizontal.timebase * - DIVS_TIME); - - dsoWidget->updateTimebase(this->settings->scope.horizontal.timebase); - }); - connect(dsoControl, &HantekDsoControl::samplerateChanged, [this](double samplerate) { - if (this->settings->scope.horizontal.samplerateSource == DsoSettingsScopeHorizontal::Duration && - this->settings->scope.horizontal.recordLength != UINT_MAX) { + connect(horizontalDock, &HorizontalDock::recordLengthChanged, + [dsoControl](unsigned long recordLength) { dsoControl->setRecordLength(recordLength); }); + + connect(dsoControl, &HantekDsoControl::recordTimeChanged, + [this, settings, horizontalDock, dsoControl](double duration) { + if (settings->scope.horizontal.samplerateSource == DsoSettingsScopeHorizontal::Samplerrate && + settings->scope.horizontal.recordLength != UINT_MAX) { + // The samplerate was set, let's adapt the timebase accordingly + settings->scope.horizontal.timebase = horizontalDock->setTimebase(duration / DIVS_TIME); + } + + // The trigger position should be kept at the same place but the timebase has + // changed + dsoControl->setPretriggerPosition(settings->scope.trigger.position * + settings->scope.horizontal.timebase * DIVS_TIME); + + this->dsoWidget->updateTimebase(settings->scope.horizontal.timebase); + }); + connect(dsoControl, &HantekDsoControl::samplerateChanged, [this, horizontalDock](double samplerate) { + if (mSettings->scope.horizontal.samplerateSource == DsoSettingsScopeHorizontal::Duration && + mSettings->scope.horizontal.recordLength != UINT_MAX) { // The timebase was set, let's adapt the samplerate accordingly - this->settings->scope.horizontal.samplerate = samplerate; + mSettings->scope.horizontal.samplerate = samplerate; horizontalDock->setSamplerate(samplerate); dsoWidget->updateSamplerate(samplerate); } @@ -127,19 +129,18 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, connect(dsoWidget, &DsoWidget::triggerPositionChanged, dsoControl, &HantekDsoControl::setPretriggerPosition); connect(dsoWidget, &DsoWidget::triggerLevelChanged, dsoControl, &HantekDsoControl::setTriggerLevel); - auto usedChanged = [this](ChannelID channel, bool used) { - if (channel >= (unsigned int)this->settings->scope.voltage.size()) return; + auto usedChanged = [this, dsoControl, spec](ChannelID channel, bool used) { + if (channel >= (unsigned int)mSettings->scope.voltage.size()) return; - bool mathUsed = this->settings->scope.anyUsed(this->settings->deviceSpecification->channels); + bool mathUsed = mSettings->scope.anyUsed(spec->channels); // Normal channel, check if voltage/spectrum or math channel is used - if (channel < this->settings->deviceSpecification->channels) - this->dsoControl->setChannelUsed( - channel, mathUsed | this->settings->scope.anyUsed(channel)); + if (channel < spec->channels) + dsoControl->setChannelUsed(channel, mathUsed | mSettings->scope.anyUsed(channel)); // Math channel, update all channels - else if (channel == this->settings->deviceSpecification->channels) { - for (ChannelID c = 0; c < this->settings->deviceSpecification->channels; ++c) - this->dsoControl->setChannelUsed(c, mathUsed | this->settings->scope.anyUsed(c)); + else if (channel == spec->channels) { + for (ChannelID c = 0; c < spec->channels; ++c) + dsoControl->setChannelUsed(c, mathUsed | mSettings->scope.anyUsed(c)); } }; connect(voltageDock, &VoltageDock::usedChanged, usedChanged); @@ -148,15 +149,15 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, connect(voltageDock, &VoltageDock::couplingChanged, dsoControl, &HantekDsoControl::setCoupling); connect(voltageDock, &VoltageDock::couplingChanged, dsoWidget, &DsoWidget::updateVoltageCoupling); connect(voltageDock, &VoltageDock::modeChanged, dsoWidget, &DsoWidget::updateMathMode); - connect(voltageDock, &VoltageDock::gainChanged, [this](ChannelID channel, double gain) { - if (channel >= this->settings->deviceSpecification->channels) return; + connect(voltageDock, &VoltageDock::gainChanged, [this, dsoControl, spec](ChannelID channel, double gain) { + if (channel >= spec->channels) return; - this->dsoControl->setGain(channel, this->settings->scope.gain(channel) * DIVS_VOLTAGE); + dsoControl->setGain(channel, mSettings->scope.gain(channel) * DIVS_VOLTAGE); }); connect(voltageDock, &VoltageDock::gainChanged, dsoWidget, &DsoWidget::updateVoltageGain); - connect(dsoWidget, &DsoWidget::offsetChanged, [this](ChannelID channel) { - if (channel >= this->settings->deviceSpecification->channels) return; - this->dsoControl->setOffset(channel, (this->settings->scope.voltage[channel].offset / DIVS_VOLTAGE) + 0.5); + connect(dsoWidget, &DsoWidget::offsetChanged, [this, dsoControl, spec](ChannelID channel) { + if (channel >= spec->channels) return; + dsoControl->setOffset(channel, (mSettings->scope.voltage[channel].offset / DIVS_VOLTAGE) + 0.5); }); connect(voltageDock, &VoltageDock::usedChanged, dsoWidget, &DsoWidget::updateVoltageUsed); @@ -164,21 +165,21 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, connect(spectrumDock, &SpectrumDock::magnitudeChanged, dsoWidget, &DsoWidget::updateSpectrumMagnitude); // Started/stopped signals from oscilloscope - connect(dsoControl, &HantekDsoControl::samplingStarted, [this]() { + connect(dsoControl, &HantekDsoControl::samplingStarted, [this, dsoControl]() { this->ui->actionSampling->setText(tr("&Stop")); this->ui->actionSampling->setIcon(QIcon(":actions/stop.png")); this->ui->actionSampling->setStatusTip(tr("Stop the oscilloscope")); - disconnect(this->ui->actionSampling, &QAction::triggered, this->dsoControl, &HantekDsoControl::startSampling); - connect(this->ui->actionSampling, &QAction::triggered, this->dsoControl, &HantekDsoControl::stopSampling); + disconnect(this->ui->actionSampling, &QAction::triggered, dsoControl, &HantekDsoControl::startSampling); + connect(this->ui->actionSampling, &QAction::triggered, dsoControl, &HantekDsoControl::stopSampling); }); - connect(dsoControl, &HantekDsoControl::samplingStopped, [this]() { + connect(dsoControl, &HantekDsoControl::samplingStopped, [this, dsoControl]() { this->ui->actionSampling->setText(tr("&Start")); this->ui->actionSampling->setIcon(QIcon(":actions/start.png")); this->ui->actionSampling->setStatusTip(tr("Start the oscilloscope")); - disconnect(this->ui->actionSampling, &QAction::triggered, this->dsoControl, &HantekDsoControl::stopSampling); - connect(this->ui->actionSampling, &QAction::triggered, this->dsoControl, &HantekDsoControl::startSampling); + disconnect(this->ui->actionSampling, &QAction::triggered, dsoControl, &HantekDsoControl::stopSampling); + connect(this->ui->actionSampling, &QAction::triggered, dsoControl, &HantekDsoControl::startSampling); }); connect(dsoControl, &HantekDsoControl::availableRecordLengthsChanged, horizontalDock, @@ -190,67 +191,67 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, connect(ui->actionOpen, &QAction::triggered, [this]() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), "", tr("Settings (*.ini)")); if (!fileName.isEmpty()) { - if (this->settings->setFilename(fileName)) { - this->settings->load(); - } + if (mSettings->setFilename(fileName)) { mSettings->load(); } } }); connect(ui->actionSave, &QAction::triggered, [this]() { - this->settings->mainWindowGeometry = saveGeometry(); - this->settings->mainWindowState = saveState(); - this->settings->save(); + mSettings->mainWindowGeometry = saveGeometry(); + mSettings->mainWindowState = saveState(); + mSettings->save(); }); connect(ui->actionSave_as, &QAction::triggered, [this]() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save settings"), "", tr("Settings (*.ini)")); if (fileName.isEmpty()) return; - this->settings->mainWindowGeometry = saveGeometry(); - this->settings->mainWindowState = saveState(); - this->settings->setFilename(fileName); - this->settings->save(); + mSettings->mainWindowGeometry = saveGeometry(); + mSettings->mainWindowState = saveState(); + mSettings->setFilename(fileName); + mSettings->save(); }); - connect(ui->actionPrint, &QAction::triggered, [this]() { - this->dsoWidget->setExporterForNextFrame(std::unique_ptr(Exporter::createPrintExporter(this->settings))); + connect(ui->actionPrint, &QAction::triggered, [this, spec]() { + this->dsoWidget->setExporterForNextFrame( + std::unique_ptr(Exporter::createPrintExporter(spec, this->mSettings))); }); - connect(ui->actionExport, &QAction::triggered, [this]() { - this->dsoWidget->setExporterForNextFrame(std::unique_ptr(Exporter::createSaveToFileExporter(this->settings))); + connect(ui->actionExport, &QAction::triggered, [this, spec]() { + this->dsoWidget->setExporterForNextFrame( + std::unique_ptr(Exporter::createSaveToFileExporter(spec, this->mSettings))); }); connect(ui->actionExit, &QAction::triggered, this, &QWidget::close); connect(ui->actionSettings, &QAction::triggered, [this]() { - this->settings->mainWindowGeometry = saveGeometry(); - this->settings->mainWindowState = saveState(); + mSettings->mainWindowGeometry = saveGeometry(); + mSettings->mainWindowState = saveState(); - DsoConfigDialog* configDialog = new DsoConfigDialog(this->settings, this); + DsoConfigDialog *configDialog = new DsoConfigDialog(this->mSettings, this); configDialog->setModal(true); configDialog->show(); }); connect(this->ui->actionDigital_phosphor, &QAction::toggled, [this](bool enabled) { - this->settings->view.digitalPhosphor = enabled; + mSettings->view.digitalPhosphor = enabled; - if (this->settings->view.digitalPhosphor) + if (mSettings->view.digitalPhosphor) this->ui->actionDigital_phosphor->setStatusTip(tr("Disable fading of previous graphs")); else this->ui->actionDigital_phosphor->setStatusTip(tr("Enable fading of previous graphs")); }); - this->ui->actionDigital_phosphor->setChecked(settings->view.digitalPhosphor); + this->ui->actionDigital_phosphor->setChecked(mSettings->view.digitalPhosphor); connect(ui->actionZoom, &QAction::toggled, [this](bool enabled) { - this->settings->view.zoom = enabled; + mSettings->view.zoom = enabled; - if (this->settings->view.zoom) + if (mSettings->view.zoom) this->ui->actionZoom->setStatusTip(tr("Hide magnified scope")); else this->ui->actionZoom->setStatusTip(tr("Show magnified scope")); this->dsoWidget->updateZoom(enabled); }); - ui->actionZoom->setChecked(settings->view.zoom); + ui->actionZoom->setChecked(mSettings->view.zoom); connect(ui->actionAbout, &QAction::triggered, [this]() { QMessageBox::about( @@ -263,29 +264,28 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, }); - if (settings->scope.horizontal.samplerateSource == DsoSettingsScopeHorizontal::Samplerrate) - dsoWidget->updateSamplerate(settings->scope.horizontal.samplerate); + if (mSettings->scope.horizontal.samplerateSource == DsoSettingsScopeHorizontal::Samplerrate) + dsoWidget->updateSamplerate(mSettings->scope.horizontal.samplerate); else - dsoWidget->updateTimebase(settings->scope.horizontal.timebase); + dsoWidget->updateTimebase(mSettings->scope.horizontal.timebase); - for (ChannelID channel = 0; channel < settings->deviceSpecification->channels; ++channel) { - this->dsoWidget->updateVoltageUsed(channel, settings->scope.voltage[channel].used); - this->dsoWidget->updateSpectrumUsed(channel, settings->scope.spectrum[channel].used); + for (ChannelID channel = 0; channel < spec->channels; ++channel) { + this->dsoWidget->updateVoltageUsed(channel, mSettings->scope.voltage[channel].used); + this->dsoWidget->updateSpectrumUsed(channel, mSettings->scope.spectrum[channel].used); } } -MainWindow::~MainWindow() -{ - delete ui; -} +MainWindow::~MainWindow() { delete ui; } + +void MainWindow::showNewData(std::shared_ptr data) { dsoWidget->showNew(data); } /// \brief Save the settings before exiting. /// \param event The close event that should be handled. void MainWindow::closeEvent(QCloseEvent *event) { - if (settings->options.alwaysSave) { - settings->mainWindowGeometry = saveGeometry(); - settings->mainWindowState = saveState(); - settings->save(); + if (mSettings->alwaysSave) { + mSettings->mainWindowGeometry = saveGeometry(); + mSettings->mainWindowState = saveState(); + mSettings->save(); } QMainWindow::closeEvent(event); diff --git a/openhantek/src/mainwindow.h b/openhantek/src/mainwindow.h index 50a1948..5aecd02 100644 --- a/openhantek/src/mainwindow.h +++ b/openhantek/src/mainwindow.h @@ -1,7 +1,9 @@ #pragma once #include +#include +#include "post/ppresult.h" -class DataAnalyzer; +class SpectrumGenerator; class HantekDsoControl; class DsoSettings; class DsoWidget; @@ -22,27 +24,19 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyser, DsoSettings *settings, QWidget *parent = 0); + explicit MainWindow(HantekDsoControl *dsoControl, DsoSettings *mSettings, QWidget *parent = 0); ~MainWindow(); +public slots: + void showNewData(std::shared_ptr data); protected: void closeEvent(QCloseEvent *event) override; private: Ui::MainWindow *ui; - // Docking windows - HorizontalDock *horizontalDock; - TriggerDock *triggerDock; - SpectrumDock *spectrumDock; - VoltageDock *voltageDock; - // Central widgets DsoWidget *dsoWidget; - // Data handling classes - HantekDsoControl *dsoControl; - DataAnalyzer *dataAnalyzer; - // Settings used for the whole program - DsoSettings *settings; + DsoSettings *mSettings; }; diff --git a/openhantek/src/utils/dsoStrings.h b/openhantek/src/utils/dsoStrings.h index f69e604..153bb27 100644 --- a/openhantek/src/utils/dsoStrings.h +++ b/openhantek/src/utils/dsoStrings.h @@ -2,7 +2,7 @@ #pragma once #include -#include "analyse/enums.h" +#include "post/enums.h" #include "hantekdso/enums.h" #define MARKER_COUNT 2 ///< Number of markers