Commit 0eff8d451180c8c1328ff0d33de4e2f477147e16
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
Showing
15 changed files
with
699 additions
and
129 deletions
openhantek/src/configdialog/DsoConfigScopePage.cpp
| ... | ... | @@ -27,8 +27,22 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : |
| 27 | 27 | graphGroup = new QGroupBox(tr("Graph")); |
| 28 | 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 | 43 | mainLayout = new QVBoxLayout(); |
| 31 | 44 | mainLayout->addWidget(graphGroup); |
| 45 | + mainLayout->addWidget(cursorsGroup); | |
| 32 | 46 | mainLayout->addStretch(1); |
| 33 | 47 | |
| 34 | 48 | setLayout(mainLayout); |
| ... | ... | @@ -38,4 +52,5 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : |
| 38 | 52 | void DsoConfigScopePage::saveSettings() { |
| 39 | 53 | settings->view.interpolation = (Dso::InterpolationMode)interpolationComboBox->currentIndex(); |
| 40 | 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 | 37 | QSpinBox *digitalPhosphorDepthSpinBox; |
| 38 | 38 | QLabel *interpolationLabel; |
| 39 | 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 | 22 | #include "viewconstants.h" |
| 23 | 23 | #include "viewsettings.h" |
| 24 | 24 | #include "widgets/levelslider.h" |
| 25 | +#include "widgets/datagrid.h" | |
| 25 | 26 | |
| 26 | 27 | static int zoomScopeRow = 0; |
| 27 | 28 | |
| ... | ... | @@ -38,12 +39,14 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: |
| 38 | 39 | setupSliders(mainSliders); |
| 39 | 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 | 52 | // The table for the settings |
| ... | ... | @@ -142,40 +145,88 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: |
| 142 | 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 | 194 | // The layout for the widgets |
| 146 | 195 | mainLayout = new QGridLayout(); |
| 147 | - mainLayout->setColumnStretch(2, 1); // Scopes increase their size | |
| 196 | + mainLayout->setColumnStretch(3, 1); // Scopes increase their size | |
| 148 | 197 | // Bars around the scope, needed because the slider-drawing-area is outside |
| 149 | 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 | 201 | mainLayout->setSpacing(0); |
| 153 | 202 | int row = 0; |
| 154 | - mainLayout->addLayout(settingsLayout, row++, 0, 1, 5); | |
| 203 | + mainLayout->addLayout(settingsLayout, row++, 1, 1, 5); | |
| 155 | 204 | // 5x5 box for mainScope & mainSliders |
| 156 | 205 | mainLayout->setRowMinimumHeight(row + 1, mainSliders.offsetSlider->preMargin()); |
| 157 | 206 | mainLayout->setRowMinimumHeight(row + 3, mainSliders.offsetSlider->postMargin()); |
| 158 | 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 | 213 | row += 5; |
| 165 | 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 | 217 | mainLayout->setRowMinimumHeight(row++, 4); |
| 169 | 218 | // 5x5 box for zoomScope & zoomSliders |
| 170 | 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 | 224 | row += 5; |
| 176 | 225 | // Separator and embedded measurementLayout |
| 177 | 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 | 231 | // The widget itself |
| 181 | 232 | setPalette(palette); |
| ... | ... | @@ -187,20 +238,54 @@ DsoWidget::DsoWidget(DsoSettingsScope *scope, DsoSettingsView *view, const Dso:: |
| 187 | 238 | connect(mainSliders.offsetSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateOffset); |
| 188 | 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 | 248 | connect(mainSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel); |
| 194 | 249 | connect(zoomSliders.triggerLevelSlider, &LevelSlider::valueChanged, this, &DsoWidget::updateTriggerLevel); |
| 195 | 250 | |
| 196 | 251 | connect(mainSliders.markerSlider, &LevelSlider::valueChanged, [this](int index, double value) { |
| 197 | 252 | updateMarker(index, value); |
| 198 | - mainScope->update(); | |
| 199 | - zoomScope->update(); | |
| 253 | + mainScope->updateCursor(); | |
| 254 | + zoomScope->updateCursor(); | |
| 200 | 255 | }); |
| 201 | 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 | 289 | void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) { |
| 205 | 290 | // The offset sliders for all possible channels |
| 206 | 291 | sliders.offsetSlider = new LevelSlider(Qt::RightArrow); |
| ... | ... | @@ -248,7 +333,7 @@ void DsoWidget::setupSliders(DsoWidget::Sliders &sliders) { |
| 248 | 333 | sliders.markerSlider->addSlider(QString::number(marker + 1), marker); |
| 249 | 334 | sliders.markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2); |
| 250 | 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 | 337 | sliders.markerSlider->setIndexVisible(marker, true); |
| 253 | 338 | } |
| 254 | 339 | } |
| ... | ... | @@ -285,7 +370,7 @@ void DsoWidget::setMeasurementVisible(ChannelID channel) { |
| 285 | 370 | } |
| 286 | 371 | |
| 287 | 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 | 374 | int precision = 3 - (int)floor(log10(fabs(value))); |
| 290 | 375 | |
| 291 | 376 | if (scope->horizontal.timebase < 1e-9) |
| ... | ... | @@ -302,8 +387,9 @@ static QString markerToString(DsoSettingsScope *scope, unsigned index) { |
| 302 | 387 | |
| 303 | 388 | /// \brief Update the label about the marker measurements |
| 304 | 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 | 391 | double time = divs * scope->horizontal.timebase; |
| 392 | + double freq = divs * scope->horizontal.frequencybase; | |
| 307 | 393 | |
| 308 | 394 | QString prefix(tr("Markers")); |
| 309 | 395 | if (view->zoom) { |
| ... | ... | @@ -315,6 +401,36 @@ void DsoWidget::updateMarkerDetails() { |
| 315 | 401 | markerInfoLabel->setText(prefix.append(": %1 %2").arg(markerToString(scope, 0)).arg(markerToString(scope, 1))); |
| 316 | 402 | markerTimeLabel->setText(valueToString(time, UNIT_SECONDS, 4)); |
| 317 | 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 | 436 | /// \brief Update the label about the trigger settings |
| ... | ... | @@ -385,10 +501,13 @@ void DsoWidget::updateSpectrumMagnitude(ChannelID channel) { updateSpectrumDetai |
| 385 | 501 | void DsoWidget::updateSpectrumUsed(ChannelID channel, bool used) { |
| 386 | 502 | if (channel >= (unsigned int)scope->voltage.size()) return; |
| 387 | 503 | |
| 504 | +// if (!used && cursorDataGrid->spectrumCursors[channel].selector->isChecked()) cursorDataGrid->selectItem(0); | |
| 505 | + | |
| 388 | 506 | mainSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used); |
| 389 | 507 | zoomSliders.offsetSlider->setIndexVisible(scope->voltage.size() + channel, used); |
| 390 | 508 | |
| 391 | 509 | updateSpectrumDetails(channel); |
| 510 | + updateMarkerDetails(); | |
| 392 | 511 | } |
| 393 | 512 | |
| 394 | 513 | /// \brief Handles modeChanged signal from the trigger dock. |
| ... | ... | @@ -452,6 +571,8 @@ void DsoWidget::updateVoltageGain(ChannelID channel) { |
| 452 | 571 | void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) { |
| 453 | 572 | if (channel >= (unsigned int)scope->voltage.size()) return; |
| 454 | 573 | |
| 574 | +// if (!used && cursorDataGrid->voltageCursors[channel].selector->isChecked()) cursorDataGrid->selectItem(0); | |
| 575 | + | |
| 455 | 576 | mainSliders.offsetSlider->setIndexVisible(channel, used); |
| 456 | 577 | zoomSliders.offsetSlider->setIndexVisible(channel, used); |
| 457 | 578 | |
| ... | ... | @@ -460,6 +581,7 @@ void DsoWidget::updateVoltageUsed(ChannelID channel, bool used) { |
| 460 | 581 | |
| 461 | 582 | setMeasurementVisible(channel); |
| 462 | 583 | updateVoltageDetails(channel); |
| 584 | + updateMarkerDetails(); | |
| 463 | 585 | } |
| 464 | 586 | |
| 465 | 587 | /// \brief Change the record length. |
| ... | ... | @@ -549,11 +671,11 @@ void DsoWidget::updateOffset(ChannelID channel, double value) { |
| 549 | 671 | |
| 550 | 672 | if (channel < scope->voltage.size() * 2) { |
| 551 | 673 | if (mainSliders.offsetSlider->value(channel) != value) { |
| 552 | - QSignalBlocker blocker(mainSliders.offsetSlider); | |
| 674 | + const QSignalBlocker blocker(mainSliders.offsetSlider); | |
| 553 | 675 | mainSliders.offsetSlider->setValue(channel, value); |
| 554 | 676 | } |
| 555 | 677 | if (zoomSliders.offsetSlider->value(channel) != value) { |
| 556 | - QSignalBlocker blocker(zoomSliders.offsetSlider); | |
| 678 | + const QSignalBlocker blocker(zoomSliders.offsetSlider); | |
| 557 | 679 | zoomSliders.offsetSlider->setValue(channel, value); |
| 558 | 680 | } |
| 559 | 681 | } |
| ... | ... | @@ -561,18 +683,38 @@ void DsoWidget::updateOffset(ChannelID channel, double value) { |
| 561 | 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 | 702 | /// \brief Handles signals affecting trigger position in the zoom view. |
| 565 | 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 | 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 | 722 | /// \param index The index of the slider. |
| 581 | 723 | /// \param value The new triggerPosition in seconds relative to the first |
| 582 | 724 | /// sample. |
| 583 | -void DsoWidget::updateTriggerPosition(int index, double value) { | |
| 725 | +void DsoWidget::updateTriggerPosition(int index, double value, bool mainView) { | |
| 584 | 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 | 737 | updateTriggerDetails(); |
| 590 | 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 | 743 | /// \brief Handles valueChanged signal from the trigger level slider. |
| ... | ... | @@ -599,11 +747,11 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) { |
| 599 | 747 | scope->voltage[channel].trigger = value; |
| 600 | 748 | |
| 601 | 749 | if (mainSliders.triggerLevelSlider->value(channel) != value) { |
| 602 | - QSignalBlocker blocker(mainSliders.triggerLevelSlider); | |
| 750 | + const QSignalBlocker blocker(mainSliders.triggerLevelSlider); | |
| 603 | 751 | mainSliders.triggerLevelSlider->setValue(channel, value); |
| 604 | 752 | } |
| 605 | 753 | if (zoomSliders.triggerLevelSlider->value(channel) != value) { |
| 606 | - QSignalBlocker blocker(zoomSliders.triggerLevelSlider); | |
| 754 | + const QSignalBlocker blocker(zoomSliders.triggerLevelSlider); | |
| 607 | 755 | zoomSliders.triggerLevelSlider->setValue(channel, value); |
| 608 | 756 | } |
| 609 | 757 | |
| ... | ... | @@ -616,10 +764,7 @@ void DsoWidget::updateTriggerLevel(ChannelID channel, double value) { |
| 616 | 764 | /// \param marker The index of the slider. |
| 617 | 765 | /// \param value The new marker position. |
| 618 | 766 | void DsoWidget::updateMarker(int marker, double value) { |
| 619 | - scope->horizontal.marker[marker] = value; | |
| 620 | - | |
| 767 | + scope->setMarker(marker, value); | |
| 621 | 768 | adaptTriggerPositionSlider(); |
| 622 | 769 | updateMarkerDetails(); |
| 623 | - | |
| 624 | - emit markerChanged(marker, value); | |
| 625 | 770 | } | ... | ... |
openhantek/src/dsowidget.h
| ... | ... | @@ -15,12 +15,15 @@ |
| 15 | 15 | class SpectrumGenerator; |
| 16 | 16 | struct DsoSettingsScope; |
| 17 | 17 | struct DsoSettingsView; |
| 18 | +class DataGrid; | |
| 18 | 19 | |
| 19 | 20 | /// \brief The widget for the oszilloscope-screen |
| 20 | 21 | /// This widget contains the scopes and all level sliders. |
| 21 | 22 | class DsoWidget : public QWidget { |
| 22 | 23 | Q_OBJECT |
| 23 | 24 | |
| 25 | + public: | |
| 26 | + | |
| 24 | 27 | struct Sliders { |
| 25 | 28 | LevelSlider *offsetSlider; ///< The sliders for the graph offsets |
| 26 | 29 | LevelSlider *triggerPositionSlider; ///< The slider for the pretrigger |
| ... | ... | @@ -28,7 +31,6 @@ class DsoWidget : public QWidget { |
| 28 | 31 | LevelSlider *markerSlider; ///< The sliders for the markers |
| 29 | 32 | }; |
| 30 | 33 | |
| 31 | - public: | |
| 32 | 34 | /// \brief Initializes the components of the oszilloscope-screen. |
| 33 | 35 | /// \param settings The settings object containing the oscilloscope settings. |
| 34 | 36 | /// \param dataAnalyzer The data analyzer that should be used as data source. |
| ... | ... | @@ -50,6 +52,9 @@ class DsoWidget : public QWidget { |
| 50 | 52 | void updateTriggerDetails(); |
| 51 | 53 | void updateVoltageDetails(ChannelID channel); |
| 52 | 54 | |
| 55 | + double mainToZoom(double position) const; | |
| 56 | + double zoomToMain(double position) const; | |
| 57 | + | |
| 53 | 58 | Sliders mainSliders; |
| 54 | 59 | Sliders zoomSliders; |
| 55 | 60 | |
| ... | ... | @@ -79,6 +84,8 @@ class DsoWidget : public QWidget { |
| 79 | 84 | std::vector<QLabel *> measurementAmplitudeLabel; ///< Amplitude of the signal (V) |
| 80 | 85 | std::vector<QLabel *> measurementFrequencyLabel; ///< Frequency of the signal (Hz) |
| 81 | 86 | |
| 87 | + DataGrid *cursorDataGrid; | |
| 88 | + | |
| 82 | 89 | DsoSettingsScope* scope; |
| 83 | 90 | DsoSettingsView* view; |
| 84 | 91 | const Dso::ControlSpecification* spec; |
| ... | ... | @@ -113,11 +120,12 @@ class DsoWidget : public QWidget { |
| 113 | 120 | |
| 114 | 121 | // Scope control |
| 115 | 122 | void updateZoom(bool enabled); |
| 123 | + void updateCursorGrid(bool enabled); | |
| 116 | 124 | |
| 117 | 125 | private slots: |
| 118 | 126 | // Sliders |
| 119 | 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 | 129 | void updateTriggerLevel(ChannelID channel, double value); |
| 122 | 130 | void updateMarker(int marker, double value); |
| 123 | 131 | |
| ... | ... | @@ -126,5 +134,4 @@ class DsoWidget : public QWidget { |
| 126 | 134 | void offsetChanged(ChannelID channel, double value); ///< A graph offset has been changed |
| 127 | 135 | void triggerPositionChanged(double value); ///< The pretrigger has been changed |
| 128 | 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 | 115 | painter.setPen(colorValues->text); |
| 116 | 116 | |
| 117 | 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 | 121 | double time = divs * settings->scope.horizontal.timebase; |
| 120 | 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 | 125 | if (settings->view.zoom) { |
| 124 | 126 | scopeHeight = (double)(paintDevice->height() - (channelCount + 5) * lineHeight) / 2; | ... | ... |
openhantek/src/glscope.cpp
| ... | ... | @@ -54,50 +54,106 @@ void GlScope::fixOpenGLversion(QSurfaceFormat::RenderableType t) { |
| 54 | 54 | |
| 55 | 55 | GlScope::GlScope(DsoSettingsScope *scope, DsoSettingsView *view, QWidget *parent) |
| 56 | 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 | 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 | 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 | 86 | selectedMarker = NO_MARKER; |
| 87 | + DsoSettingsScopeCursor *cursor = cursorInfo[selectedCursor]; | |
| 67 | 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 | 128 | event->accept(); |
| 78 | 129 | } |
| 79 | 130 | |
| 80 | 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 | 134 | if (selectedMarker == NO_MARKER) { |
| 84 | 135 | // User started draging outside the snap area of any marker: |
| 85 | 136 | // move all markers to current position and select last marker in the array. |
| 86 | 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 | 140 | selectedMarker = marker; |
| 89 | 141 | } |
| 90 | 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 | 147 | event->accept(); |
| 95 | 148 | } |
| 96 | 149 | |
| 97 | 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 | 157 | selectedMarker = NO_MARKER; |
| 102 | 158 | } |
| 103 | 159 | event->accept(); |
| ... | ... | @@ -209,12 +265,11 @@ void GlScope::initializeGL() { |
| 209 | 265 | m_marker.create(); |
| 210 | 266 | m_marker.bind(); |
| 211 | 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 | 269 | program->enableAttributeArray(vertexLocation); |
| 214 | 270 | program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 0); |
| 215 | 271 | } |
| 216 | - | |
| 217 | - markerUpdated(); | |
| 272 | + updateCursor(); | |
| 218 | 273 | |
| 219 | 274 | m_program = std::move(program); |
| 220 | 275 | shaderCompileSuccess = true; |
| ... | ... | @@ -239,18 +294,65 @@ void GlScope::showData(std::shared_ptr<PPresult> data) { |
| 239 | 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 | 352 | // Write coordinates to GPU |
| 251 | 353 | makeCurrent(); |
| 252 | 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 | 358 | void GlScope::paintGL() { |
| ... | ... | @@ -268,12 +370,13 @@ void GlScope::paintGL() { |
| 268 | 370 | // Apply zoom settings via matrix transformation |
| 269 | 371 | if (zoomed) { |
| 270 | 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 | 375 | m_program->setUniformValue(matrixLocation, pmvMatrix * m); |
| 275 | 376 | } |
| 276 | 377 | |
| 378 | + drawMarkers(); | |
| 379 | + | |
| 277 | 380 | unsigned historyIndex = 0; |
| 278 | 381 | for (Graph &graph : m_GraphHistory) { |
| 279 | 382 | for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) { |
| ... | ... | @@ -287,8 +390,6 @@ void GlScope::paintGL() { |
| 287 | 390 | |
| 288 | 391 | if (zoomed) { m_program->setUniformValue(matrixLocation, pmvMatrix); } |
| 289 | 392 | |
| 290 | - if (!this->zoomed) drawMarkers(); | |
| 291 | - | |
| 292 | 393 | drawGrid(); |
| 293 | 394 | m_program->release(); |
| 294 | 395 | } |
| ... | ... | @@ -432,21 +533,38 @@ void GlScope::drawGrid() { |
| 432 | 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 | 546 | void GlScope::drawMarkers() { |
| 436 | 547 | auto *gl = context()->functions(); |
| 437 | - QColor trColor = view->screen.markers; | |
| 438 | - m_program->setUniformValue(colorLocation, trColor); | |
| 439 | 548 | |
| 440 | 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 | 570 | m_vaoMarker.release(); | ... | ... |
openhantek/src/glscope.h
| ... | ... | @@ -18,6 +18,7 @@ |
| 18 | 18 | |
| 19 | 19 | struct DsoSettingsView; |
| 20 | 20 | struct DsoSettingsScope; |
| 21 | +struct DsoSettingsScopeCursor; | |
| 21 | 22 | class PPresult; |
| 22 | 23 | |
| 23 | 24 | /// \brief OpenGL accelerated widget that displays the oscilloscope screen. |
| ... | ... | @@ -40,7 +41,8 @@ class GlScope : public QOpenGLWidget { |
| 40 | 41 | * @param data |
| 41 | 42 | */ |
| 42 | 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 | 47 | protected: |
| 46 | 48 | /// \brief Initializes the scope widget. |
| ... | ... | @@ -70,11 +72,14 @@ class GlScope : public QOpenGLWidget { |
| 70 | 72 | void drawGrid(); |
| 71 | 73 | /// Draw vertical lines at marker positions |
| 72 | 74 | void drawMarkers(); |
| 75 | + void generateVertices(unsigned marker, const DsoSettingsScopeCursor &cursor); | |
| 76 | + void drawVertices(QOpenGLFunctions *gl, unsigned marker, QColor color); | |
| 73 | 77 | |
| 74 | 78 | void drawVoltageChannelGraph(ChannelID channel, Graph &graph, int historyIndex); |
| 75 | 79 | void drawSpectrumChannelGraph(ChannelID channel, Graph &graph, int historyIndex); |
| 80 | + QPointF eventToPosition(QMouseEvent *event); | |
| 76 | 81 | signals: |
| 77 | - void markerMoved(unsigned marker, double position); | |
| 82 | + void markerMoved(unsigned cursorIndex, unsigned marker); | |
| 78 | 83 | |
| 79 | 84 | private: |
| 80 | 85 | // User settings |
| ... | ... | @@ -85,16 +90,20 @@ class GlScope : public QOpenGLWidget { |
| 85 | 90 | // Marker |
| 86 | 91 | const unsigned NO_MARKER = UINT_MAX; |
| 87 | 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 | 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 | 99 | unsigned selectedMarker = NO_MARKER; |
| 95 | 100 | QOpenGLBuffer m_marker; |
| 96 | 101 | QOpenGLVertexArrayObject m_vaoMarker; |
| 97 | 102 | |
| 103 | + // Cursors | |
| 104 | + std::vector<DsoSettingsScopeCursor *> cursorInfo; | |
| 105 | + unsigned selectedCursor = 0; | |
| 106 | + | |
| 98 | 107 | // Grid |
| 99 | 108 | QOpenGLBuffer m_grid; |
| 100 | 109 | QOpenGLVertexArrayObject m_vaoGrid[3]; | ... | ... |
openhantek/src/mainwindow.cpp
| ... | ... | @@ -39,6 +39,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo |
| 39 | 39 | ui->actionManualCommand->setIcon(iconFont->icon(fa::edit)); |
| 40 | 40 | ui->actionDigital_phosphor->setIcon(QIcon(":/images/digitalphosphor.svg")); |
| 41 | 41 | ui->actionZoom->setIcon(iconFont->icon(fa::crop)); |
| 42 | + ui->actionCursors->setIcon(iconFont->icon(fa::crosshairs)); | |
| 42 | 43 | |
| 43 | 44 | // Window title |
| 44 | 45 | setWindowIcon(QIcon(":openhantek.png")); |
| ... | ... | @@ -160,6 +161,7 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo |
| 160 | 161 | auto usedChanged = [this, dsoControl, spec](ChannelID channel, bool used) { |
| 161 | 162 | if (channel >= (unsigned int)mSettings->scope.voltage.size()) return; |
| 162 | 163 | |
| 164 | +// if (!used) dsoWidget-> | |
| 163 | 165 | bool mathUsed = mSettings->scope.anyUsed(spec->channels); |
| 164 | 166 | |
| 165 | 167 | // Normal channel, check if voltage/spectrum or math channel is used |
| ... | ... | @@ -267,6 +269,18 @@ MainWindow::MainWindow(HantekDsoControl *dsoControl, DsoSettings *settings, Expo |
| 267 | 269 | }); |
| 268 | 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 | 284 | connect(ui->actionAbout, &QAction::triggered, [this]() { |
| 271 | 285 | QMessageBox::about( |
| 272 | 286 | this, tr("About OpenHantek %1").arg(VERSION), | ... | ... |
openhantek/src/mainwindow.ui
| ... | ... | @@ -20,7 +20,7 @@ |
| 20 | 20 | <x>0</x> |
| 21 | 21 | <y>0</y> |
| 22 | 22 | <width>800</width> |
| 23 | - <height>25</height> | |
| 23 | + <height>28</height> | |
| 24 | 24 | </rect> |
| 25 | 25 | </property> |
| 26 | 26 | <widget class="QMenu" name="menuFile"> |
| ... | ... | @@ -39,6 +39,8 @@ |
| 39 | 39 | </property> |
| 40 | 40 | <addaction name="actionDigital_phosphor"/> |
| 41 | 41 | <addaction name="actionZoom"/> |
| 42 | + <addaction name="actionCursors"/> | |
| 43 | + <addaction name="separator"/> | |
| 42 | 44 | <addaction name="actionManualCommand"/> |
| 43 | 45 | </widget> |
| 44 | 46 | <widget class="QMenu" name="menuOscilloscope"> |
| ... | ... | @@ -84,6 +86,7 @@ |
| 84 | 86 | <addaction name="separator"/> |
| 85 | 87 | <addaction name="actionDigital_phosphor"/> |
| 86 | 88 | <addaction name="actionZoom"/> |
| 89 | + <addaction name="actionCursors"/> | |
| 87 | 90 | <addaction name="separator"/> |
| 88 | 91 | </widget> |
| 89 | 92 | <action name="actionOpen"> |
| ... | ... | @@ -167,6 +170,14 @@ |
| 167 | 170 | <string>Manual command</string> |
| 168 | 171 | </property> |
| 169 | 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 | 181 | </widget> |
| 171 | 182 | <resources/> |
| 172 | 183 | <connections/> | ... | ... |
openhantek/src/scopesettings.h
| ... | ... | @@ -3,6 +3,7 @@ |
| 3 | 3 | #pragma once |
| 4 | 4 | |
| 5 | 5 | #include <QString> |
| 6 | +#include <QPointF> | |
| 6 | 7 | |
| 7 | 8 | #include "hantekdso/controlspecification.h" |
| 8 | 9 | #include "hantekdso/enums.h" |
| ... | ... | @@ -12,12 +13,22 @@ |
| 12 | 13 | #define MARKER_COUNT 2 ///< Number of markers |
| 13 | 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 | 27 | /// \brief Holds the settings for the horizontal axis. |
| 16 | 28 | struct DsoSettingsScopeHorizontal { |
| 17 | 29 | Dso::GraphFormat format = Dso::GraphFormat::TY; ///< Graph drawing mode of the scope |
| 18 | 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 | 33 | unsigned int recordLength = 0; ///< Sample count |
| 23 | 34 | |
| ... | ... | @@ -39,25 +50,27 @@ struct DsoSettingsScopeTrigger { |
| 39 | 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 | 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 | 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 | 66 | /// \brief Holds the settings for the normal voltage graphs. |
| 52 | 67 | /// TODO Use ControlSettingsVoltage |
| 53 | -struct DsoSettingsScopeVoltage { | |
| 68 | +struct DsoSettingsScopeVoltage : public DsoSettingsScopeChannel { | |
| 54 | 69 | double offset = 0.0; ///< Vertical offset in divs |
| 55 | 70 | double trigger = 0.0; ///< Trigger level in V |
| 56 | 71 | unsigned gainStepIndex = 6; ///< The vertical resolution in V/div (default = 1.0) |
| 57 | 72 | unsigned couplingOrMathIndex = 0; ///< Different index: coupling for real- and mode for math-channels |
| 58 | - QString name; ///< Name of this channel | |
| 59 | 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 | 76 | /// \brief Holds the settings for the oscilloscope. |
| ... | ... | @@ -77,4 +90,11 @@ struct DsoSettingsScope { |
| 77 | 90 | } |
| 78 | 91 | // Channels, including math channels |
| 79 | 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 | 75 | if (store->contains("format")) scope.horizontal.format = (Dso::GraphFormat)store->value("format").toInt(); |
| 76 | 76 | if (store->contains("frequencybase")) |
| 77 | 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 | 79 | QString name; |
| 80 | 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 | 83 | if (store->contains("timebase")) scope.horizontal.timebase = store->value("timebase").toDouble(); |
| 84 | 84 | if (store->contains("recordLength")) scope.horizontal.recordLength = store->value("recordLength").toUInt(); |
| ... | ... | @@ -100,6 +100,17 @@ void DsoSettings::load() { |
| 100 | 100 | scope.spectrum[channel].magnitude = store->value("magnitude").toDouble(); |
| 101 | 101 | if (store->contains("offset")) scope.spectrum[channel].offset = store->value("offset").toDouble(); |
| 102 | 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 | 114 | store->endGroup(); |
| 104 | 115 | } |
| 105 | 116 | // Vertical axis |
| ... | ... | @@ -112,6 +123,17 @@ void DsoSettings::load() { |
| 112 | 123 | if (store->contains("offset")) scope.voltage[channel].offset = store->value("offset").toDouble(); |
| 113 | 124 | if (store->contains("trigger")) scope.voltage[channel].trigger = store->value("trigger").toDouble(); |
| 114 | 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 | 137 | store->endGroup(); |
| 116 | 138 | } |
| 117 | 139 | |
| ... | ... | @@ -159,7 +181,10 @@ void DsoSettings::load() { |
| 159 | 181 | if (store->contains("interpolation")) |
| 160 | 182 | view.interpolation = (Dso::InterpolationMode)store->value("interpolation").toInt(); |
| 161 | 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 | 188 | store->endGroup(); |
| 164 | 189 | |
| 165 | 190 | store->beginGroup("window"); |
| ... | ... | @@ -184,8 +209,8 @@ void DsoSettings::save() { |
| 184 | 209 | store->beginGroup("horizontal"); |
| 185 | 210 | store->setValue("format", scope.horizontal.format); |
| 186 | 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 | 214 | store->setValue("timebase", scope.horizontal.timebase); |
| 190 | 215 | store->setValue("recordLength", scope.horizontal.recordLength); |
| 191 | 216 | store->setValue("samplerate", scope.horizontal.samplerate); |
| ... | ... | @@ -205,6 +230,16 @@ void DsoSettings::save() { |
| 205 | 230 | store->setValue("magnitude", scope.spectrum[channel].magnitude); |
| 206 | 231 | store->setValue("offset", scope.spectrum[channel].offset); |
| 207 | 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 | 243 | store->endGroup(); |
| 209 | 244 | } |
| 210 | 245 | // Vertical axis |
| ... | ... | @@ -216,6 +251,16 @@ void DsoSettings::save() { |
| 216 | 251 | store->setValue("offset", scope.voltage[channel].offset); |
| 217 | 252 | store->setValue("trigger", scope.voltage[channel].trigger); |
| 218 | 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 | 264 | store->endGroup(); |
| 220 | 265 | } |
| 221 | 266 | |
| ... | ... | @@ -259,6 +304,8 @@ void DsoSettings::save() { |
| 259 | 304 | store->setValue("interpolation", view.interpolation); |
| 260 | 305 | store->setValue("screenColorImages", view.screenColorImages); |
| 261 | 306 | store->setValue("zoom", view.zoom); |
| 307 | + store->setValue("cursorGridPosition", view.cursorGridPosition); | |
| 308 | + store->setValue("cursorsVisible", view.cursorsVisible); | |
| 262 | 309 | store->endGroup(); |
| 263 | 310 | |
| 264 | 311 | store->beginGroup("window"); | ... | ... |
openhantek/src/viewsettings.h
| ... | ... | @@ -42,6 +42,8 @@ struct DsoSettingsView { |
| 42 | 42 | Dso::InterpolationMode interpolation = Dso::INTERPOLATION_LINEAR; ///< Interpolation mode for the graph |
| 43 | 43 | bool screenColorImages = false; ///< true exports images with screen colors |
| 44 | 44 | bool zoom = false; ///< true if the magnified scope is enabled |
| 45 | + Qt::ToolBarArea cursorGridPosition = Qt::RightToolBarArea; | |
| 46 | + bool cursorsVisible = false; | |
| 45 | 47 | |
| 46 | 48 | unsigned digitalPhosphorDraws() const { |
| 47 | 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->mode = mode; } |
| 160 | 160 | |
| 161 | 161 | /// \brief Generic initializations. |
| 162 | 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 | 178 | /// \brief Resets the ::steppedTo flag after the value has been changed. | ... | ... |