#include "widegraph.h" #include #include #include #include #include #include #include #include #include #include "ui_widegraph.h" #include "Configuration.hpp" #include "DriftingDateTime.h" #include "EventFilter.hpp" #include "MessageBox.hpp" #include "SettingsGroup.hpp" #include "varicode.h" #include "moc_widegraph.cpp" Q_DECLARE_LOGGING_CATEGORY(widegraph_js8) namespace { auto const user_defined = QObject::tr ("User Defined"); // Time formats; we're likely only ever to use the second. constexpr QStringView TIME_FORMAT_MINS = u"hh:mm"; constexpr QStringView TIME_FORMAT_SECS = u"hh:mm:ss"; constexpr auto timeFormat(int const period) { return period < 60 ? TIME_FORMAT_SECS : TIME_FORMAT_MINS; } // Set the spinbox to the value, ensuring that signals are // blocked during the set operation and restoring the prior // blocked state afterward. void setValueBlocked(int const value, QSpinBox * block) { QSignalBlocker blocker(block); block->setValue(value); }; // Set the dial to the value, ensuring that signals are // blocked during the set operation and restoring the prior // blocked state afterward. void setValueBlocked(int const value, QDial * block) { QSignalBlocker blocker(block); block->setValue(value); }; // Set the checkbox to the value, ensuring that signals are // blocked during the set operation and restoring the prior // blocked state afterward. void setValueBlocked(bool const value, QCheckBox * block) { QSignalBlocker blocker(block); block->setChecked(value); }; } WideGraph::WideGraph(QSettings * settings, QWidget * parent) : QWidget {parent} , ui {new Ui::WideGraph} , m_settings {settings} , m_drawTimer {new QTimer(this)} , m_autoSyncTimer {new QTimer(this)} , m_palettes_path {":/Palettes"} , m_timeFormat {timeFormat(m_TRperiod)} { ui->setupUi(this); setMaximumHeight (880); ui->splitter->setChildrenCollapsible(false); ui->splitter->setCollapsible(ui->splitter->indexOf(ui->controls_widget), false); ui->splitter->updateGeometry(); // If the escape key is pressed while the filter center spin box has focus, // put the default value in the filter center field. ui->filterCenterSpinBox->installEventFilter(new EventFilter::EscapeKeyPress([this](QKeyEvent *) { setFilterCenter(1500); return true; }, this)); // If the escape key is pressed while the filter width spin box has focus, // put the default value in the filter width field. ui->filterWidthSpinBox->installEventFilter(new EventFilter::EscapeKeyPress([this](QKeyEvent *) { setFilterWidth(2000); return true; }, this)); // The filter center and width spin boxes are the lead controls; any time the associated // dials change, ensure the spin boxes are updated to the value of the dials. Same thing // in the reverse direction, but with events blocked to avoid recursion. connect(ui->filterCenterDial, &QDial::valueChanged, ui->filterCenterSpinBox, &QSpinBox::setValue); connect(ui->filterWidthDial, &QDial::valueChanged, ui->filterWidthSpinBox, &QSpinBox::setValue); connect(ui->filterCenterSpinBox, QOverload::of(&QSpinBox::valueChanged), this, [this](int const value) { setValueBlocked(value, ui->filterCenterDial); }); connect(ui->filterWidthSpinBox, QOverload::of(&QSpinBox::valueChanged), this, [this](int const value) { setValueBlocked(value, ui->filterWidthDial); }); ui->widePlot->setCursor(Qt::CrossCursor); ui->widePlot->setMaximumWidth(WF::MaxScreenWidth); ui->widePlot->setMaximumHeight(800); ui->widePlot->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->widePlot, &CPlotter::customContextMenuRequested, this, [this](const QPoint &pos){ auto menu = new QMenu(this); auto const f = ui->widePlot->frequencyAt(pos.x()); auto offsetAction = menu->addAction(QString("Set &Offset to %1 Hz").arg(f)); connect(offsetAction, &QAction::triggered, this, [this, f](){ ui->offsetSpinBox->setValue(f); }); menu->addSeparator(); if(m_filterEnabled){ auto disableAction = menu->addAction(QString("&Disable Filter")); connect(disableAction, &QAction::triggered, this, [this](){ ui->filterCheckBox->setChecked(false); }); } auto centerAction = menu->addAction(QString("Set Filter &Center to %1 Hz").arg(f)); connect(centerAction, &QAction::triggered, this, [this, f](){ ui->filterCenterSpinBox->setValue(f); ui->filterCheckBox->setChecked(true); }); auto widthMenu = menu->addMenu("Set Filter &Width to..."); auto widths = QList{ 25, 50, 75, 100, 250, 500, 750, 1000, 1500, 2000 }; foreach(auto width, widths){ if(width < m_filterMinWidth){ continue; } auto widthAction = widthMenu->addAction(QString("%1 Hz").arg(width)); connect(widthAction, &QAction::triggered, this, [this, width](){ ui->filterWidthSpinBox->setValue(width); ui->filterCheckBox->setChecked(true); }); } menu->popup(ui->widePlot->mapToGlobal(pos)); }); connect(ui->widePlot, &CPlotter::changeFreq, this, &WideGraph::changeFreq); { //Restore user's settings SettingsGroup g {m_settings, "WideGraph"}; restoreGeometry (m_settings->value ("geometry", saveGeometry ()).toByteArray ()); ui->widePlot->setPlotZero(m_settings->value("PlotZero", 0).toInt()); ui->widePlot->setPlotGain(m_settings->value("PlotGain", 0).toInt()); ui->widePlot->setPlot2dGain(m_settings->value("Plot2dGain", 0).toInt()); ui->widePlot->setPlot2dZero(m_settings->value("Plot2dZero", 0).toInt()); ui->widePlot->setFlatten(m_settings->value("Flatten", true).toBool()); ui->widePlot->setBinsPerPixel(m_settings->value("BinsPerPixel", 2).toInt()); ui->widePlot->setPercent2D(m_settings->value("Percent2D", 0).toInt()); ui->zeroSlider->setValue(ui->widePlot->plotZero()); ui->gainSlider->setValue(ui->widePlot->plotGain()); ui->gain2dSlider->setValue(ui->widePlot->plot2dGain()); ui->zero2dSlider->setValue(ui->widePlot->plot2dZero()); ui->cbFlatten->setChecked(ui->widePlot->flatten()); ui->bppSpinBox->setValue(ui->widePlot->binsPerPixel()); ui->sbPercent2dPlot->setValue(ui->widePlot->percent2D()); m_nsmo=m_settings->value("SmoothYellow",1).toInt(); ui->smoSpinBox->setValue(m_nsmo); m_waterfallAvg = m_settings->value("WaterfallAvg", 1).toInt(); ui->waterfallAvgSpinBox->setValue(m_waterfallAvg); ui->widePlot->setWaterfallAvg(m_waterfallAvg); ui->widePlot->setSpectrum(m_settings->value("WaterfallSpectrum", QVariant::fromValue(WF::Spectrum::Current)).value()); if(ui->widePlot->spectrum() == WF::Spectrum::Current) ui->spec2dComboBox->setCurrentIndex(0); if(ui->widePlot->spectrum() == WF::Spectrum::Cumulative) ui->spec2dComboBox->setCurrentIndex(1); if(ui->widePlot->spectrum() == WF::Spectrum::LinearAvg) ui->spec2dComboBox->setCurrentIndex(2); ui->widePlot->setStartFreq(m_settings->value("StartFreq", 500).toInt()); ui->centerSpinBox->setValue(m_settings->value("CenterOffset", 1500).toInt()); ui->fStartSpinBox->setValue(ui->widePlot->startFreq()); m_waterfallPalette=m_settings->value("WaterfallPalette","Default").toString(); m_userPalette = WF::Palette {m_settings->value("UserPalette").value ()}; ui->controls_widget->setVisible(!m_settings->value("HideControls", false).toBool()); ui->fpsSpinBox->setValue(m_settings->value ("WaterfallFPS", 4).toInt()); ui->decodeAttemptCheckBox->setChecked(m_settings->value("DisplayDecodeAttempts", false).toBool()); ui->autoDriftAutoStopCheckBox->setChecked(m_settings->value ("StopAutoSyncOnDecode", true).toBool()); ui->autoDriftStopSpinBox->setValue(m_settings->value ("StopAutoSyncAfter", 1).toInt()); auto splitState = m_settings->value("SplitState").toByteArray(); if(!splitState.isEmpty()){ ui->splitter->restoreState(splitState); } setFilterCenter (m_settings->value("FilterCenter", 1500).toInt()); setFilterWidth (m_settings->value("FilterWidth", 2000).toInt()); setFilterOpacityPercent(m_settings->value("FilterOpacityPercent", 50).toInt()); setFilterEnabled (m_settings->value("FilterEnabled", true).toBool()); } for (auto const & file : m_palettes_path.entryList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) { auto const item = QFileInfo(file).completeBaseName(); ui->paletteComboBox->addItem(item); if (item == m_waterfallPalette) { ui->paletteComboBox->setCurrentIndex(ui->paletteComboBox->count() - 1); } } ui->paletteComboBox->addItem(user_defined); if (user_defined == m_waterfallPalette) { ui->paletteComboBox->setCurrentIndex(ui->paletteComboBox->count() - 1); } readPalette(); connect(m_drawTimer, &QTimer::timeout, this, [this] { auto const fps = std::clamp(ui->fpsSpinBox->value(), 1, 100); qint64 const loopMs = 1000 / (fps * devicePixelRatio()) * m_waterfallAvg; QElapsedTimer timer; // Start the elapsed timer and do the drawing, unless we're paused. timer.start(); if (!m_paused) { QMutexLocker lock(&m_drawLock); // Draw the tr cycle horizontal lines if needed. auto const now = DriftingDateTime::currentDateTimeUtc(); auto const secondInToday = now.time().msecsSinceStartOfDay() / 1000; int const secondInPeriod = secondInToday % m_TRperiod; if (secondInPeriod < m_lastSecondInPeriod) { ui->widePlot->drawLine(now.toString(m_timeFormat).append(m_band)); } m_lastSecondInPeriod = secondInPeriod; // Draw the data, handing the plotter a copy, and informing them // of our current state. ui->widePlot->drawData(m_swide, m_state); // Whatever our state was, the sink is now drained until new data // arrives; data we draw until then will duplicate the operation // we just performed. m_state = WF::Sink::Drained; } // Compute the processing time and adjust loop to hit the next frame. m_drawTimer->start(std::max(std::chrono::milliseconds(loopMs - timer.elapsed()), std::chrono::milliseconds::zero())); }); m_drawTimer->setTimerType(Qt::PreciseTimer); m_drawTimer->setSingleShot(true); m_drawTimer->start(100); //### Don't change the 100 ms! ### } WideGraph::~WideGraph() = default; void WideGraph::closeEvent (QCloseEvent * event) { saveSettings(); QWidget::closeEvent(event); } void WideGraph::saveSettings() //saveSettings { SettingsGroup g {m_settings, "WideGraph"}; m_settings->setValue ("geometry", saveGeometry ()); m_settings->setValue ("PlotZero", ui->widePlot->plotZero()); m_settings->setValue ("PlotGain", ui->widePlot->plotGain()); m_settings->setValue ("Plot2dGain", ui->widePlot->plot2dGain()); m_settings->setValue ("Plot2dZero", ui->widePlot->plot2dZero()); m_settings->setValue ("SmoothYellow", ui->smoSpinBox->value ()); m_settings->setValue ("Percent2D", ui->widePlot->percent2D()); m_settings->setValue ("WaterfallAvg", ui->waterfallAvgSpinBox->value ()); m_settings->setValue ("WaterfallSpectrum", QVariant::fromValue(ui->widePlot->spectrum())); m_settings->setValue ("BinsPerPixel", ui->widePlot->binsPerPixel ()); m_settings->setValue ("StartFreq", ui->widePlot->startFreq ()); m_settings->setValue ("WaterfallPalette", m_waterfallPalette); m_settings->setValue ("UserPalette", QVariant::fromValue (m_userPalette.colours ())); m_settings->setValue ("Flatten", ui->widePlot->flatten()); m_settings->setValue ("HideControls", ui->controls_widget->isHidden ()); m_settings->setValue ("CenterOffset", ui->centerSpinBox->value()); m_settings->setValue ("FilterCenter", m_filterCenter); m_settings->setValue ("FilterWidth", m_filterWidth); m_settings->setValue ("FilterEnabled", m_filterEnabled); m_settings->setValue ("FilterOpacityPercent", ui->filterOpacitySpinBox->value()); m_settings->setValue ("SplitState", ui->splitter->saveState()); m_settings->setValue ("WaterfallFPS", ui->fpsSpinBox->value()); m_settings->setValue ("DisplayDecodeAttempts", ui->decodeAttemptCheckBox->isChecked()); m_settings->setValue ("StopAutoSyncOnDecode", ui->autoDriftAutoStopCheckBox->isChecked()); m_settings->setValue ("StopAutoSyncAfter", ui->autoDriftStopSpinBox->value()); } bool WideGraph::shouldDisplayDecodeAttempts() const { return ui->decodeAttemptCheckBox->isChecked(); } bool WideGraph::isAutoSyncEnabled() const { // enabled if we're auto drifting // and we are not auto stopping // or if we are auto stopping, // we have auto sync decodes left return ui->autoDriftButton->isChecked() && ( !ui->autoDriftAutoStopCheckBox->isChecked() || m_autoSyncDecodesLeft > 0 ); } bool WideGraph::shouldAutoSyncSubmode(int const submode) const { return isAutoSyncEnabled() && ( submode == Varicode::JS8CallSlow || submode == Varicode::JS8CallNormal // || submode == Varicode::JS8CallFast // || submode == Varicode::JS8CallTurbo // || submode == Varicode::JS8CallUltra ); } void WideGraph::notifyDriftedSignalsDecoded(int const signalsDecoded) { //qCDebug(widegraph_js8) << "decoded" << signalsDecoded << "with" << m_autoSyncDecodesLeft << "left"; m_autoSyncDecodesLeft -= signalsDecoded; if(ui->autoDriftAutoStopCheckBox->isChecked() && m_autoSyncDecodesLeft <= 0) { ui->autoDriftButton->setChecked(false); } } void WideGraph::on_autoDriftButton_toggled(bool const checked) { if (!m_autoSyncConnected) { connect(m_autoSyncTimer, &QTimer::timeout, this, [this]() { // if auto drift isn't checked, don't worry about this... if (!ui->autoDriftButton->isChecked()) return; // uncheck after timeout if (m_autoSyncTimeLeft == 0) { ui->autoDriftButton->setChecked(false); return; } // set new text and decrement timeleft auto const text = ui->autoDriftButton->text(); auto const newText = QString("%1 (%2)") .arg(text.left(text.indexOf("(")).trimmed()) .arg(m_autoSyncTimeLeft--); ui->autoDriftButton->setText(newText); }); m_autoSyncConnected = true; } // if in the future we want to auto sync timeout after a time period auto const autoSyncTimeout = false; auto text = ui->autoDriftButton->text(); if (autoSyncTimeout) { if (checked) { m_autoSyncTimeLeft = 120; m_autoSyncTimer->setInterval(1000); m_autoSyncTimer->start(); ui->autoDriftButton->setText(QString("%1 (%2)") .arg(text.replace("Start", "Stop")) .arg(m_autoSyncTimeLeft--)); } else { m_autoSyncTimeLeft = 0; m_autoSyncTimer->stop(); ui->autoDriftButton->setText(text.left(text.indexOf("(")).trimmed().replace("Stop", "Start")); } } else { if (checked) { m_autoSyncDecodesLeft = ui->autoDriftStopSpinBox->value(); ui->autoDriftButton->setText(text.left(text.indexOf("(")).trimmed().replace("Start", "Stop")); ui->autoDriftStopSpinBox->setEnabled(false); } else { m_autoSyncDecodesLeft = 0; ui->autoDriftButton->setText(text.left(text.indexOf("(")).trimmed().replace("Stop", "Start")); ui->autoDriftStopSpinBox->setEnabled(true); } } } void WideGraph::drawDecodeLine(QColor const & color, int const ia, int const ib) { ui->widePlot->drawDecodeLine(color, ia, ib); } void WideGraph::drawHorizontalLine(QColor const & color, int const x, int const width) { ui->widePlot->drawHorizontalLine(color, x, width); } void WideGraph::dataSink(WF::SPlot const & s, float const df3) { QMutexLocker lock(&m_drawLock); // If we need a fresh picture, just copy the entirety of the inbound // data. Otherwise, we're somewhere in the process of averaging data, // so add to what we've already accumulated. if (m_waterfallNow == 0) { m_splot = s; } else { std::transform(s.begin(), s.end(), m_splot.begin(), m_splot.begin(), std::plus<>{}); } // We can be confident at this point we've got summary data in the // sink that needs to be drained to the plotter. m_state |= WF::Sink::Summary; // Either way, that was another round; see if we've hit the point at // which we should prepare results to be flushed to the plotter. Note // that m_waterfallAvg can change at any time, so we must be defensive // here; it could have been a high value last round, and is now a low // one. if (++m_waterfallNow >= m_waterfallAvg) { // We've now hit the averaging threshold, and are ready to commit // data to be flushed to the plotter, which is a bit of a schlep: // // 1. Each source value must be averaged over the number of // accumulations that it took to get to this point. // 2. Source values must be reduced from bins to pixels. // 3. Values must be converted from power scaled to dB scaled. // // Fortunately, we can manage that in a single pass, and can work // only on the data to be displayed, rather than all of it. auto const bpp = ui->widePlot->binsPerPixel(); auto sit = m_splot.begin() + static_cast(ui->widePlot->startFreq() / df3 + 0.5f); auto it = m_swide.begin(); auto const end = it + std::min(m_swide.size(), static_cast(5000.0f / (bpp * df3))); auto const avg = [runs = m_waterfallNow](auto const value) { return value / runs; }; for (; it != end; ++it, sit += bpp) { *it = 10.0f * std::log10(bpp * std::transform_reduce(sit, sit + bpp, 0.0f, std::plus<>{}, avg)); } // Next round, we'll need a fresh picture, and we've now progressed // to having current data in the sink. m_waterfallNow = 0; m_state |= WF::Sink::Current; } } void WideGraph::on_bppSpinBox_valueChanged(int const n) { ui->widePlot->setBinsPerPixel(n); } void WideGraph::on_qsyPushButton_clicked() { emit qsy(freq() - centerFreq()); } void WideGraph::on_offsetSpinBox_valueChanged(int const n) { if (n == freq()) return; // TODO: jsherer - here's where we'd set minimum frequency again (later?) auto const newFreq = qMax(0, n); setFreq(newFreq); emit changeFreq(newFreq); } void WideGraph::on_waterfallAvgSpinBox_valueChanged(int const n) { m_waterfallAvg = n; ui->widePlot->setWaterfallAvg(n); } void WideGraph::keyPressEvent(QKeyEvent * event) { switch (event->key()) { case Qt::Key_F11: emit f11f12(11); break; case Qt::Key_F12: emit f11f12(12); break; default: event->ignore(); } } int WideGraph::freq() const { return ui->widePlot->freq(); } int WideGraph::centerFreq() const { return ui->centerSpinBox->value(); } int WideGraph::nStartFreq() const { return ui->widePlot->startFreq(); } int WideGraph::filterMinimum() const { return std::clamp(m_filterCenter - m_filterWidth / 2, 0, 5000); } int WideGraph::filterMaximum() const { return std::clamp(m_filterCenter + m_filterWidth / 2, 0, 5000); } bool WideGraph::filterEnabled() const { return m_filterEnabled; } void WideGraph::setFilterCenter(int const value) { m_filterCenter = value; setValueBlocked(value, ui->filterCenterSpinBox); setValueBlocked(value, ui->filterCenterDial); ui->widePlot->setFilter(m_filterCenter, m_filterWidth); } void WideGraph::setFilterWidth(int const value) { m_filterWidth = value; setValueBlocked(value, ui->filterWidthSpinBox); setValueBlocked(value, ui->filterWidthDial); ui->widePlot->setFilter(m_filterCenter, m_filterWidth); } void WideGraph::setFilterMinimumBandwidth(int const width) { m_filterMinWidth = width; ui->filterWidthSpinBox->setMinimum(width); ui->filterWidthDial->setMinimum(width); setFilterWidth(std::max(m_filterWidth, width)); } void WideGraph::setFilterEnabled(bool enabled) { m_filterEnabled = enabled; ui->filterCenterSyncButton->setEnabled(enabled); ui->filterCenterSpinBox->setEnabled(enabled); ui->filterCenterDial->setEnabled(enabled); ui->filterWidthSpinBox->setEnabled(enabled); ui->filterWidthDial->setEnabled(enabled); setValueBlocked(enabled, ui->filterCheckBox); ui->widePlot->setFilterEnabled(enabled); } void WideGraph::setFilterOpacityPercent(int const n) { setValueBlocked(n, ui->filterOpacitySpinBox); ui->widePlot->setFilterOpacity(int((float(n)/100.0)*255)); } void WideGraph::setPeriod(int const ntrperiod) { m_TRperiod = ntrperiod; m_timeFormat = timeFormat(m_TRperiod); } void WideGraph::setFreq(int const n) { emit setXIT(n); ui->widePlot->setFreq(n); ui->offsetSpinBox->setValue(n); } void WideGraph::setSubMode(int const n) { ui->widePlot->setSubMode(n); } void WideGraph::on_spec2dComboBox_currentIndexChanged(int const index) { ui->smoSpinBox->setEnabled(false); switch (index) { case 0: ui->widePlot->setSpectrum(WF::Spectrum::Current); break; case 1: ui->widePlot->setSpectrum(WF::Spectrum::Cumulative); break; case 2: ui->widePlot->setSpectrum(WF::Spectrum::LinearAvg); ui->smoSpinBox->setEnabled(true); break; } } void WideGraph::setDialFreq(float const dialFreq) { ui->widePlot->setDialFreq(dialFreq); } void WideGraph::setTimeControlsVisible(bool const visible) { setControlsVisible(visible, false); ui->tabWidget->setCurrentWidget(ui->timingTab); } bool WideGraph::timeControlsVisible() const { return controlsVisible() && ui->tabWidget->currentWidget() == ui->timingTab; } void WideGraph::setControlsVisible(bool const visible, bool const controlTab) { if (ui->controls_widget->isVisible() != visible) { if (visible) { if (m_sizes.isEmpty()) { auto const width = ui->splitter->width(); m_sizes = {width, width / 4}; } ui->splitter->setSizes(m_sizes); if (controlTab) { ui->tabWidget->setCurrentWidget(ui->controlTab); } } else { m_sizes = ui->splitter->sizes(); } ui->controls_widget->setVisible(visible); } } bool WideGraph::controlsVisible() const { return ui->controls_widget->isVisible(); } void WideGraph::setBand(QString const & band) { m_band = QString(4, ' ').append(band); } void WideGraph::on_fStartSpinBox_valueChanged(int const n) { ui->widePlot->setStartFreq(n); } void WideGraph::readPalette() { try { ui->widePlot->setColors(user_defined == m_waterfallPalette ? WF::Palette{m_userPalette}.interpolate() : WF::Palette{m_palettes_path.absoluteFilePath(m_waterfallPalette + ".pal")}.interpolate()); } catch (std::exception const & e) { MessageBox::warning_message(this, tr("Read Palette"), e.what()); } } void WideGraph::on_paletteComboBox_activated(int const palette_index) { m_waterfallPalette = ui->paletteComboBox->itemText(palette_index); readPalette(); } void WideGraph::on_cbFlatten_toggled(bool const flatten) { ui->widePlot->setFlatten(flatten); } void WideGraph::on_adjust_palette_push_button_clicked(bool) { try { if (m_userPalette.design ()) { m_waterfallPalette = user_defined; ui->paletteComboBox->setCurrentText(m_waterfallPalette); readPalette(); } } catch (std::exception const & e) { MessageBox::warning_message(this, tr("Read Palette"), e.what()); } } void WideGraph::on_gainSlider_valueChanged(int const value) { ui->widePlot->setPlotGain(value); } void WideGraph::on_zeroSlider_valueChanged(int const value) { ui->widePlot->setPlotZero(value); } void WideGraph::on_gain2dSlider_valueChanged(int const value) { ui->widePlot->setPlot2dGain(value); } void WideGraph::on_zero2dSlider_valueChanged(int const value) { ui->widePlot->setPlot2dZero(value); } void WideGraph::on_smoSpinBox_valueChanged(int const n) { m_nsmo = n; } int WideGraph::smoothYellow() const { return m_nsmo; } void WideGraph::on_sbPercent2dPlot_valueChanged(int const n) { ui->widePlot->setPercent2D(n); } void WideGraph::on_filterCenterSpinBox_valueChanged(int const value) { if (!ui->filterCenterSpinBox->hasFocus()) setFilterCenter(value); } void WideGraph::on_filterCenterSpinBox_editingFinished() { setFilterCenter(ui->filterCenterSpinBox->value()); } void WideGraph::on_filterWidthSpinBox_valueChanged(int const value) { if (!ui->filterWidthSpinBox->hasFocus()) setFilterWidth(value); } void WideGraph::on_filterWidthSpinBox_editingFinished() { setFilterWidth( ui->filterWidthSpinBox->value()); } void WideGraph::on_filterCenterSyncButton_clicked() { setFilterCenter(ui->offsetSpinBox->value()); } void WideGraph::on_filterCheckBox_toggled(bool const b) { setFilterEnabled(b); } void WideGraph::on_filterOpacitySpinBox_valueChanged(int const n) { setFilterOpacityPercent(n); } void WideGraph::on_driftSpinBox_valueChanged(int const n) { if (n != DriftingDateTime::drift()) setDrift(n); } void WideGraph::on_driftSyncButton_clicked() { auto const now = QDateTime::currentDateTimeUtc(); int const pos = m_TRperiod - (now.time().second() % m_TRperiod); int const neg = (now.time().second() % m_TRperiod) - m_TRperiod; auto const sec = abs(neg) < pos ? neg : pos; setDrift(sec * 1000); } void WideGraph::on_driftSyncEndButton_clicked() { auto const now = QDateTime::currentDateTimeUtc(); int const pos = m_TRperiod - (now.time().second() % m_TRperiod); int const neg = (now.time().second() % m_TRperiod) - m_TRperiod; auto const sec = abs(neg) < pos ? neg + 2 : pos - 2; setDrift(sec * 1000); } void WideGraph::on_driftSyncMinuteButton_clicked() { auto const now = QDateTime::currentDateTimeUtc(); auto const val = now.time().second(); auto const sec = val < 30 ? -val : 60 - val; setDrift(sec * 1000); } void WideGraph::on_driftSyncResetButton_clicked() { setDrift(0); } void WideGraph::setDrift(int const n) { auto const prev = drift(); DriftingDateTime::setDrift(n); qCDebug(widegraph_js8) << qSetRealNumberPrecision(12) << "Drift milliseconds:" << n; qCDebug(widegraph_js8) << qSetRealNumberPrecision(12) << "Clock time:" << QDateTime::currentDateTimeUtc(); qCDebug(widegraph_js8) << qSetRealNumberPrecision(12) << "Drifted time:" << DriftingDateTime::currentDateTimeUtc(); if (ui->driftSpinBox->value() != n) ui->driftSpinBox->setValue(n); emit drifted(prev, n); } int WideGraph::drift() const { return DriftingDateTime::drift(); } Q_LOGGING_CATEGORY(widegraph_js8, "widegraph.js8", QtWarningMsg)