Commit 0eff8d451180c8c1328ff0d33de4e2f477147e16

Authored by Denis Dovzhenko
Committed by David Gräff
1 parent 52622ac9

Cursor measurements (#177)

* Cursor measurements
* encapsulate measurements grid into widget
* enable fg / bg color change after creation
openhantek/src/configdialog/DsoConfigScopePage.cpp
@@ -27,8 +27,22 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : @@ -27,8 +27,22 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) :
27 graphGroup = new QGroupBox(tr("Graph")); 27 graphGroup = new QGroupBox(tr("Graph"));
28 graphGroup->setLayout(graphLayout); 28 graphGroup->setLayout(graphLayout);
29 29
  30 + cursorsLabel = new QLabel(tr("Position"));
  31 + cursorsComboBox = new QComboBox();
  32 + cursorsComboBox->addItem("Left", Qt::LeftToolBarArea);
  33 + cursorsComboBox->addItem("Right", Qt::RightToolBarArea);
  34 + cursorsComboBox->setCurrentIndex(settings->view.cursorGridPosition == Qt::LeftToolBarArea ? 0 : 1);
  35 +
  36 + cursorsLayout = new QGridLayout();
  37 + cursorsLayout->addWidget(cursorsLabel, 0, 0);
  38 + cursorsLayout->addWidget(cursorsComboBox, 0, 1);
  39 +
  40 + cursorsGroup = new QGroupBox(tr("Cursors"));
  41 + cursorsGroup->setLayout(cursorsLayout);
  42 +
30 mainLayout = new QVBoxLayout(); 43 mainLayout = new QVBoxLayout();
31 mainLayout->addWidget(graphGroup); 44 mainLayout->addWidget(graphGroup);
  45 + mainLayout->addWidget(cursorsGroup);
32 mainLayout->addStretch(1); 46 mainLayout->addStretch(1);
33 47
34 setLayout(mainLayout); 48 setLayout(mainLayout);
@@ -38,4 +52,5 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : @@ -38,4 +52,5 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) :
38 void DsoConfigScopePage::saveSettings() { 52 void DsoConfigScopePage::saveSettings() {
39 settings->view.interpolation = (Dso::InterpolationMode)interpolationComboBox->currentIndex(); 53 settings->view.interpolation = (Dso::InterpolationMode)interpolationComboBox->currentIndex();
40 settings->view.digitalPhosphorDepth = digitalPhosphorDepthSpinBox->value(); 54 settings->view.digitalPhosphorDepth = digitalPhosphorDepthSpinBox->value();
  55 + settings->view.cursorGridPosition = (Qt::ToolBarArea)cursorsComboBox->currentData().toUInt();
41 } 56 }
openhantek/src/configdialog/DsoConfigScopePage.h
@@ -37,4 +37,9 @@ class DsoConfigScopePage : public QWidget { @@ -37,4 +37,9 @@ class DsoConfigScopePage : public QWidget {
37 QSpinBox *digitalPhosphorDepthSpinBox; 37 QSpinBox *digitalPhosphorDepthSpinBox;
38 QLabel *interpolationLabel; 38 QLabel *interpolationLabel;
39 QComboBox *interpolationComboBox; 39 QComboBox *interpolationComboBox;
  40 +
  41 + QGroupBox *cursorsGroup;
  42 + QGridLayout *cursorsLayout;
  43 + QLabel *cursorsLabel;
  44 + QComboBox *cursorsComboBox;
40 }; 45 };
openhantek/src/dsowidget.cpp
@@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
22 #include "viewconstants.h" 22 #include "viewconstants.h"
23 #include "viewsettings.h" 23 #include "viewsettings.h"
24 #include "widgets/levelslider.h" 24 #include "widgets/levelslider.h"
  25 +#include "widgets/datagrid.h"
25 26
26 static int zoomScopeRow = 0; 27 static int zoomScopeRow = 0;
27 28
@@ -38,12 +39,14 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: @@ -38,12 +39,14 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso::
38 setupSliders(mainSliders); 39 setupSliders(mainSliders);
39 setupSliders(zoomSliders); 40 setupSliders(zoomSliders);
40 41
41 - connect(mainScope, &GlScope::markerMoved, [this](int marker, double position) {  
42 - double value = std::round(position / MARKER_STEP) * MARKER_STEP;  
43 -  
44 - this->scope->horizontal.marker[marker] = value;  
45 - this->mainSliders.markerSlider->setValue(marker, value);  
46 - this->mainScope->markerUpdated(); 42 + connect(mainScope, &GlScope::markerMoved, [this](unsigned cursorIndex, unsigned marker) {
  43 + mainSliders.markerSlider->setValue(marker, this->scope->getMarker(marker));
  44 + mainScope->updateCursor(cursorIndex);
  45 + zoomScope->updateCursor(cursorIndex);
  46 + });
  47 + connect(zoomScope, &GlScope::markerMoved, [this](unsigned cursorIndex, unsigned marker) {
  48 + mainScope->updateCursor(cursorIndex);
  49 + zoomScope->updateCursor(cursorIndex);
47 }); 50 });
48 51
49 // The table for the settings 52 // The table for the settings
@@ -142,40 +145,88 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: @@ -142,40 +145,88 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso::
142 updateSpectrumDetails((unsigned)channel); 145 updateSpectrumDetails((unsigned)channel);
143 } 146 }
144 147
  148 + // Cursors
  149 + cursorDataGrid = new DataGrid(this);
  150 + cursorDataGrid->setBackgroundColor(view->screen.background);
  151 + cursorDataGrid->addItem(tr("Markers"), view->screen.text);
  152 + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
  153 + cursorDataGrid->addItem(scope->voltage[channel].name, view->screen.voltage[channel]);
  154 + }
  155 + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel) {
  156 + cursorDataGrid->addItem(scope->spectrum[channel].name, view->screen.spectrum[channel]);
  157 + }
  158 + cursorDataGrid->selectItem(0);
  159 +
  160 + connect(cursorDataGrid, &DataGrid::itemSelected, [this] (unsigned index) {
  161 + mainScope->cursorSelected(index);
  162 + zoomScope->cursorSelected(index);
  163 + });
  164 + connect(cursorDataGrid, &DataGrid::itemUpdated, [this, scope] (unsigned index) {
  165 + unsigned channelCount = scope->countChannels();
  166 + if (0 < index && index < channelCount + 1) {
  167 + ChannelID channel = index - 1;
  168 + if (scope->voltage[channel].used) {
  169 + unsigned shape = (unsigned)scope->voltage[channel].cursor.shape;
  170 + if (shape == DsoSettingsScopeCursor::NONE) {
  171 + scope->voltage[channel].cursor.shape = DsoSettingsScopeCursor::RECTANGULAR;
  172 + } else {
  173 + scope->voltage[channel].cursor.shape = DsoSettingsScopeCursor::NONE;
  174 + }
  175 + }
  176 + } else if (channelCount < index && index < 2 * channelCount + 1) {
  177 + ChannelID channel = index - channelCount - 1;
  178 + if (scope->spectrum[channel].used) {
  179 + unsigned shape = (unsigned)scope->spectrum[channel].cursor.shape;
  180 + if (shape == DsoSettingsScopeCursor::NONE) {
  181 + scope->spectrum[channel].cursor.shape = DsoSettingsScopeCursor::RECTANGULAR;
  182 + } else {
  183 + scope->spectrum[channel].cursor.shape = DsoSettingsScopeCursor::NONE;
  184 + }
  185 + }
  186 + }
  187 + updateMarkerDetails();
  188 + mainScope->updateCursor(index);
  189 + zoomScope->updateCursor(index);
  190 + });
  191 +
  192 + scope->horizontal.cursor.shape = DsoSettingsScopeCursor::VERTICAL;
  193 +
145 // The layout for the widgets 194 // The layout for the widgets
146 mainLayout = new QGridLayout(); 195 mainLayout = new QGridLayout();
147 - mainLayout->setColumnStretch(2, 1); // Scopes increase their size 196 + mainLayout->setColumnStretch(3, 1); // Scopes increase their size
148 // Bars around the scope, needed because the slider-drawing-area is outside 197 // Bars around the scope, needed because the slider-drawing-area is outside
149 // the scope at min/max 198 // the scope at min/max
150 - mainLayout->setColumnMinimumWidth(1, mainSliders.triggerPositionSlider->preMargin());  
151 - mainLayout->setColumnMinimumWidth(3, mainSliders.triggerPositionSlider->postMargin()); 199 + mainLayout->setColumnMinimumWidth(2, mainSliders.triggerPositionSlider->preMargin());
  200 + mainLayout->setColumnMinimumWidth(4, mainSliders.triggerPositionSlider->postMargin());
152 mainLayout->setSpacing(0); 201 mainLayout->setSpacing(0);
153 int row = 0; 202 int row = 0;
154 - mainLayout->addLayout(settingsLayout, row++, 0, 1, 5); 203 + mainLayout->addLayout(settingsLayout, row++, 1, 1, 5);
155 // 5x5 box for mainScope & mainSliders 204 // 5x5 box for mainScope & mainSliders
156 mainLayout->setRowMinimumHeight(row + 1, mainSliders.offsetSlider->preMargin()); 205 mainLayout->setRowMinimumHeight(row + 1, mainSliders.offsetSlider->preMargin());
157 mainLayout->setRowMinimumHeight(row + 3, mainSliders.offsetSlider->postMargin()); 206 mainLayout->setRowMinimumHeight(row + 3, mainSliders.offsetSlider->postMargin());
158 mainLayout->setRowStretch(row + 2, 1); 207 mainLayout->setRowStretch(row + 2, 1);
159 - mainLayout->addWidget(mainScope, row + 2, 2);  
160 - mainLayout->addWidget(mainSliders.offsetSlider, row + 1, 0, 3, 2, Qt::AlignRight);  
161 - mainLayout->addWidget(mainSliders.triggerPositionSlider, row, 1, 2, 3, Qt::AlignBottom);  
162 - mainLayout->addWidget(mainSliders.triggerLevelSlider, row + 1, 3, 3, 2, Qt::AlignLeft);  
163 - mainLayout->addWidget(mainSliders.markerSlider, row + 3, 1, 2, 3, Qt::AlignTop); 208 + mainLayout->addWidget(mainScope, row + 2, 3);
  209 + mainLayout->addWidget(mainSliders.offsetSlider, row + 1, 1, 3, 2, Qt::AlignRight);
  210 + mainLayout->addWidget(mainSliders.triggerPositionSlider, row, 2, 2, 3, Qt::AlignBottom);
  211 + mainLayout->addWidget(mainSliders.triggerLevelSlider, row + 1, 4, 3, 2, Qt::AlignLeft);
  212 + mainLayout->addWidget(mainSliders.markerSlider, row + 3, 2, 2, 3, Qt::AlignTop);
164 row += 5; 213 row += 5;
165 // Separators and markerLayout 214 // Separators and markerLayout
166 - mainLayout->setRowMinimumHeight(row++, 4);  
167 - mainLayout->addLayout(markerLayout, row++, 0, 1, 5); 215 + mainLayout->setRowMinimumHeight(row++, 5);
  216 + mainLayout->addLayout(markerLayout, row++, 1, 1, 5);
168 mainLayout->setRowMinimumHeight(row++, 4); 217 mainLayout->setRowMinimumHeight(row++, 4);
169 // 5x5 box for zoomScope & zoomSliders 218 // 5x5 box for zoomScope & zoomSliders
170 zoomScopeRow = row + 2; 219 zoomScopeRow = row + 2;
171 - mainLayout->addWidget(zoomScope, zoomScopeRow, 2);  
172 - mainLayout->addWidget(zoomSliders.offsetSlider, row + 1, 0, 3, 2, Qt::AlignRight);  
173 - mainLayout->addWidget(zoomSliders.triggerPositionSlider, row, 1, 2, 3, Qt::AlignBottom);  
174 - mainLayout->addWidget(zoomSliders.triggerLevelSlider, row + 1, 3, 3, 2, Qt::AlignLeft); 220 + mainLayout->addWidget(zoomScope, zoomScopeRow, 3);
  221 + mainLayout->addWidget(zoomSliders.offsetSlider, row + 1, 1, 3, 2, Qt::AlignRight);
  222 + mainLayout->addWidget(zoomSliders.triggerPositionSlider, row, 2, 2, 3, Qt::AlignBottom);
  223 + mainLayout->addWidget(zoomSliders.triggerLevelSlider, row + 1, 4, 3, 2, Qt::AlignLeft);
175 row += 5; 224 row += 5;
176 // Separator and embedded measurementLayout 225 // Separator and embedded measurementLayout
177 mainLayout->setRowMinimumHeight(row++, 8); 226 mainLayout->setRowMinimumHeight(row++, 8);
178 - mainLayout->addLayout(measurementLayout, row++, 0, 1, 5); 227 + mainLayout->addLayout(measurementLayout, row++, 1, 1, 5);
  228 +
  229 + updateCursorGrid(view->cursorsVisible);
179 230
180 // The widget itself 231 // The widget itself
181 setPalette(palette); 232 setPalette(palette);
@@ -187,20 +238,54 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: @@ -187,20 +238,54 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso::
187 connect(mainSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset); 238 connect(mainSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset);
188 connect(zoomSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset); 239 connect(zoomSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset);
189 240
190 - connect(mainSliders.triggerPositionSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerPosition);  
191 - zoomSliders.triggerPositionSlider->setEnabled(false); 241 + connect(mainSliders.triggerPositionSlider, &LevelSlider::valueChanged, [this](int index, double value) {
  242 + updateTriggerPosition(index, value, true);
  243 + });
  244 + connect(zoomSliders.triggerPositionSlider, &LevelSlider::valueChanged, [this](int index, double value) {
  245 + updateTriggerPosition(index, value, false);
  246 + });
192 247
193 connect(mainSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel); 248 connect(mainSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel);
194 connect(zoomSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel); 249 connect(zoomSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel);
195 250
196 connect(mainSliders.markerSlider, &LevelSlider::valueChanged, [this](int index, double value) { 251 connect(mainSliders.markerSlider, &LevelSlider::valueChanged, [this](int index, double value) {
197 updateMarker(index, value); 252 updateMarker(index, value);
198 - mainScope->update();  
199 - zoomScope->update(); 253 + mainScope->updateCursor();
  254 + zoomScope->updateCursor();
200 }); 255 });
201 zoomSliders.markerSlider->setEnabled(false); 256 zoomSliders.markerSlider->setEnabled(false);
202 } 257 }
203 258
  259 +void DsoWidget::updateCursorGrid(bool enabled) {
  260 + if (!enabled) {
  261 + cursorDataGrid->selectItem(0);
  262 + cursorDataGrid->setParent(nullptr);
  263 + mainScope->cursorSelected(0);
  264 + zoomScope->cursorSelected(0);
  265 + return;
  266 + }
  267 +
  268 + switch (view->cursorGridPosition) {
  269 + case Qt::LeftToolBarArea:
  270 + if (mainLayout->itemAtPosition(0, 0) == nullptr) {
  271 + cursorDataGrid->setParent(nullptr);
  272 + mainLayout->addWidget(cursorDataGrid, 0, 0, mainLayout->rowCount(), 1);
  273 + }
  274 + break;
  275 + case Qt::RightToolBarArea:
  276 + if (mainLayout->itemAtPosition(0, 6) == nullptr) {
  277 + cursorDataGrid->setParent(nullptr);
  278 + mainLayout->addWidget(cursorDataGrid, 0, 6, mainLayout->rowCount(), 1);
  279 + }
  280 + break;
  281 + default:
  282 + if (cursorDataGrid->parent() != nullptr) {
  283 + cursorDataGrid->setParent(nullptr);
  284 + }
  285 + break;
  286 + }
  287 +}
  288 +
204 void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) { 289 void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) {
205 // The offset sliders for all possible channels 290 // The offset sliders for all possible channels
206 sliders.offsetSlider = new LevelSlider(Qt::RightArrow); 291 sliders.offsetSlider = new LevelSlider(Qt::RightArrow);
@@ -248,7 +333,7 @@ void DsoWidget::setupSliders(DsoWidget::Sliders &amp;sliders) { @@ -248,7 +333,7 @@ void DsoWidget::setupSliders(DsoWidget::Sliders &amp;sliders) {
248 sliders.markerSlider->addSlider(QString::number(marker + 1), marker); 333 sliders.markerSlider->addSlider(QString::number(marker + 1), marker);
249 sliders.markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2); 334 sliders.markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2);
250 sliders.markerSlider->setStep(marker, MARKER_STEP); 335 sliders.markerSlider->setStep(marker, MARKER_STEP);
251 - sliders.markerSlider->setValue(marker, scope->horizontal.marker[marker]); 336 + sliders.markerSlider->setValue(marker, scope->horizontal.cursor.pos[marker].x());
252 sliders.markerSlider->setIndexVisible(marker, true); 337 sliders.markerSlider->setIndexVisible(marker, true);
253 } 338 }
254 } 339 }
@@ -285,7 +370,7 @@ void DsoWidget::setMeasurementVisible(ChannelID channel) { @@ -285,7 +370,7 @@ void DsoWidget::setMeasurementVisible(ChannelID channel) {
285 } 370 }
286 371
287 static QString markerToString(DsoSettingsScope *scope, unsigned index) { 372 static QString markerToString(DsoSettingsScope *scope, unsigned index) {
288 - double value = (DIVS_TIME * (0.5 - scope->trigger.position) + scope->horizontal.marker[index]) * scope->horizontal.timebase; 373 + double value = (DIVS_TIME * (0.5 - scope->trigger.position) + scope->getMarker(index)) * scope->horizontal.timebase;
289 int precision = 3 - (int)floor(log10(fabs(value))); 374 int precision = 3 - (int)floor(log10(fabs(value)));
290 375
291 if (scope->horizontal.timebase < 1e-9) 376 if (scope->horizontal.timebase < 1e-9)
@@ -302,8 +387,9 @@ static QString markerToString(DsoSettingsScope *scope, unsigned index) { @@ -302,8 +387,9 @@ static QString markerToString(DsoSettingsScope *scope, unsigned index) {
302 387
303 /// \brief Update the label about the marker measurements 388 /// \brief Update the label about the marker measurements
304 void DsoWidget::updateMarkerDetails() { 389 void DsoWidget::updateMarkerDetails() {
305 - double divs = fabs(scope->horizontal.marker[1] - scope->horizontal.marker[0]); 390 + double divs = fabs(scope->horizontal.cursor.pos[1].x() - scope->horizontal.cursor.pos[0].x());
306 double time = divs * scope->horizontal.timebase; 391 double time = divs * scope->horizontal.timebase;
  392 + double freq = divs * scope->horizontal.frequencybase;
307 393
308 QString prefix(tr("Markers")); 394 QString prefix(tr("Markers"));
309 if (view->zoom) { 395 if (view->zoom) {
@@ -315,6 +401,36 @@ void DsoWidget::updateMarkerDetails() { @@ -315,6 +401,36 @@ void DsoWidget::updateMarkerDetails() {
315 markerInfoLabel->setText(prefix.append(": %1 %2").arg(markerToString(scope, 0)).arg(markerToString(scope, 1))); 401 markerInfoLabel->setText(prefix.append(": %1 %2").arg(markerToString(scope, 0)).arg(markerToString(scope, 1)));
316 markerTimeLabel->setText(valueToString(time, UNIT_SECONDS, 4)); 402 markerTimeLabel->setText(valueToString(time, UNIT_SECONDS, 4));
317 markerFrequencyLabel->setText(valueToString(1.0 / time, UNIT_HERTZ, 4)); 403 markerFrequencyLabel->setText(valueToString(1.0 / time, UNIT_HERTZ, 4));
  404 +
  405 + int index = 0;
  406 + cursorDataGrid->updateInfo(index++, true, QString(), valueToString(time, UNIT_SECONDS, 4), valueToString(freq, UNIT_HERTZ, 4));
  407 +
  408 + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
  409 + if (scope->voltage[channel].used) {
  410 + QPointF p0 = scope->voltage[channel].cursor.pos[0];
  411 + QPointF p1 = scope->voltage[channel].cursor.pos[1];
  412 + cursorDataGrid->updateInfo(index, true,
  413 + scope->voltage[channel].cursor.shape != DsoSettingsScopeCursor::NONE ? tr("ON") : tr("OFF"),
  414 + valueToString(fabs(p1.x() - p0.x()) * scope->horizontal.timebase, UNIT_SECONDS, 4),
  415 + valueToString(fabs(p1.y() - p0.y()) * scope->gain(channel), UNIT_VOLTS, 4));
  416 + } else {
  417 + cursorDataGrid->updateInfo(index, false);
  418 + }
  419 + ++index;
  420 + }
  421 + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel) {
  422 + if (scope->spectrum[channel].used) {
  423 + QPointF p0 = scope->spectrum[channel].cursor.pos[0];
  424 + QPointF p1 = scope->spectrum[channel].cursor.pos[1];
  425 + cursorDataGrid->updateInfo(index, true,
  426 + scope->spectrum[channel].cursor.shape != DsoSettingsScopeCursor::NONE ? tr("ON") : tr("OFF"),
  427 + valueToString(fabs(p1.x() - p0.x()) * scope->horizontal.frequencybase, UNIT_HERTZ, 4),
  428 + valueToString(fabs(p1.y() - p0.y()) * scope->spectrum[channel].magnitude, UNIT_DECIBEL, 4));
  429 + } else {
  430 + cursorDataGrid->updateInfo(index, false);
  431 + }
  432 + ++index;
  433 + }
318 } 434 }
319 435
320 /// \brief Update the label about the trigger settings 436 /// \brief Update the label about the trigger settings
@@ -385,10 +501,13 @@ void DsoWidget::updateSpectrumMagnitude(ChannelID channel) { updateSpectrumDetai @@ -385,10 +501,13 @@ void DsoWidget::updateSpectrumMagnitude(ChannelID channel) { updateSpectrumDetai
385 void DsoWidget::updateSpectrumUsed(ChannelID channel, bool used) { 501 void DsoWidget::updateSpectrumUsed(ChannelID channel, bool used) {
386 if (channel >= (unsigned int)scope->voltage.size()) return; 502 if (channel >= (unsigned int)scope->voltage.size()) return;
387 503
  504 +// if (!used && cursorDataGrid->spectrumCursors[channel].selector->isChecked()) cursorDataGrid->selectItem(0);
  505 +
388 mainSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used); 506 mainSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used);
389 zoomSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used); 507 zoomSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used);
390 508
391 updateSpectrumDetails(channel); 509 updateSpectrumDetails(channel);
  510 + updateMarkerDetails();
392 } 511 }
393 512
394 /// \brief Handles modeChanged signal from the trigger dock. 513 /// \brief Handles modeChanged signal from the trigger dock.
@@ -452,6 +571,8 @@ void DsoWidget::updateVoltageGain(ChannelID channel) { @@ -452,6 +571,8 @@ void DsoWidget::updateVoltageGain(ChannelID channel) {
452 void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) { 571 void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) {
453 if (channel >= (unsigned int)scope->voltage.size()) return; 572 if (channel >= (unsigned int)scope->voltage.size()) return;
454 573
  574 +// if (!used && cursorDataGrid->voltageCursors[channel].selector->isChecked()) cursorDataGrid->selectItem(0);
  575 +
455 mainSliders.offsetSlider->setIndexVisible(channel, used); 576 mainSliders.offsetSlider->setIndexVisible(channel, used);
456 zoomSliders.offsetSlider->setIndexVisible(channel, used); 577 zoomSliders.offsetSlider->setIndexVisible(channel, used);
457 578
@@ -460,6 +581,7 @@ void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) { @@ -460,6 +581,7 @@ void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) {
460 581
461 setMeasurementVisible(channel); 582 setMeasurementVisible(channel);
462 updateVoltageDetails(channel); 583 updateVoltageDetails(channel);
  584 + updateMarkerDetails();
463 } 585 }
464 586
465 /// \brief Change the record length. 587 /// \brief Change the record length.
@@ -549,11 +671,11 @@ void DsoWidget::updateOffset(ChannelID channel, double value) { @@ -549,11 +671,11 @@ void DsoWidget::updateOffset(ChannelID channel, double value) {
549 671
550 if (channel < scope->voltage.size() * 2) { 672 if (channel < scope->voltage.size() * 2) {
551 if (mainSliders.offsetSlider->value(channel) != value) { 673 if (mainSliders.offsetSlider->value(channel) != value) {
552 - QSignalBlocker blocker(mainSliders.offsetSlider); 674 + const QSignalBlocker blocker(mainSliders.offsetSlider);
553 mainSliders.offsetSlider->setValue(channel, value); 675 mainSliders.offsetSlider->setValue(channel, value);
554 } 676 }
555 if (zoomSliders.offsetSlider->value(channel) != value) { 677 if (zoomSliders.offsetSlider->value(channel) != value) {
556 - QSignalBlocker blocker(zoomSliders.offsetSlider); 678 + const QSignalBlocker blocker(zoomSliders.offsetSlider);
557 zoomSliders.offsetSlider->setValue(channel, value); 679 zoomSliders.offsetSlider->setValue(channel, value);
558 } 680 }
559 } 681 }
@@ -561,18 +683,38 @@ void DsoWidget::updateOffset(ChannelID channel, double value) { @@ -561,18 +683,38 @@ void DsoWidget::updateOffset(ChannelID channel, double value) {
561 emit offsetChanged(channel, value); 683 emit offsetChanged(channel, value);
562 } 684 }
563 685
  686 +/// \brief Translate horizontal position (0..1) from main view to zoom view.
  687 +double DsoWidget::mainToZoom(double position) const {
  688 + double m1 = scope->getMarker(0);
  689 + double m2 = scope->getMarker(1);
  690 + if (m1 > m2) std::swap(m1, m2);
  691 + return ((position - 0.5) * DIVS_TIME - m1) / (m2 - m1);
  692 +}
  693 +
  694 +/// \brief Translate horizontal position (0..1) from zoom view to main view.
  695 +double DsoWidget::zoomToMain(double position) const {
  696 + double m1 = scope->getMarker(0);
  697 + double m2 = scope->getMarker(1);
  698 + if (m1 > m2) std::swap(m1, m2);
  699 + return 0.5 + (m1 + position * (m2 - m1)) / DIVS_TIME;
  700 +}
  701 +
564 /// \brief Handles signals affecting trigger position in the zoom view. 702 /// \brief Handles signals affecting trigger position in the zoom view.
565 void DsoWidget::adaptTriggerPositionSlider() { 703 void DsoWidget::adaptTriggerPositionSlider() {
566 - double m1 = scope->horizontal.marker[0];  
567 - double m2 = scope->horizontal.marker[1]; 704 + double value = mainToZoom(scope->trigger.position);
568 705
569 - if (m1 > m2) std::swap(m1, m2);  
570 - double value = (scope->trigger.position - 0.5) * DIVS_TIME;  
571 - if (m1 != m2 && m1 <= value && value <= m2) {  
572 - zoomSliders.triggerPositionSlider->setIndexVisible(0, true);  
573 - zoomSliders.triggerPositionSlider->setValue(0, (value - m1) / (m2 - m1)); 706 + LevelSlider &slider = *zoomSliders.triggerPositionSlider;
  707 + const QSignalBlocker blocker(slider);
  708 + if (slider.minimum(0) <= value && value <= slider.maximum(0)) {
  709 + slider.setEnabled(true);
  710 + slider.setValue(0, value);
574 } else { 711 } else {
575 - zoomSliders.triggerPositionSlider->setIndexVisible(0, false); 712 + slider.setEnabled(false);
  713 + if (value < slider.minimum(0)) {
  714 + slider.setValue(0, slider.minimum(0));
  715 + } else {
  716 + slider.setValue(0, slider.maximum(0));
  717 + }
576 } 718 }
577 } 719 }
578 720
@@ -580,16 +722,22 @@ void DsoWidget::adaptTriggerPositionSlider() { @@ -580,16 +722,22 @@ void DsoWidget::adaptTriggerPositionSlider() {
580 /// \param index The index of the slider. 722 /// \param index The index of the slider.
581 /// \param value The new triggerPosition in seconds relative to the first 723 /// \param value The new triggerPosition in seconds relative to the first
582 /// sample. 724 /// sample.
583 -void DsoWidget::updateTriggerPosition(int index, double value) { 725 +void DsoWidget::updateTriggerPosition(int index, double value, bool mainView) {
584 if (index != 0) return; 726 if (index != 0) return;
585 727
586 - scope->trigger.position = value;  
587 - adaptTriggerPositionSlider(); 728 + if (mainView) {
  729 + scope->trigger.position = value;
  730 + adaptTriggerPositionSlider();
  731 + } else {
  732 + scope->trigger.position = zoomToMain(value);
  733 + const QSignalBlocker blocker(mainSliders.triggerPositionSlider);
  734 + mainSliders.triggerPositionSlider->setValue(index, scope->trigger.position);
  735 + }
588 736
589 updateTriggerDetails(); 737 updateTriggerDetails();
590 updateMarkerDetails(); 738 updateMarkerDetails();
591 739
592 - emit triggerPositionChanged(value * scope->horizontal.timebase * DIVS_TIME); 740 + emit triggerPositionChanged(scope->trigger.position * scope->horizontal.timebase * DIVS_TIME);
593 } 741 }
594 742
595 /// \brief Handles valueChanged signal from the trigger level slider. 743 /// \brief Handles valueChanged signal from the trigger level slider.
@@ -599,11 +747,11 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) { @@ -599,11 +747,11 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) {
599 scope->voltage[channel].trigger = value; 747 scope->voltage[channel].trigger = value;
600 748
601 if (mainSliders.triggerLevelSlider->value(channel) != value) { 749 if (mainSliders.triggerLevelSlider->value(channel) != value) {
602 - QSignalBlocker blocker(mainSliders.triggerLevelSlider); 750 + const QSignalBlocker blocker(mainSliders.triggerLevelSlider);
603 mainSliders.triggerLevelSlider->setValue(channel, value); 751 mainSliders.triggerLevelSlider->setValue(channel, value);
604 } 752 }
605 if (zoomSliders.triggerLevelSlider->value(channel) != value) { 753 if (zoomSliders.triggerLevelSlider->value(channel) != value) {
606 - QSignalBlocker blocker(zoomSliders.triggerLevelSlider); 754 + const QSignalBlocker blocker(zoomSliders.triggerLevelSlider);
607 zoomSliders.triggerLevelSlider->setValue(channel, value); 755 zoomSliders.triggerLevelSlider->setValue(channel, value);
608 } 756 }
609 757
@@ -616,10 +764,7 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) { @@ -616,10 +764,7 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) {
616 /// \param marker The index of the slider. 764 /// \param marker The index of the slider.
617 /// \param value The new marker position. 765 /// \param value The new marker position.
618 void DsoWidget::updateMarker(int marker, double value) { 766 void DsoWidget::updateMarker(int marker, double value) {
619 - scope->horizontal.marker[marker] = value;  
620 - 767 + scope->setMarker(marker, value);
621 adaptTriggerPositionSlider(); 768 adaptTriggerPositionSlider();
622 updateMarkerDetails(); 769 updateMarkerDetails();
623 -  
624 - emit markerChanged(marker, value);  
625 } 770 }
openhantek/src/dsowidget.h
@@ -15,12 +15,15 @@ @@ -15,12 +15,15 @@
15 class SpectrumGenerator; 15 class SpectrumGenerator;
16 struct DsoSettingsScope; 16 struct DsoSettingsScope;
17 struct DsoSettingsView; 17 struct DsoSettingsView;
  18 +class DataGrid;
18 19
19 /// \brief The widget for the oszilloscope-screen 20 /// \brief The widget for the oszilloscope-screen
20 /// This widget contains the scopes and all level sliders. 21 /// This widget contains the scopes and all level sliders.
21 class DsoWidget : public QWidget { 22 class DsoWidget : public QWidget {
22 Q_OBJECT 23 Q_OBJECT
23 24
  25 + public:
  26 +
24 struct Sliders { 27 struct Sliders {
25 LevelSlider *offsetSlider; ///< The sliders for the graph offsets 28 LevelSlider *offsetSlider; ///< The sliders for the graph offsets
26 LevelSlider *triggerPositionSlider; ///< The slider for the pretrigger 29 LevelSlider *triggerPositionSlider; ///< The slider for the pretrigger
@@ -28,7 +31,6 @@ class DsoWidget : public QWidget { @@ -28,7 +31,6 @@ class DsoWidget : public QWidget {
28 LevelSlider *markerSlider; ///< The sliders for the markers 31 LevelSlider *markerSlider; ///< The sliders for the markers
29 }; 32 };
30 33
31 - public:  
32 /// \brief Initializes the components of the oszilloscope-screen. 34 /// \brief Initializes the components of the oszilloscope-screen.
33 /// \param settings The settings object containing the oscilloscope settings. 35 /// \param settings The settings object containing the oscilloscope settings.
34 /// \param dataAnalyzer The data analyzer that should be used as data source. 36 /// \param dataAnalyzer The data analyzer that should be used as data source.
@@ -50,6 +52,9 @@ class DsoWidget : public QWidget { @@ -50,6 +52,9 @@ class DsoWidget : public QWidget {
50 void updateTriggerDetails(); 52 void updateTriggerDetails();
51 void updateVoltageDetails(ChannelID channel); 53 void updateVoltageDetails(ChannelID channel);
52 54
  55 + double mainToZoom(double position) const;
  56 + double zoomToMain(double position) const;
  57 +
53 Sliders mainSliders; 58 Sliders mainSliders;
54 Sliders zoomSliders; 59 Sliders zoomSliders;
55 60
@@ -79,6 +84,8 @@ class DsoWidget : public QWidget { @@ -79,6 +84,8 @@ class DsoWidget : public QWidget {
79 std::vector<QLabel *> measurementAmplitudeLabel; ///< Amplitude of the signal (V) 84 std::vector<QLabel *> measurementAmplitudeLabel; ///< Amplitude of the signal (V)
80 std::vector<QLabel *> measurementFrequencyLabel; ///< Frequency of the signal (Hz) 85 std::vector<QLabel *> measurementFrequencyLabel; ///< Frequency of the signal (Hz)
81 86
  87 + DataGrid *cursorDataGrid;
  88 +
82 DsoSettingsScope* scope; 89 DsoSettingsScope* scope;
83 DsoSettingsView* view; 90 DsoSettingsView* view;
84 const Dso::ControlSpecification* spec; 91 const Dso::ControlSpecification* spec;
@@ -113,11 +120,12 @@ class DsoWidget : public QWidget { @@ -113,11 +120,12 @@ class DsoWidget : public QWidget {
113 120
114 // Scope control 121 // Scope control
115 void updateZoom(bool enabled); 122 void updateZoom(bool enabled);
  123 + void updateCursorGrid(bool enabled);
116 124
117 private slots: 125 private slots:
118 // Sliders 126 // Sliders
119 void updateOffset(ChannelID channel, double value); 127 void updateOffset(ChannelID channel, double value);
120 - void updateTriggerPosition(int index, double value); 128 + void updateTriggerPosition(int index, double value, bool mainView = true);
121 void updateTriggerLevel(ChannelID channel, double value); 129 void updateTriggerLevel(ChannelID channel, double value);
122 void updateMarker(int marker, double value); 130 void updateMarker(int marker, double value);
123 131
@@ -126,5 +134,4 @@ class DsoWidget : public QWidget { @@ -126,5 +134,4 @@ class DsoWidget : public QWidget {
126 void offsetChanged(ChannelID channel, double value); ///< A graph offset has been changed 134 void offsetChanged(ChannelID channel, double value); ///< A graph offset has been changed
127 void triggerPositionChanged(double value); ///< The pretrigger has been changed 135 void triggerPositionChanged(double value); ///< The pretrigger has been changed
128 void triggerLevelChanged(ChannelID channel, double value); ///< A trigger level has been changed 136 void triggerLevelChanged(ChannelID channel, double value); ///< A trigger level has been changed
129 - void markerChanged(unsigned int marker, double value); ///< A marker position has been changed  
130 }; 137 };
openhantek/src/exporting/legacyexportdrawer.cpp
@@ -115,10 +115,12 @@ bool LegacyExportDrawer::exportSamples(const PPresult *result, QPaintDevice* pai @@ -115,10 +115,12 @@ bool LegacyExportDrawer::exportSamples(const PPresult *result, QPaintDevice* pai
115 painter.setPen(colorValues->text); 115 painter.setPen(colorValues->text);
116 116
117 // Calculate variables needed for zoomed scope 117 // Calculate variables needed for zoomed scope
118 - double divs = fabs(settings->scope.horizontal.marker[1] - settings->scope.horizontal.marker[0]); 118 + double m1 = settings->scope.getMarker(0);
  119 + double m2 = settings->scope.getMarker(1);
  120 + double divs = fabs(m2 - m1);
119 double time = divs * settings->scope.horizontal.timebase; 121 double time = divs * settings->scope.horizontal.timebase;
120 double zoomFactor = DIVS_TIME / divs; 122 double zoomFactor = DIVS_TIME / divs;
121 - double zoomOffset = (settings->scope.horizontal.marker[0] + settings->scope.horizontal.marker[1]) / 2; 123 + double zoomOffset = (m1 + m2) / 2;
122 124
123 if (settings->view.zoom) { 125 if (settings->view.zoom) {
124 scopeHeight = (double)(paintDevice->height() - (channelCount + 5) * lineHeight) / 2; 126 scopeHeight = (double)(paintDevice->height() - (channelCount + 5) * lineHeight) / 2;
openhantek/src/glscope.cpp
@@ -54,50 +54,106 @@ void GlScope::fixOpenGLversion(QSurfaceFormat::RenderableType t) { @@ -54,50 +54,106 @@ void GlScope::fixOpenGLversion(QSurfaceFormat::RenderableType t) {
54 54
55 GlScope::GlScope(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent) 55 GlScope::GlScope(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent)
56 : QOpenGLWidget(parent), scope(scope), view(view) { 56 : QOpenGLWidget(parent), scope(scope), view(view) {
57 - vaMarker.resize(MARKER_COUNT); 57 + cursorInfo.clear();
  58 + cursorInfo.push_back(&scope->horizontal.cursor);
  59 + selectedCursor = 0;
  60 + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
  61 + cursorInfo.push_back(&scope->voltage[channel].cursor);
  62 + }
  63 + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel) {
  64 + cursorInfo.push_back(&scope->spectrum[channel].cursor);
  65 + }
  66 + vaMarker.resize(cursorInfo.size());
58 } 67 }
59 68
60 GlScope::~GlScope() {/* virtual destructor necessary */} 69 GlScope::~GlScope() {/* virtual destructor necessary */}
61 70
  71 +QPointF GlScope::eventToPosition(QMouseEvent *event) {
  72 + QPointF position((double)(event->x() - width() / 2) * DIVS_TIME / (double)width(),
  73 + (double)(height() / 2 - event->y()) * DIVS_VOLTAGE / (double)height());
  74 + if (zoomed) {
  75 + double m1 = scope->getMarker(0);
  76 + double m2 = scope->getMarker(1);
  77 + if (m1 > m2) std::swap(m1, m2);
  78 + position.setX(m1 + (0.5 + (position.x() / DIVS_TIME)) * (m2 - m1));
  79 + }
  80 + return position;
  81 +}
  82 +
62 void GlScope::mousePressEvent(QMouseEvent *event) { 83 void GlScope::mousePressEvent(QMouseEvent *event) {
63 - if (!zoomed && event->button() == Qt::LeftButton) {  
64 - double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width();  
65 - double distance = DIVS_TIME; 84 + if (!(zoomed && selectedCursor == 0) && event->button() == Qt::LeftButton) {
  85 + QPointF position = eventToPosition(event);
66 selectedMarker = NO_MARKER; 86 selectedMarker = NO_MARKER;
  87 + DsoSettingsScopeCursor *cursor = cursorInfo[selectedCursor];
67 // Capture nearest marker located within snap area (+/- 1% of full scale). 88 // Capture nearest marker located within snap area (+/- 1% of full scale).
68 - for (unsigned marker = 0; marker < MARKER_COUNT; ++marker) {  
69 - if (!scope->horizontal.marker_visible[marker]) continue;  
70 - if (fabs(scope->horizontal.marker[marker] - position) < std::min(distance, DIVS_TIME / 100.0)) {  
71 - distance = fabs(scope->horizontal.marker[marker] - position);  
72 - selectedMarker = marker; 89 + double dX0 = fabs(cursor->pos[0].x() - position.x());
  90 + double dX1 = fabs(cursor->pos[1].x() - position.x());
  91 + double dY0 = fabs(cursor->pos[0].y() - position.y());
  92 + double dY1 = fabs(cursor->pos[1].y() - position.y());
  93 +
  94 + switch (cursor->shape) {
  95 + case DsoSettingsScopeCursor::RECTANGULAR:
  96 + if (std::min(dX0, dX1) < 1.0 / DIVS_SUB && std::min(dY0, dY1) < 1.0 / DIVS_SUB) {
  97 + // Do we need to swap Y-coords?
  98 + if ((dX0 < dX1 && dY0 > dY1) || (dX0 > dX1 && dY0 < dY1)) {
  99 + std::swap(cursor->pos[0].ry(), cursor->pos[1].ry());
  100 + }
  101 + selectedMarker = (dX0 < dX1) ? 0 : 1;
73 } 102 }
  103 + break;
  104 + case DsoSettingsScopeCursor::VERTICAL:
  105 + if (dX0 < dX1) {
  106 + if (dX0 < 1.0 / DIVS_SUB) selectedMarker = 0;
  107 + } else {
  108 + if (dX1 < 1.0 / DIVS_SUB) selectedMarker = 1;
  109 + }
  110 + break;
  111 + case DsoSettingsScopeCursor::HORIZONTAL:
  112 + if (dY0 < dY1) {
  113 + if (dY0 < 1.0 / DIVS_SUB) selectedMarker = 0;
  114 + } else {
  115 + if (dY1 < 1.0 / DIVS_SUB) selectedMarker = 1;
  116 + }
  117 + break;
  118 + case DsoSettingsScopeCursor::NONE:
  119 + break;
  120 + default:
  121 + break;
  122 + }
  123 + if (selectedMarker != NO_MARKER) {
  124 + cursorInfo[selectedCursor]->pos[selectedMarker] = position;
  125 + if (selectedCursor == 0) emit markerMoved(selectedCursor, selectedMarker);
74 } 126 }
75 - if (selectedMarker != NO_MARKER) { emit markerMoved(selectedMarker, position); }  
76 } 127 }
77 event->accept(); 128 event->accept();
78 } 129 }
79 130
80 void GlScope::mouseMoveEvent(QMouseEvent *event) { 131 void GlScope::mouseMoveEvent(QMouseEvent *event) {
81 - if (!zoomed && (event->buttons() & Qt::LeftButton) != 0) {  
82 - double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width(); 132 + if (!(zoomed && selectedCursor == 0) && (event->buttons() & Qt::LeftButton) != 0) {
  133 + QPointF position = eventToPosition(event);
83 if (selectedMarker == NO_MARKER) { 134 if (selectedMarker == NO_MARKER) {
84 // User started draging outside the snap area of any marker: 135 // User started draging outside the snap area of any marker:
85 // move all markers to current position and select last marker in the array. 136 // move all markers to current position and select last marker in the array.
86 for (unsigned marker = 0; marker < MARKER_COUNT; ++marker) { 137 for (unsigned marker = 0; marker < MARKER_COUNT; ++marker) {
87 - emit markerMoved(marker, position); 138 + cursorInfo[selectedCursor]->pos[marker] = position;
  139 + emit markerMoved(selectedCursor, marker);
88 selectedMarker = marker; 140 selectedMarker = marker;
89 } 141 }
90 } else if (selectedMarker < MARKER_COUNT) { 142 } else if (selectedMarker < MARKER_COUNT) {
91 - emit markerMoved(selectedMarker, position); 143 + cursorInfo[selectedCursor]->pos[selectedMarker] = position;
  144 + emit markerMoved(selectedCursor, selectedMarker);
92 } 145 }
93 } 146 }
94 event->accept(); 147 event->accept();
95 } 148 }
96 149
97 void GlScope::mouseReleaseEvent(QMouseEvent *event) { 150 void GlScope::mouseReleaseEvent(QMouseEvent *event) {
98 - if (!zoomed && event->button() == Qt::LeftButton) {  
99 - double position = (double)(event->x() - width() / 2) * DIVS_TIME / (double)width();  
100 - if (selectedMarker < MARKER_COUNT) { emit markerMoved(selectedMarker, position); } 151 + if (!(zoomed && selectedCursor == 0) && event->button() == Qt::LeftButton) {
  152 + QPointF position = eventToPosition(event);
  153 + if (selectedMarker < MARKER_COUNT) {
  154 + cursorInfo[selectedCursor]->pos[selectedMarker] = position;
  155 + emit markerMoved(selectedCursor, selectedMarker);
  156 + }
101 selectedMarker = NO_MARKER; 157 selectedMarker = NO_MARKER;
102 } 158 }
103 event->accept(); 159 event->accept();
@@ -209,12 +265,11 @@ void GlScope::initializeGL() { @@ -209,12 +265,11 @@ void GlScope::initializeGL() {
209 m_marker.create(); 265 m_marker.create();
210 m_marker.bind(); 266 m_marker.bind();
211 m_marker.setUsagePattern(QOpenGLBuffer::StaticDraw); 267 m_marker.setUsagePattern(QOpenGLBuffer::StaticDraw);
212 - m_marker.allocate(int(vaMarker.size() * sizeof(Line))); 268 + m_marker.allocate(int(vaMarker.size() * sizeof(Vertices)));
213 program->enableAttributeArray(vertexLocation); 269 program->enableAttributeArray(vertexLocation);
214 program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 0); 270 program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 0);
215 } 271 }
216 -  
217 - markerUpdated(); 272 + updateCursor();
218 273
219 m_program = std::move(program); 274 m_program = std::move(program);
220 shaderCompileSuccess = true; 275 shaderCompileSuccess = true;
@@ -239,18 +294,65 @@ void GlScope::showData(std::shared_ptr&lt;PPresult&gt; data) { @@ -239,18 +294,65 @@ void GlScope::showData(std::shared_ptr&lt;PPresult&gt; data) {
239 update(); 294 update();
240 } 295 }
241 296
242 -void GlScope::markerUpdated() {  
243 -  
244 - for (unsigned marker = 0; marker < vaMarker.size(); ++marker) {  
245 - if (!scope->horizontal.marker_visible[marker]) continue;  
246 - vaMarker[marker] = {QVector3D((GLfloat)scope->horizontal.marker[marker], -DIVS_VOLTAGE, 0.0f),  
247 - QVector3D((GLfloat)scope->horizontal.marker[marker], DIVS_VOLTAGE, 0.0f)}; 297 +void GlScope::generateVertices(unsigned marker, const DsoSettingsScopeCursor &cursor) {
  298 + const float Z_ORDER = 1.0f;
  299 + switch (cursor.shape) {
  300 + case DsoSettingsScopeCursor::NONE:
  301 + vaMarker[marker] = {
  302 + QVector3D(-DIVS_TIME, -DIVS_VOLTAGE, Z_ORDER),
  303 + QVector3D(-DIVS_TIME, DIVS_VOLTAGE, Z_ORDER),
  304 + QVector3D( DIVS_TIME, DIVS_VOLTAGE, Z_ORDER),
  305 + QVector3D( DIVS_TIME, -DIVS_VOLTAGE, Z_ORDER)
  306 + };
  307 + break;
  308 + case DsoSettingsScopeCursor::VERTICAL:
  309 + vaMarker[marker] = {
  310 + QVector3D(cursor.pos[0].x(), -DIVS_VOLTAGE, Z_ORDER),
  311 + QVector3D(cursor.pos[0].x(), DIVS_VOLTAGE, Z_ORDER),
  312 + QVector3D(cursor.pos[1].x(), DIVS_VOLTAGE, Z_ORDER),
  313 + QVector3D(cursor.pos[1].x(), -DIVS_VOLTAGE, Z_ORDER)
  314 + };
  315 + break;
  316 + case DsoSettingsScopeCursor::HORIZONTAL:
  317 + vaMarker[marker] = {
  318 + QVector3D(-DIVS_TIME, cursor.pos[0].y(), Z_ORDER),
  319 + QVector3D( DIVS_TIME, cursor.pos[0].y(), Z_ORDER),
  320 + QVector3D( DIVS_TIME, cursor.pos[1].y(), Z_ORDER),
  321 + QVector3D(-DIVS_TIME, cursor.pos[1].y(), Z_ORDER)
  322 + };
  323 + break;
  324 + case DsoSettingsScopeCursor::RECTANGULAR:
  325 + if ((cursor.pos[1].x() - cursor.pos[0].x()) * (cursor.pos[1].y() - cursor.pos[0].y()) > 0.0) {
  326 + vaMarker[marker] = {
  327 + QVector3D(cursor.pos[0].x(), cursor.pos[0].y(), Z_ORDER),
  328 + QVector3D(cursor.pos[1].x(), cursor.pos[0].y(), Z_ORDER),
  329 + QVector3D(cursor.pos[1].x(), cursor.pos[1].y(), Z_ORDER),
  330 + QVector3D(cursor.pos[0].x(), cursor.pos[1].y(), Z_ORDER)
  331 + };
  332 + } else {
  333 + vaMarker[marker] = {
  334 + QVector3D(cursor.pos[0].x(), cursor.pos[0].y(), Z_ORDER),
  335 + QVector3D(cursor.pos[0].x(), cursor.pos[1].y(), Z_ORDER),
  336 + QVector3D(cursor.pos[1].x(), cursor.pos[1].y(), Z_ORDER),
  337 + QVector3D(cursor.pos[1].x(), cursor.pos[0].y(), Z_ORDER)
  338 + };
  339 + }
  340 + break;
  341 + default:
  342 + break;
248 } 343 }
  344 +}
249 345
  346 +void GlScope::updateCursor(unsigned index) {
  347 + if (index > 0) {
  348 + generateVertices(index, *cursorInfo[index]);
  349 + } else for (index = 0; index < cursorInfo.size(); ++index) {
  350 + generateVertices(index, *cursorInfo[index]);
  351 + }
250 // Write coordinates to GPU 352 // Write coordinates to GPU
251 makeCurrent(); 353 makeCurrent();
252 m_marker.bind(); 354 m_marker.bind();
253 - m_marker.write(0, vaMarker.data(), vaMarker.size() * sizeof(Line)); 355 + m_marker.write(0, vaMarker.data(), vaMarker.size() * sizeof(Vertices));
254 } 356 }
255 357
256 void GlScope::paintGL() { 358 void GlScope::paintGL() {
@@ -268,12 +370,13 @@ void GlScope::paintGL() { @@ -268,12 +370,13 @@ void GlScope::paintGL() {
268 // Apply zoom settings via matrix transformation 370 // Apply zoom settings via matrix transformation
269 if (zoomed) { 371 if (zoomed) {
270 QMatrix4x4 m; 372 QMatrix4x4 m;
271 - m.scale(QVector3D(DIVS_TIME / (GLfloat)fabs(scope->horizontal.marker[1] - scope->horizontal.marker[0]), 1.0f,  
272 - 1.0f));  
273 - m.translate((GLfloat) - (scope->horizontal.marker[0] + scope->horizontal.marker[1]) / 2, 0.0f, 0.0f); 373 + m.scale(QVector3D(DIVS_TIME / (GLfloat)fabs(scope->getMarker(1) - scope->getMarker(0)), 1.0f, 1.0f));
  374 + m.translate((GLfloat) - (scope->getMarker(0) + scope->getMarker(1)) / 2, 0.0f, 0.0f);
274 m_program->setUniformValue(matrixLocation, pmvMatrix * m); 375 m_program->setUniformValue(matrixLocation, pmvMatrix * m);
275 } 376 }
276 377
  378 + drawMarkers();
  379 +
277 unsigned historyIndex = 0; 380 unsigned historyIndex = 0;
278 for (Graph &graph : m_GraphHistory) { 381 for (Graph &graph : m_GraphHistory) {
279 for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { 382 for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
@@ -287,8 +390,6 @@ void GlScope::paintGL() { @@ -287,8 +390,6 @@ void GlScope::paintGL() {
287 390
288 if (zoomed) { m_program->setUniformValue(matrixLocation, pmvMatrix); } 391 if (zoomed) { m_program->setUniformValue(matrixLocation, pmvMatrix); }
289 392
290 - if (!this->zoomed) drawMarkers();  
291 -  
292 drawGrid(); 393 drawGrid();
293 m_program->release(); 394 m_program->release();
294 } 395 }
@@ -432,21 +533,38 @@ void GlScope::drawGrid() { @@ -432,21 +533,38 @@ void GlScope::drawGrid() {
432 m_vaoGrid[2].release(); 533 m_vaoGrid[2].release();
433 } 534 }
434 535
  536 +void GlScope::drawVertices(QOpenGLFunctions *gl, unsigned marker, QColor color) {
  537 + m_program->setUniformValue(colorLocation, (marker == selectedCursor) ? color : color.darker());
  538 + gl->glDrawArrays(GL_LINE_LOOP, GLint(marker * VERTICES_ARRAY_SIZE), VERTICES_ARRAY_SIZE);
  539 + if (cursorInfo[marker]->shape == DsoSettingsScopeCursor::RECTANGULAR) {
  540 + color.setAlphaF(0.25);
  541 + m_program->setUniformValue(colorLocation, color.darker());
  542 + gl->glDrawArrays(GL_TRIANGLE_FAN, GLint(marker * VERTICES_ARRAY_SIZE), VERTICES_ARRAY_SIZE);
  543 + }
  544 +}
  545 +
435 void GlScope::drawMarkers() { 546 void GlScope::drawMarkers() {
436 auto *gl = context()->functions(); 547 auto *gl = context()->functions();
437 - QColor trColor = view->screen.markers;  
438 - m_program->setUniformValue(colorLocation, trColor);  
439 548
440 m_vaoMarker.bind(); 549 m_vaoMarker.bind();
441 550
442 - // Draw all  
443 - gl->glLineWidth(1);  
444 - gl->glDrawArrays(GL_LINES, 0, (GLsizei)vaMarker.size() * 2); 551 + unsigned marker = 0;
  552 + drawVertices(gl, marker, view->screen.markers);
  553 + ++marker;
445 554
446 - // Draw selected  
447 - if (selectedMarker != NO_MARKER) {  
448 - gl->glLineWidth(3);  
449 - gl->glDrawArrays(GL_LINES, selectedMarker * 2, (GLsizei)2); 555 + if (view->cursorsVisible) {
  556 + gl->glDepthMask(GL_FALSE);
  557 + for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel, ++marker) {
  558 + if (scope->voltage[channel].used) {
  559 + drawVertices(gl, marker, view->screen.voltage[channel]);
  560 + }
  561 + }
  562 + for (ChannelID channel = 0; channel < scope->spectrum.size(); ++channel, ++marker) {
  563 + if (scope->spectrum[channel].used) {
  564 + drawVertices(gl, marker, view->screen.spectrum[channel]);
  565 + }
  566 + }
  567 + gl->glDepthMask(GL_TRUE);
450 } 568 }
451 569
452 m_vaoMarker.release(); 570 m_vaoMarker.release();
openhantek/src/glscope.h
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 18
19 struct DsoSettingsView; 19 struct DsoSettingsView;
20 struct DsoSettingsScope; 20 struct DsoSettingsScope;
  21 +struct DsoSettingsScopeCursor;
21 class PPresult; 22 class PPresult;
22 23
23 /// \brief OpenGL accelerated widget that displays the oscilloscope screen. 24 /// \brief OpenGL accelerated widget that displays the oscilloscope screen.
@@ -40,7 +41,8 @@ class GlScope : public QOpenGLWidget { @@ -40,7 +41,8 @@ class GlScope : public QOpenGLWidget {
40 * @param data 41 * @param data
41 */ 42 */
42 void showData(std::shared_ptr<PPresult> data); 43 void showData(std::shared_ptr<PPresult> data);
43 - void markerUpdated(); 44 + void updateCursor(unsigned index = 0);
  45 + void cursorSelected(unsigned index) { selectedCursor = index; updateCursor(index); }
44 46
45 protected: 47 protected:
46 /// \brief Initializes the scope widget. 48 /// \brief Initializes the scope widget.
@@ -70,11 +72,14 @@ class GlScope : public QOpenGLWidget { @@ -70,11 +72,14 @@ class GlScope : public QOpenGLWidget {
70 void drawGrid(); 72 void drawGrid();
71 /// Draw vertical lines at marker positions 73 /// Draw vertical lines at marker positions
72 void drawMarkers(); 74 void drawMarkers();
  75 + void generateVertices(unsigned marker, const DsoSettingsScopeCursor &cursor);
  76 + void drawVertices(QOpenGLFunctions *gl, unsigned marker, QColor color);
73 77
74 void drawVoltageChannelGraph(ChannelID channel, Graph &graph, int historyIndex); 78 void drawVoltageChannelGraph(ChannelID channel, Graph &graph, int historyIndex);
75 void drawSpectrumChannelGraph(ChannelID channel, Graph &graph, int historyIndex); 79 void drawSpectrumChannelGraph(ChannelID channel, Graph &graph, int historyIndex);
  80 + QPointF eventToPosition(QMouseEvent *event);
76 signals: 81 signals:
77 - void markerMoved(unsigned marker, double position); 82 + void markerMoved(unsigned cursorIndex, unsigned marker);
78 83
79 private: 84 private:
80 // User settings 85 // User settings
@@ -85,16 +90,20 @@ class GlScope : public QOpenGLWidget { @@ -85,16 +90,20 @@ class GlScope : public QOpenGLWidget {
85 // Marker 90 // Marker
86 const unsigned NO_MARKER = UINT_MAX; 91 const unsigned NO_MARKER = UINT_MAX;
87 #pragma pack(push, 1) 92 #pragma pack(push, 1)
88 - struct Line {  
89 - QVector3D x;  
90 - QVector3D y; 93 + struct Vertices {
  94 + QVector3D a, b, c, d;
91 }; 95 };
92 #pragma pack(pop) 96 #pragma pack(pop)
93 - std::vector<Line> vaMarker; 97 + const unsigned VERTICES_ARRAY_SIZE = sizeof(Vertices) / sizeof(QVector3D);
  98 + std::vector<Vertices> vaMarker;
94 unsigned selectedMarker = NO_MARKER; 99 unsigned selectedMarker = NO_MARKER;
95 QOpenGLBuffer m_marker; 100 QOpenGLBuffer m_marker;
96 QOpenGLVertexArrayObject m_vaoMarker; 101 QOpenGLVertexArrayObject m_vaoMarker;
97 102
  103 + // Cursors
  104 + std::vector<DsoSettingsScopeCursor *> cursorInfo;
  105 + unsigned selectedCursor = 0;
  106 +
98 // Grid 107 // Grid
99 QOpenGLBuffer m_grid; 108 QOpenGLBuffer m_grid;
100 QOpenGLVertexArrayObject m_vaoGrid[3]; 109 QOpenGLVertexArrayObject m_vaoGrid[3];
openhantek/src/mainwindow.cpp
@@ -39,6 +39,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo @@ -39,6 +39,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo
39 ui->actionManualCommand->setIcon(iconFont->icon(fa::edit)); 39 ui->actionManualCommand->setIcon(iconFont->icon(fa::edit));
40 ui->actionDigital_phosphor->setIcon(QIcon(":/images/digitalphosphor.svg")); 40 ui->actionDigital_phosphor->setIcon(QIcon(":/images/digitalphosphor.svg"));
41 ui->actionZoom->setIcon(iconFont->icon(fa::crop)); 41 ui->actionZoom->setIcon(iconFont->icon(fa::crop));
  42 + ui->actionCursors->setIcon(iconFont->icon(fa::crosshairs));
42 43
43 // Window title 44 // Window title
44 setWindowIcon(QIcon(":openhantek.png")); 45 setWindowIcon(QIcon(":openhantek.png"));
@@ -160,6 +161,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo @@ -160,6 +161,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo
160 auto usedChanged = [this, dsoControl, spec](ChannelID channel, bool used) { 161 auto usedChanged = [this, dsoControl, spec](ChannelID channel, bool used) {
161 if (channel >= (unsigned int)mSettings->scope.voltage.size()) return; 162 if (channel >= (unsigned int)mSettings->scope.voltage.size()) return;
162 163
  164 +// if (!used) dsoWidget->
163 bool mathUsed = mSettings->scope.anyUsed(spec->channels); 165 bool mathUsed = mSettings->scope.anyUsed(spec->channels);
164 166
165 // Normal channel, check if voltage/spectrum or math channel is used 167 // Normal channel, check if voltage/spectrum or math channel is used
@@ -267,6 +269,18 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo @@ -267,6 +269,18 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo
267 }); 269 });
268 ui->actionZoom->setChecked(mSettings->view.zoom); 270 ui->actionZoom->setChecked(mSettings->view.zoom);
269 271
  272 + connect(ui->actionCursors, &QAction::toggled, [this](bool enabled) {
  273 + mSettings->view.cursorsVisible = enabled;
  274 +
  275 + if (mSettings->view.cursorsVisible)
  276 + this->ui->actionCursors->setStatusTip(tr("Hide measurements"));
  277 + else
  278 + this->ui->actionCursors->setStatusTip(tr("Show measurements"));
  279 +
  280 + this->dsoWidget->updateCursorGrid(enabled);
  281 + });
  282 + ui->actionCursors->setChecked(mSettings->view.cursorsVisible);
  283 +
270 connect(ui->actionAbout, &QAction::triggered, [this]() { 284 connect(ui->actionAbout, &QAction::triggered, [this]() {
271 QMessageBox::about( 285 QMessageBox::about(
272 this, tr("About OpenHantek %1").arg(VERSION), 286 this, tr("About OpenHantek %1").arg(VERSION),
openhantek/src/mainwindow.ui
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <x>0</x> 20 <x>0</x>
21 <y>0</y> 21 <y>0</y>
22 <width>800</width> 22 <width>800</width>
23 - <height>25</height> 23 + <height>28</height>
24 </rect> 24 </rect>
25 </property> 25 </property>
26 <widget class="QMenu" name="menuFile"> 26 <widget class="QMenu" name="menuFile">
@@ -39,6 +39,8 @@ @@ -39,6 +39,8 @@
39 </property> 39 </property>
40 <addaction name="actionDigital_phosphor"/> 40 <addaction name="actionDigital_phosphor"/>
41 <addaction name="actionZoom"/> 41 <addaction name="actionZoom"/>
  42 + <addaction name="actionCursors"/>
  43 + <addaction name="separator"/>
42 <addaction name="actionManualCommand"/> 44 <addaction name="actionManualCommand"/>
43 </widget> 45 </widget>
44 <widget class="QMenu" name="menuOscilloscope"> 46 <widget class="QMenu" name="menuOscilloscope">
@@ -84,6 +86,7 @@ @@ -84,6 +86,7 @@
84 <addaction name="separator"/> 86 <addaction name="separator"/>
85 <addaction name="actionDigital_phosphor"/> 87 <addaction name="actionDigital_phosphor"/>
86 <addaction name="actionZoom"/> 88 <addaction name="actionZoom"/>
  89 + <addaction name="actionCursors"/>
87 <addaction name="separator"/> 90 <addaction name="separator"/>
88 </widget> 91 </widget>
89 <action name="actionOpen"> 92 <action name="actionOpen">
@@ -167,6 +170,14 @@ @@ -167,6 +170,14 @@
167 <string>Manual command</string> 170 <string>Manual command</string>
168 </property> 171 </property>
169 </action> 172 </action>
  173 + <action name="actionCursors">
  174 + <property name="checkable">
  175 + <bool>true</bool>
  176 + </property>
  177 + <property name="text">
  178 + <string>Cursors</string>
  179 + </property>
  180 + </action>
170 </widget> 181 </widget>
171 <resources/> 182 <resources/>
172 <connections/> 183 <connections/>
openhantek/src/scopesettings.h
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 #pragma once 3 #pragma once
4 4
5 #include <QString> 5 #include <QString>
  6 +#include <QPointF>
6 7
7 #include "hantekdso/controlspecification.h" 8 #include "hantekdso/controlspecification.h"
8 #include "hantekdso/enums.h" 9 #include "hantekdso/enums.h"
@@ -12,12 +13,22 @@ @@ -12,12 +13,22 @@
12 #define MARKER_COUNT 2 ///< Number of markers 13 #define MARKER_COUNT 2 ///< Number of markers
13 #define MARKER_STEP (DIVS_TIME / 100.0) 14 #define MARKER_STEP (DIVS_TIME / 100.0)
14 15
  16 +/// \brief Holds the cursor parameters
  17 +struct DsoSettingsScopeCursor {
  18 + enum CursorShape {
  19 + NONE,
  20 + HORIZONTAL,
  21 + VERTICAL,
  22 + RECTANGULAR
  23 + } shape = NONE;
  24 + QPointF pos[MARKER_COUNT] = {{-1.0, -1.0}, {1.0, 1.0}}; ///< Position in div
  25 +};
  26 +
15 /// \brief Holds the settings for the horizontal axis. 27 /// \brief Holds the settings for the horizontal axis.
16 struct DsoSettingsScopeHorizontal { 28 struct DsoSettingsScopeHorizontal {
17 Dso::GraphFormat format = Dso::GraphFormat::TY; ///< Graph drawing mode of the scope 29 Dso::GraphFormat format = Dso::GraphFormat::TY; ///< Graph drawing mode of the scope
18 double frequencybase = 1e3; ///< Frequencybase in Hz/div 30 double frequencybase = 1e3; ///< Frequencybase in Hz/div
19 - double marker[MARKER_COUNT] = {-1.0, 1.0}; ///< Marker positions in div  
20 - bool marker_visible[MARKER_COUNT] = {true, true}; 31 + DsoSettingsScopeCursor cursor;
21 32
22 unsigned int recordLength = 0; ///< Sample count 33 unsigned int recordLength = 0; ///< Sample count
23 34
@@ -39,25 +50,27 @@ struct DsoSettingsScopeTrigger { @@ -39,25 +50,27 @@ struct DsoSettingsScopeTrigger {
39 unsigned swTriggerSampleSet = 11; ///< Software trigger, sample set 50 unsigned swTriggerSampleSet = 11; ///< Software trigger, sample set
40 }; 51 };
41 52
  53 +/// \brief Base for DsoSettingsScopeSpectrum and DsoSettingsScopeVoltage
  54 +struct DsoSettingsScopeChannel {
  55 + QString name; ///< Name of this channel
  56 + bool used = false; ///< true if the channel is turned on
  57 + DsoSettingsScopeCursor cursor;
  58 +};
  59 +
42 /// \brief Holds the settings for the spectrum analysis. 60 /// \brief Holds the settings for the spectrum analysis.
43 -struct DsoSettingsScopeSpectrum {  
44 - ChannelID channel;  
45 - double magnitude = 20.0; ///< The vertical resolution in dB/div  
46 - QString name; ///< Name of this channel 61 +struct DsoSettingsScopeSpectrum : public DsoSettingsScopeChannel {
47 double offset = 0.0; ///< Vertical offset in divs 62 double offset = 0.0; ///< Vertical offset in divs
48 - bool used = false; ///< true if the spectrum is turned on 63 + double magnitude = 20.0; ///< The vertical resolution in dB/div
49 }; 64 };
50 65
51 /// \brief Holds the settings for the normal voltage graphs. 66 /// \brief Holds the settings for the normal voltage graphs.
52 /// TODO Use ControlSettingsVoltage 67 /// TODO Use ControlSettingsVoltage
53 -struct DsoSettingsScopeVoltage { 68 +struct DsoSettingsScopeVoltage : public DsoSettingsScopeChannel {
54 double offset = 0.0; ///< Vertical offset in divs 69 double offset = 0.0; ///< Vertical offset in divs
55 double trigger = 0.0; ///< Trigger level in V 70 double trigger = 0.0; ///< Trigger level in V
56 unsigned gainStepIndex = 6; ///< The vertical resolution in V/div (default = 1.0) 71 unsigned gainStepIndex = 6; ///< The vertical resolution in V/div (default = 1.0)
57 unsigned couplingOrMathIndex = 0; ///< Different index: coupling for real- and mode for math-channels 72 unsigned couplingOrMathIndex = 0; ///< Different index: coupling for real- and mode for math-channels
58 - QString name; ///< Name of this channel  
59 bool inverted = false; ///< true if the channel is inverted (mirrored on cross-axis) 73 bool inverted = false; ///< true if the channel is inverted (mirrored on cross-axis)
60 - bool used = false; ///< true if this channel is enabled  
61 }; 74 };
62 75
63 /// \brief Holds the settings for the oscilloscope. 76 /// \brief Holds the settings for the oscilloscope.
@@ -77,4 +90,11 @@ struct DsoSettingsScope { @@ -77,4 +90,11 @@ struct DsoSettingsScope {
77 } 90 }
78 // Channels, including math channels 91 // Channels, including math channels
79 unsigned countChannels() const { return (unsigned)voltage.size(); } 92 unsigned countChannels() const { return (unsigned)voltage.size(); }
  93 +
  94 + double getMarker(unsigned int marker) const {
  95 + return marker < MARKER_COUNT ? horizontal.cursor.pos[marker].x() : 0.0;
  96 + }
  97 + void setMarker(unsigned int marker, double value) {
  98 + if (marker < MARKER_COUNT) horizontal.cursor.pos[marker].setX(value);
  99 + }
80 }; 100 };
openhantek/src/settings.cpp
@@ -75,10 +75,10 @@ void DsoSettings::load() { @@ -75,10 +75,10 @@ void DsoSettings::load() {
75 if (store->contains("format")) scope.horizontal.format = (Dso::GraphFormat)store->value("format").toInt(); 75 if (store->contains("format")) scope.horizontal.format = (Dso::GraphFormat)store->value("format").toInt();
76 if (store->contains("frequencybase")) 76 if (store->contains("frequencybase"))
77 scope.horizontal.frequencybase = store->value("frequencybase").toDouble(); 77 scope.horizontal.frequencybase = store->value("frequencybase").toDouble();
78 - for (int marker = 0; marker < 2; ++marker) { 78 + for (int marker = 0; marker < MARKER_COUNT; ++marker) {
79 QString name; 79 QString name;
80 name = QString("marker%1").arg(marker); 80 name = QString("marker%1").arg(marker);
81 - if (store->contains(name)) scope.horizontal.marker[marker] = store->value(name).toDouble(); 81 + if (store->contains(name)) scope.setMarker(marker, store->value(name).toDouble());
82 } 82 }
83 if (store->contains("timebase")) scope.horizontal.timebase = store->value("timebase").toDouble(); 83 if (store->contains("timebase")) scope.horizontal.timebase = store->value("timebase").toDouble();
84 if (store->contains("recordLength")) scope.horizontal.recordLength = store->value("recordLength").toUInt(); 84 if (store->contains("recordLength")) scope.horizontal.recordLength = store->value("recordLength").toUInt();
@@ -100,6 +100,17 @@ void DsoSettings::load() { @@ -100,6 +100,17 @@ void DsoSettings::load() {
100 scope.spectrum[channel].magnitude = store->value("magnitude").toDouble(); 100 scope.spectrum[channel].magnitude = store->value("magnitude").toDouble();
101 if (store->contains("offset")) scope.spectrum[channel].offset = store->value("offset").toDouble(); 101 if (store->contains("offset")) scope.spectrum[channel].offset = store->value("offset").toDouble();
102 if (store->contains("used")) scope.spectrum[channel].used = store->value("used").toBool(); 102 if (store->contains("used")) scope.spectrum[channel].used = store->value("used").toBool();
  103 + store->beginGroup("cursor");
  104 + if (store->contains("shape")) scope.spectrum[channel].cursor.shape =
  105 + DsoSettingsScopeCursor::CursorShape(store->value("shape").toUInt());
  106 + for (int marker = 0; marker < MARKER_COUNT; ++marker) {
  107 + QString name;
  108 + name = QString("x%1").arg(marker);
  109 + if (store->contains(name)) scope.spectrum[channel].cursor.pos[marker].setX(store->value(name).toDouble());
  110 + name = QString("y%1").arg(marker);
  111 + if (store->contains(name)) scope.spectrum[channel].cursor.pos[marker].setY(store->value(name).toDouble());
  112 + }
  113 + store->endGroup();
103 store->endGroup(); 114 store->endGroup();
104 } 115 }
105 // Vertical axis 116 // Vertical axis
@@ -112,6 +123,17 @@ void DsoSettings::load() { @@ -112,6 +123,17 @@ void DsoSettings::load() {
112 if (store->contains("offset")) scope.voltage[channel].offset = store->value("offset").toDouble(); 123 if (store->contains("offset")) scope.voltage[channel].offset = store->value("offset").toDouble();
113 if (store->contains("trigger")) scope.voltage[channel].trigger = store->value("trigger").toDouble(); 124 if (store->contains("trigger")) scope.voltage[channel].trigger = store->value("trigger").toDouble();
114 if (store->contains("used")) scope.voltage[channel].used = store->value("used").toBool(); 125 if (store->contains("used")) scope.voltage[channel].used = store->value("used").toBool();
  126 + store->beginGroup("cursor");
  127 + if (store->contains("shape")) scope.voltage[channel].cursor.shape =
  128 + DsoSettingsScopeCursor::CursorShape(store->value("shape").toUInt());
  129 + for (int marker = 0; marker < MARKER_COUNT; ++marker) {
  130 + QString name;
  131 + name = QString("x%1").arg(marker);
  132 + if (store->contains(name)) scope.voltage[channel].cursor.pos[marker].setX(store->value(name).toDouble());
  133 + name = QString("y%1").arg(marker);
  134 + if (store->contains(name)) scope.voltage[channel].cursor.pos[marker].setY(store->value(name).toDouble());
  135 + }
  136 + store->endGroup();
115 store->endGroup(); 137 store->endGroup();
116 } 138 }
117 139
@@ -159,7 +181,10 @@ void DsoSettings::load() { @@ -159,7 +181,10 @@ void DsoSettings::load() {
159 if (store->contains("interpolation")) 181 if (store->contains("interpolation"))
160 view.interpolation = (Dso::InterpolationMode)store->value("interpolation").toInt(); 182 view.interpolation = (Dso::InterpolationMode)store->value("interpolation").toInt();
161 if (store->contains("screenColorImages")) view.screenColorImages = store->value("screenColorImages").toBool(); 183 if (store->contains("screenColorImages")) view.screenColorImages = store->value("screenColorImages").toBool();
162 - if (store->contains("zoom")) view.zoom = (Dso::InterpolationMode)store->value("zoom").toBool(); 184 + if (store->contains("zoom")) view.zoom = store->value("zoom").toBool();
  185 + if (store->contains("cursorGridPosition"))
  186 + view.cursorGridPosition = (Qt::ToolBarArea)store->value("cursorGridPosition").toUInt();
  187 + if (store->contains("cursorsVisible")) view.cursorsVisible = store->value("cursorsVisible").toBool();
163 store->endGroup(); 188 store->endGroup();
164 189
165 store->beginGroup("window"); 190 store->beginGroup("window");
@@ -184,8 +209,8 @@ void DsoSettings::save() { @@ -184,8 +209,8 @@ void DsoSettings::save() {
184 store->beginGroup("horizontal"); 209 store->beginGroup("horizontal");
185 store->setValue("format", scope.horizontal.format); 210 store->setValue("format", scope.horizontal.format);
186 store->setValue("frequencybase", scope.horizontal.frequencybase); 211 store->setValue("frequencybase", scope.horizontal.frequencybase);
187 - for (int marker = 0; marker < 2; ++marker)  
188 - store->setValue(QString("marker%1").arg(marker), scope.horizontal.marker[marker]); 212 + for (int marker = 0; marker < MARKER_COUNT; ++marker)
  213 + store->setValue(QString("marker%1").arg(marker), scope.getMarker(marker));
189 store->setValue("timebase", scope.horizontal.timebase); 214 store->setValue("timebase", scope.horizontal.timebase);
190 store->setValue("recordLength", scope.horizontal.recordLength); 215 store->setValue("recordLength", scope.horizontal.recordLength);
191 store->setValue("samplerate", scope.horizontal.samplerate); 216 store->setValue("samplerate", scope.horizontal.samplerate);
@@ -205,6 +230,16 @@ void DsoSettings::save() { @@ -205,6 +230,16 @@ void DsoSettings::save() {
205 store->setValue("magnitude", scope.spectrum[channel].magnitude); 230 store->setValue("magnitude", scope.spectrum[channel].magnitude);
206 store->setValue("offset", scope.spectrum[channel].offset); 231 store->setValue("offset", scope.spectrum[channel].offset);
207 store->setValue("used", scope.spectrum[channel].used); 232 store->setValue("used", scope.spectrum[channel].used);
  233 + store->beginGroup("cursor");
  234 + store->setValue("shape", scope.spectrum[channel].cursor.shape);
  235 + for (int marker = 0; marker < MARKER_COUNT; ++marker) {
  236 + QString name;
  237 + name = QString("x%1").arg(marker);
  238 + store->setValue(name, scope.spectrum[channel].cursor.pos[marker].x());
  239 + name = QString("y%1").arg(marker);
  240 + store->setValue(name, scope.spectrum[channel].cursor.pos[marker].y());
  241 + }
  242 + store->endGroup();
208 store->endGroup(); 243 store->endGroup();
209 } 244 }
210 // Vertical axis 245 // Vertical axis
@@ -216,6 +251,16 @@ void DsoSettings::save() { @@ -216,6 +251,16 @@ void DsoSettings::save() {
216 store->setValue("offset", scope.voltage[channel].offset); 251 store->setValue("offset", scope.voltage[channel].offset);
217 store->setValue("trigger", scope.voltage[channel].trigger); 252 store->setValue("trigger", scope.voltage[channel].trigger);
218 store->setValue("used", scope.voltage[channel].used); 253 store->setValue("used", scope.voltage[channel].used);
  254 + store->beginGroup("cursor");
  255 + store->setValue("shape", scope.voltage[channel].cursor.shape);
  256 + for (int marker = 0; marker < MARKER_COUNT; ++marker) {
  257 + QString name;
  258 + name = QString("x%1").arg(marker);
  259 + store->setValue(name, scope.voltage[channel].cursor.pos[marker].x());
  260 + name = QString("y%1").arg(marker);
  261 + store->setValue(name, scope.voltage[channel].cursor.pos[marker].y());
  262 + }
  263 + store->endGroup();
219 store->endGroup(); 264 store->endGroup();
220 } 265 }
221 266
@@ -259,6 +304,8 @@ void DsoSettings::save() { @@ -259,6 +304,8 @@ void DsoSettings::save() {
259 store->setValue("interpolation", view.interpolation); 304 store->setValue("interpolation", view.interpolation);
260 store->setValue("screenColorImages", view.screenColorImages); 305 store->setValue("screenColorImages", view.screenColorImages);
261 store->setValue("zoom", view.zoom); 306 store->setValue("zoom", view.zoom);
  307 + store->setValue("cursorGridPosition", view.cursorGridPosition);
  308 + store->setValue("cursorsVisible", view.cursorsVisible);
262 store->endGroup(); 309 store->endGroup();
263 310
264 store->beginGroup("window"); 311 store->beginGroup("window");
openhantek/src/viewsettings.h
@@ -42,6 +42,8 @@ struct DsoSettingsView { @@ -42,6 +42,8 @@ struct DsoSettingsView {
42 Dso::InterpolationMode interpolation = Dso::INTERPOLATION_LINEAR; ///< Interpolation mode for the graph 42 Dso::InterpolationMode interpolation = Dso::INTERPOLATION_LINEAR; ///< Interpolation mode for the graph
43 bool screenColorImages = false; ///< true exports images with screen colors 43 bool screenColorImages = false; ///< true exports images with screen colors
44 bool zoom = false; ///< true if the magnified scope is enabled 44 bool zoom = false; ///< true if the magnified scope is enabled
  45 + Qt::ToolBarArea cursorGridPosition = Qt::RightToolBarArea;
  46 + bool cursorsVisible = false;
45 47
46 unsigned digitalPhosphorDraws() const { 48 unsigned digitalPhosphorDraws() const {
47 return digitalPhosphor ? digitalPhosphorDepth : 1; 49 return digitalPhosphor ? digitalPhosphorDepth : 1;
openhantek/src/widgets/datagrid.cpp 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#include "datagrid.h"
  4 +
  5 +#include <QGridLayout>
  6 +#include <QLabel>
  7 +#include <QPushButton>
  8 +#include <QButtonGroup>
  9 +
  10 +DataGrid::DataGrid(QWidget *parent) : QGroupBox(parent)
  11 +{
  12 + cursorsLayout = new QGridLayout();
  13 + cursorsLayout->setSpacing(5);
  14 + cursorsSelectorGroup = new QButtonGroup();
  15 + cursorsSelectorGroup->setExclusive(true);
  16 +
  17 + connect(cursorsSelectorGroup,
  18 + static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonPressed), [this] (int index) {
  19 + emit itemSelected(index);
  20 + });
  21 +
  22 + setLayout(cursorsLayout);
  23 + setFixedWidth(180);
  24 +}
  25 +
  26 +DataGrid::CursorInfo::CursorInfo() {
  27 + selector = new QPushButton();
  28 + selector->setCheckable(true);
  29 + shape = new QPushButton();
  30 + deltaXLabel = new QLabel();
  31 + deltaXLabel->setAlignment(Qt::AlignRight);
  32 + deltaYLabel = new QLabel();
  33 + deltaYLabel->setAlignment(Qt::AlignRight);
  34 +}
  35 +
  36 +void DataGrid::CursorInfo::configure(const QString &text, const QColor &bgColor, const QColor &fgColor) {
  37 + palette.setColor(QPalette::Background, bgColor);
  38 + palette.setColor(QPalette::WindowText, fgColor);
  39 +
  40 + selector->setText(text);
  41 + selector->setStyleSheet(QString(R"(
  42 + QPushButton {
  43 + color: %2;
  44 + background-color: %1;
  45 + border: 1px solid %2;
  46 + }
  47 + QPushButton:checked {
  48 + color: %1;
  49 + background-color: %2;
  50 + }
  51 + QPushButton:disabled {
  52 + color: %3;
  53 + border: 1px dotted %2;
  54 + }
  55 + )").arg(bgColor.name(QColor::HexArgb))
  56 + .arg(fgColor.name(QColor::HexArgb))
  57 + .arg(fgColor.darker().name(QColor::HexArgb)));
  58 +
  59 + shape->setStyleSheet(QString(R"(
  60 + QPushButton {
  61 + color: %2;
  62 + background-color: %1;
  63 + border: none
  64 + }
  65 + )").arg(bgColor.name(QColor::HexArgb))
  66 + .arg(fgColor.name(QColor::HexArgb)));
  67 +
  68 + deltaXLabel->setPalette(palette);
  69 + deltaYLabel->setPalette(palette);
  70 +}
  71 +
  72 +void DataGrid::setBackgroundColor(const QColor &bgColor) {
  73 + backgroundColor = bgColor;
  74 + for (auto it : items) {
  75 + it.configure(it.selector->text(), bgColor, it.palette.color(QPalette::WindowText));
  76 + }
  77 +}
  78 +
  79 +void DataGrid::configureItem(unsigned index, const QColor &fgColor) {
  80 + if (index < items.size()) {
  81 + items[index].configure(items[index].selector->text(), backgroundColor, fgColor);
  82 + }
  83 +}
  84 +
  85 +unsigned DataGrid::addItem(const QString &text, const QColor &fgColor) {
  86 + unsigned index = items.size();
  87 + items.resize(index + 1);
  88 +
  89 + CursorInfo& info = items.at(index);
  90 + info.configure(text, backgroundColor, fgColor);
  91 + cursorsSelectorGroup->addButton(info.selector, index);
  92 +
  93 + connect(info.shape, &QPushButton::clicked, [this, index] () {
  94 + emit itemUpdated(index);
  95 + });
  96 +
  97 + cursorsLayout->addWidget(info.selector, 3 * index, 0);
  98 + cursorsLayout->addWidget(info.shape, 3 * index, 1);
  99 + cursorsLayout->addWidget(info.deltaXLabel, 3 * index + 1, 0);
  100 + cursorsLayout->addWidget(info.deltaYLabel, 3 * index + 1, 1);
  101 + cursorsLayout->setRowMinimumHeight(3 * index + 2, 10);
  102 + cursorsLayout->setRowStretch(3 * index, 0);
  103 + cursorsLayout->setRowStretch(3 * index + 3, 1);
  104 +
  105 + return index;
  106 +}
  107 +
  108 +void DataGrid::updateInfo(unsigned index, bool visible, const QString &strShape, const QString &strX, const QString &strY) {
  109 + if (index >= items.size()) return;
  110 + CursorInfo &info = items.at(index);
  111 + info.selector->setEnabled(visible);
  112 + if (visible) {
  113 + info.shape->setText(strShape);
  114 + info.deltaXLabel->setText(strX);
  115 + info.deltaYLabel->setText(strY);
  116 + } else {
  117 + info.shape->setText(QString());
  118 + info.deltaXLabel->setText(QString());
  119 + info.deltaYLabel->setText(QString());
  120 + }
  121 +}
  122 +
  123 +void DataGrid::selectItem(unsigned index) {
  124 + if (index >= items.size()) return;
  125 + items[index].selector->setChecked(true);
  126 +}
openhantek/src/widgets/datagrid.h 0 → 100644
  1 +// SPDX-License-Identifier: GPL-2.0+
  2 +
  3 +#pragma once
  4 +
  5 +#include <QGroupBox>
  6 +#include <QPalette>
  7 +
  8 +class QPushButton;
  9 +class QButtonGroup;
  10 +class QLabel;
  11 +class QGridLayout;
  12 +
  13 +class DataGrid : public QGroupBox
  14 +{
  15 + Q_OBJECT
  16 +public:
  17 + explicit DataGrid(QWidget *parent = nullptr);
  18 +
  19 + struct CursorInfo {
  20 + QPalette palette; ///< The widget's palette
  21 + QPushButton *selector; ///< The name of the channel
  22 + QPushButton *shape; ///< The cursor shape
  23 + QLabel *deltaXLabel; ///< The horizontal distance between cursors
  24 + QLabel *deltaYLabel; ///< The vertical distance between cursors
  25 +
  26 + CursorInfo();
  27 + void configure(const QString &text, const QColor &bgColor, const QColor &fgColor);
  28 + };
  29 +
  30 + unsigned addItem(const QString &text, const QColor &fgColor);
  31 + void setBackgroundColor(const QColor &bgColor);
  32 + void configureItem(unsigned index, const QColor &fgColor);
  33 + void updateInfo(unsigned index, bool visible, const QString &strShape = QString(),
  34 + const QString &strX = QString(), const QString &strY = QString());
  35 +
  36 +signals:
  37 + void itemSelected(unsigned index);
  38 + void itemUpdated(unsigned index);
  39 +
  40 +public slots:
  41 + void selectItem(unsigned index);
  42 +
  43 +private:
  44 + QColor backgroundColor;
  45 + QButtonGroup *cursorsSelectorGroup;
  46 + QGridLayout *cursorsLayout;
  47 + std::vector<CursorInfo> items;
  48 +};
openhantek/src/widgets/sispinbox.cpp
@@ -160,18 +160,19 @@ void SiSpinBox::setMode(const int mode) { this-&gt;mode = mode; } @@ -160,18 +160,19 @@ void SiSpinBox::setMode(const int mode) { this-&gt;mode = mode; }
160 160
161 /// \brief Generic initializations. 161 /// \brief Generic initializations.
162 void SiSpinBox::init() { 162 void SiSpinBox::init() {
163 - this->setMinimum(1e-12);  
164 - this->setMaximum(1e12);  
165 - this->setValue(1.0);  
166 - this->setDecimals(DBL_MAX_10_EXP + DBL_DIG); // Disable automatic rounding  
167 - this->unit = unit;  
168 - this->steps << 1.0 << 2.0 << 5.0 << 10.0;  
169 -  
170 - this->steppedTo = false;  
171 - this->stepId = 0;  
172 - this->mode = 0;  
173 -  
174 - connect(this, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, &SiSpinBox::resetSteppedTo); 163 + setMinimum(1e-12);
  164 + setMaximum(1e12);
  165 + setValue(1.0);
  166 + setDecimals(DBL_MAX_10_EXP + DBL_DIG); // Disable automatic rounding
  167 + setFocusPolicy(Qt::NoFocus);
  168 + steps << 1.0 << 2.0 << 5.0 << 10.0;
  169 +
  170 + steppedTo = false;
  171 + stepId = 0;
  172 + mode = 0;
  173 +
  174 + connect(this, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
  175 + this, &SiSpinBox::resetSteppedTo);
175 } 176 }
176 177
177 /// \brief Resets the ::steppedTo flag after the value has been changed. 178 /// \brief Resets the ::steppedTo flag after the value has been changed.