diff --git a/plugins/channelrx/freqscanner/freqscanner.cpp b/plugins/channelrx/freqscanner/freqscanner.cpp index fa8628b2a..6bb33cf24 100644 --- a/plugins/channelrx/freqscanner/freqscanner.cpp +++ b/plugins/channelrx/freqscanner/freqscanner.cpp @@ -249,6 +249,7 @@ bool FreqScanner::handleMessage(const Message& cmd) } else if (MsgStartScan::match(cmd)) { + muteAll(); startScan(); return true; @@ -302,6 +303,9 @@ void FreqScanner::setDeviceCenterFrequency(qint64 frequency) void FreqScanner::initScan() { + if (m_scanChannelIndex < 0) { + applyChannelSetting(m_settings.m_channel); + } ChannelWebAPIUtils::setAudioMute(m_scanDeviceSetIndex, m_scanChannelIndex, true); if (m_centerFrequency != m_stepStartFrequency) { @@ -319,7 +323,6 @@ void FreqScanner::initScan() void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList& results) { - switch (m_state) { case IDLE: @@ -329,10 +332,10 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< { // Create ordered list of frequencies to scan QList frequencies; - for (int i = 0; i < m_settings.m_frequencies.size(); i++) + for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) { - if (m_settings.m_enabled[i]) { - frequencies.append(m_settings.m_frequencies[i]); + if (m_settings.m_frequencySettings[i].m_enabled) { + frequencies.append(m_settings.m_frequencySettings[i].m_frequency); } } std::sort(frequencies.begin(), frequencies.end()); @@ -390,11 +393,11 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< } // Are any frequencies in this new range? - for (int i = 0; i < m_settings.m_frequencies.size(); i++) + for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) { - if (m_settings.m_enabled[i] - && (m_settings.m_frequencies[i] >= nextCenterFrequency - usableBW / 2) - && (m_settings.m_frequencies[i] < nextCenterFrequency + usableBW / 2)) + if (m_settings.m_frequencySettings[i].m_enabled + && (m_settings.m_frequencySettings[i].m_frequency >= nextCenterFrequency - usableBW / 2) + && (m_settings.m_frequencySettings[i].m_frequency < nextCenterFrequency + usableBW / 2)) { freqInRange = true; break; @@ -416,51 +419,52 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< m_guiMessageQueue->push(msg); } - int frequency = m_scanResults[0].m_frequency; - Real maxPower = m_scanResults[0].m_power; + int frequency = -1; + FreqScannerSettings::FrequencySettings *frequencySettings = nullptr; + FreqScannerSettings::FrequencySettings *activeFrequencySettings = nullptr; if (m_settings.m_priority == FreqScannerSettings::MAX_POWER) { - // Find frequency with max power - for (int i = 1; i < m_scanResults.size(); i++) + Real maxPower = -200.0f; + + // Find frequency with max power that exceeds thresholds + for (int i = 0; i < m_scanResults.size(); i++) { - if (m_scanResults[i].m_power > maxPower) + frequencySettings = m_settings.getFrequencySettings(m_scanResults[i].m_frequency); + Real threshold = m_settings.getThreshold(frequencySettings); + if (m_scanResults[i].m_power >= threshold) { - frequency = m_scanResults[i].m_frequency; - maxPower = m_scanResults[i].m_power; + if (!activeFrequencySettings || (m_scanResults[i].m_power > maxPower)) + { + frequency = m_scanResults[i].m_frequency; + maxPower = m_scanResults[i].m_power; + activeFrequencySettings = frequencySettings; + } } } } else { // Find first frequency in list above threshold - for (int j = 0; j < m_settings.m_frequencies.size(); j++) + for (int i = 0; i < m_scanResults.size(); i++) { - for (int i = 0; i < m_scanResults.size(); i++) + frequencySettings = m_settings.getFrequencySettings(m_scanResults[i].m_frequency); + Real threshold = m_settings.getThreshold(frequencySettings); + if (m_scanResults[i].m_power >= threshold) { - if (m_scanResults[i].m_frequency == m_settings.m_frequencies[j]) - { - if (m_scanResults[i].m_power >= m_settings.m_threshold) - { - frequency = m_scanResults[i].m_frequency; - maxPower = m_scanResults[i].m_power; - goto found_freq; - } - } + frequency = m_scanResults[i].m_frequency; + activeFrequencySettings = frequencySettings; + break; } } - found_freq: ; } if (m_settings.m_mode != FreqScannerSettings::SCAN_ONLY) { - // Is power above threshold - if (maxPower >= m_settings.m_threshold) + // Were any frequencies found to be active? + //if (maxPower >= m_settings.m_threshold) + if (activeFrequencySettings) { - if (m_guiMessageQueue) { - m_guiMessageQueue->push(MsgReportActiveFrequency::create(frequency)); - } - // Tune device/channel to frequency int offset; if ((frequency < m_centerFrequency - usableBW / 2) || (frequency >= m_centerFrequency + usableBW / 2)) @@ -494,11 +498,28 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< //qDebug() << "Tuning to active freq:" << frequency << "m_centerFrequency" << m_centerFrequency << "nextCenterFrequency" << nextCenterFrequency << "offset: " << offset << "deviceset: R" << m_scanDeviceSetIndex << ":" << m_scanChannelIndex; + QString channel = m_settings.m_channel; + if (!activeFrequencySettings->m_channel.isEmpty()) { + channel = activeFrequencySettings->m_channel; + } + applyChannelSetting(channel); + + // Tune the channel ChannelWebAPIUtils::setFrequencyOffset(m_scanDeviceSetIndex, m_scanChannelIndex, offset); // Unmute the channel ChannelWebAPIUtils::setAudioMute(m_scanDeviceSetIndex, m_scanChannelIndex, false); + // Apply squelch + if (!activeFrequencySettings->m_squelch.isEmpty()) + { + bool ok; + Real squelch = activeFrequencySettings->m_squelch.toFloat(&ok); + if (ok) { + ChannelWebAPIUtils::patchChannelSetting(m_scanDeviceSetIndex, m_scanChannelIndex, "squelch", squelch); + } + } + m_activeFrequency = frequency; if (m_settings.m_mode == FreqScannerSettings::SINGLE) @@ -514,11 +535,16 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< // Wait for transmission to finish m_state = WAIT_FOR_END_TX; } + + // Becareful to only do this at the end here, as it can recursively call handleMessage with new settings + if (m_guiMessageQueue) { + m_guiMessageQueue->push(MsgReportActiveFrequency::create(m_activeFrequency)); + } } else { if (m_guiMessageQueue) { - m_guiMessageQueue->push(MsgStatus::create(QString("Scanning: No active channels - Max power %1 dB").arg(maxPower, 0, 'f', 1))); + m_guiMessageQueue->push(MsgStatus::create("Scanning...")); } } } @@ -545,7 +571,9 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< } // Wait until power drops below threshold - if (results[i].m_power < m_settings.m_threshold) + FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(m_activeFrequency); + Real threshold = m_settings.getThreshold(frequencySettings); + if (results[i].m_power < threshold) { m_timeoutTimer.setSingleShot(true); m_timeoutTimer.start((int)(m_settings.m_retransmitTime * 1000.0)); @@ -566,7 +594,9 @@ void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList< } // Check if power has returned to being above threshold - if (results[i].m_power >= m_settings.m_threshold) + FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(m_activeFrequency); + Real threshold = m_settings.getThreshold(frequencySettings); + if (results[i].m_power >= threshold) { m_timeoutTimer.stop(); m_state = WAIT_FOR_END_TX; @@ -598,6 +628,9 @@ void FreqScanner::calcScannerSampleRate(int channelBW, int basebandSampleRate, i // But ensure we have several bins per channel // Adjust sample rate, to ensure we don't get massive FFT size scannerSampleRate = basebandSampleRate; + if (scannerSampleRate < channelBW) { + channelBW = scannerSampleRate; // Prevent divide by 0 + } while (fftSize / (scannerSampleRate / channelBW) < minBinsPerChannel) { if (fftSize == maxFFTSize) { @@ -623,6 +656,46 @@ void FreqScanner::setCenterFrequency(qint64 frequency) } } +// Mute all channels +void FreqScanner::muteAll() +{ + QStringList channels; + + channels.append(m_settings.m_channel); + for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) + { + QString channel = m_settings.m_frequencySettings[i].m_channel; + if (!channel.isEmpty() && !channels.contains(channel)) { + channels.append(channel); + } + } + + const QRegExp re("R([0-9]+):([0-9]+)"); + for (const auto& channel : channels) + { + if (re.indexIn(channel) >= 0) + { + int deviceSetIndex = re.capturedTexts()[1].toInt(); + int scanChannelIndex = re.capturedTexts()[2].toInt(); + ChannelWebAPIUtils::setAudioMute(deviceSetIndex, scanChannelIndex, true); + } + } +} + +void FreqScanner::applyChannelSetting(const QString& channel) +{ + const QRegExp re("R([0-9]+):([0-9]+)"); + if (re.indexIn(channel) >= 0) + { + m_scanDeviceSetIndex = re.capturedTexts()[1].toInt(); + m_scanChannelIndex = re.capturedTexts()[2].toInt(); + } + else + { + qDebug() << "FreqScanner::applySettings: Failed to parse channel" << channel; + } +} + void FreqScanner::applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force) { qDebug() << "FreqScanner::applySettings:" @@ -640,20 +713,6 @@ void FreqScanner::applySettings(const FreqScannerSettings& settings, const QStri } } - if (settingsKeys.contains("channel") || force) - { - const QRegExp re("R([0-9]+):([0-9]+)"); - if (re.indexIn(settings.m_channel) >= 0) - { - m_scanDeviceSetIndex = re.capturedTexts()[1].toInt(); - m_scanChannelIndex = re.capturedTexts()[2].toInt(); - } - else - { - qDebug() << "FreqScanner::applySettings: Failed to parse channel" << settings.m_channel; - } - } - if (m_running) { FreqScannerBaseband::MsgConfigureFreqScannerBaseband *msg = FreqScannerBaseband::MsgConfigureFreqScannerBaseband::create(settings, settingsKeys, force); @@ -670,7 +729,7 @@ void FreqScanner::applySettings(const FreqScannerSettings& settings, const QStri webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } - if (settingsKeys.contains("frequencies") + if (settingsKeys.contains("frequencySettings") || settingsKeys.contains("priority") || settingsKeys.contains("measurement") || settingsKeys.contains("mode") @@ -782,7 +841,7 @@ void FreqScanner::webapiUpdateChannelSettings( if (channelSettingsKeys.contains("threshold")) { settings.m_threshold = response.getFreqScannerSettings()->getThreshold(); } - if (channelSettingsKeys.contains("frequencies")) + /*if (channelSettingsKeys.contains("frequencies")) { settings.m_frequencies.clear(); settings.m_enabled.clear(); @@ -801,7 +860,7 @@ void FreqScanner::webapiUpdateChannelSettings( } } } - } + }*/ if (channelSettingsKeys.contains("rgbColor")) { settings.m_rgbColor = response.getFreqScannerSettings()->getRgbColor(); } @@ -837,7 +896,7 @@ void FreqScanner::webapiUpdateChannelSettings( QList *FreqScanner::createFrequencyList(const FreqScannerSettings& settings) { QList *frequencies = new QList(); - for (int i = 0; i < settings.m_frequencies.size(); i++) + /*for (int i = 0; i < settings.m_frequencies.size(); i++) { SWGSDRangel::SWGFreqScannerFrequency *frequency = new SWGSDRangel::SWGFreqScannerFrequency(); frequency->init(); @@ -847,7 +906,7 @@ QList *FreqScanner::createFrequencyList( frequency->setNotes(new QString(settings.m_notes[i])); } frequencies->append(frequency); - } + }*/ return frequencies; } diff --git a/plugins/channelrx/freqscanner/freqscanner.h b/plugins/channelrx/freqscanner/freqscanner.h index 2a72796d2..7613e704d 100644 --- a/plugins/channelrx/freqscanner/freqscanner.h +++ b/plugins/channelrx/freqscanner/freqscanner.h @@ -407,6 +407,8 @@ private: void initScan(); void processScanResults(const QDateTime& fftStartTime, const QList& results); void setDeviceCenterFrequency(qint64 frequency); + void applyChannelSetting(const QString& channel); + void muteAll(); static QList *createFrequencyList(const FreqScannerSettings& settings); diff --git a/plugins/channelrx/freqscanner/freqscannergui.cpp b/plugins/channelrx/freqscanner/freqscannergui.cpp index cadc52692..a08247a79 100644 --- a/plugins/channelrx/freqscanner/freqscannergui.cpp +++ b/plugins/channelrx/freqscanner/freqscannergui.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "device/deviceset.h" #include "device/deviceuiset.h" @@ -37,6 +38,7 @@ #include "gui/dialogpositioner.h" #include "gui/decimaldelegate.h" #include "gui/frequencydelegate.h" +#include "gui/int64delegate.h" #include "gui/glspectrum.h" #include "channel/channelwebapiutils.h" @@ -103,9 +105,9 @@ bool FreqScannerGUI::handleMessage(const Message& message) m_basebandSampleRate = notif.getSampleRate(); if (m_basebandSampleRate != 0) { - ui->deltaFrequency->setValueRange(true, 7, 0, m_basebandSampleRate/2); + ui->deltaFrequency->setValueRange(true, 8, 0, m_basebandSampleRate/2); ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2)); - ui->channelBandwidth->setValueRange(true, 7, 0, m_basebandSampleRate); + ui->channelBandwidth->setValueRange(true, 8, 0, m_basebandSampleRate); } if (m_channelMarker.getBandwidth() == 0) { m_channelMarker.setBandwidth(m_basebandSampleRate); @@ -116,7 +118,8 @@ bool FreqScannerGUI::handleMessage(const Message& message) else if (FreqScanner::MsgReportChannels::match(message)) { FreqScanner::MsgReportChannels& report = (FreqScanner::MsgReportChannels&)message; - updateChannelsList(report.getChannels()); + m_availableChannels = report.getChannels(); + updateChannelsList(m_availableChannels); return true; } else if (FreqScanner::MsgStatus::match(message)) @@ -187,11 +190,14 @@ bool FreqScannerGUI::handleMessage(const Message& message) { qint64 freq = results[i].m_frequency; QList items = ui->table->findItems(QString::number(freq), Qt::MatchExactly); - for (auto item : items) { + for (auto item : items) + { int row = item->row(); QTableWidgetItem* powerItem = ui->table->item(row, COL_POWER); powerItem->setData(Qt::DisplayRole, results[i].m_power); - bool active = results[i].m_power >= m_settings.m_threshold; + FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(freq); + Real threshold = m_settings.getThreshold(frequencySettings); + bool active = results[i].m_power >= threshold; if (active) { powerItem->setBackground(Qt::darkGreen); @@ -206,10 +212,13 @@ bool FreqScannerGUI::handleMessage(const Message& message) return false; } -void FreqScannerGUI::updateChannelsList(const QList& channels) +void FreqScannerGUI::updateChannelsCombo(QComboBox *combo, const QList& channels, const QString& channel, bool empty) { - ui->channels->blockSignals(true); - ui->channels->clear(); + combo->blockSignals(true); + combo->clear(); + if (empty) { + combo->addItem(""); + } for (const auto& channel : channels) { @@ -217,21 +226,32 @@ void FreqScannerGUI::updateChannelsList(const QListgetDeviceSetIndex()) && (channel.m_channelIndex != m_freqScanner->getIndexInDeviceSet())) { QString name = QString("R%1:%2").arg(channel.m_deviceSetIndex).arg(channel.m_channelIndex); - ui->channels->addItem(name); + combo->addItem(name); } } // Channel can be created after this plugin, so select it // if the chosen channel appears - int channelIndex = ui->channels->findText(m_settings.m_channel); + int channelIndex = combo->findText(channel); if (channelIndex >= 0) { - ui->channels->setCurrentIndex(channelIndex); + combo->setCurrentIndex(channelIndex); } else { - ui->channels->setCurrentIndex(-1); // return to nothing selected + combo->setCurrentIndex(-1); // return to nothing selected } - ui->channels->blockSignals(false); + combo->blockSignals(false); +} + +void FreqScannerGUI::updateChannelsList(const QList& channels) +{ + updateChannelsCombo(ui->channels, channels, m_settings.m_channel, false); + + for (int row = 0; row < ui->table->rowCount(); row++) + { + QComboBox *combo = qobject_cast(ui->table->cellWidget(row, COL_CHANNEL)); + updateChannelsCombo(combo, channels, m_settings.m_frequencySettings[row].m_channel, true); + } } void FreqScannerGUI::on_channels_currentIndexChanged(int index) @@ -412,10 +432,10 @@ FreqScannerGUI::FreqScannerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - ui->deltaFrequency->setValueRange(true, 7, 0, 9999999); + ui->deltaFrequency->setValueRange(true, 8, 0, 9999999); ui->channelBandwidth->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->channelBandwidth->setValueRange(true, 7, 0, 9999999); + ui->channelBandwidth->setValueRange(true, 8, 0, 9999999); m_channelMarker.setColor(Qt::yellow); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); @@ -464,6 +484,9 @@ FreqScannerGUI::FreqScannerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B ui->table->setItemDelegateForColumn(COL_FREQUENCY, new FrequencyDelegate("Auto", 3)); ui->table->setItemDelegateForColumn(COL_POWER, new DecimalDelegate(1)); + ui->table->setItemDelegateForColumn(COL_CHANNEL_BW, new Int64Delegate(0, 10000000)); + ui->table->setItemDelegateForColumn(COL_TH, new DecimalDelegate(1, -120.0, 0.0)); + ui->table->setItemDelegateForColumn(COL_SQ, new DecimalDelegate(1, -120.0, 0.0)); connect(m_deviceUISet->m_spectrum->getSpectrumView(), &GLSpectrumView::updateAnnotations, this, &FreqScannerGUI::updateAnnotations); } @@ -531,9 +554,9 @@ void FreqScannerGUI::displaySettings() ui->table->blockSignals(true); ui->table->setRowCount(0); - for (int i = 0; i < m_settings.m_frequencies.size(); i++) + for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) { - addRow(m_settings.m_frequencies[i], m_settings.m_enabled[i], m_settings.m_notes[i]); + addRow(m_settings.m_frequencySettings[i]); updateAnnotation(i); } ui->table->blockSignals(false); @@ -584,7 +607,7 @@ void FreqScannerGUI::on_startStop_clicked(bool checked) } } -void FreqScannerGUI::addRow(qint64 frequency, bool enabled, const QString& notes) +void FreqScannerGUI::addRow(const FreqScannerSettings::FrequencySettings& frequencySettings) { int row = ui->table->rowCount(); ui->table->setRowCount(row + 1); @@ -594,11 +617,11 @@ void FreqScannerGUI::addRow(qint64 frequency, bool enabled, const QString& notes annotationItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->table->setItem(row, COL_ANNOTATION, annotationItem); - ui->table->setItem(row, COL_FREQUENCY, new QTableWidgetItem(QString("%1").arg(frequency))); + ui->table->setItem(row, COL_FREQUENCY, new QTableWidgetItem(QString("%1").arg(frequencySettings.m_frequency))); QTableWidgetItem *enableItem = new QTableWidgetItem(); enableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); - enableItem->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); + enableItem->setCheckState(frequencySettings.m_enabled ? Qt::Checked : Qt::Unchecked); ui->table->setItem(row, COL_ENABLE, enableItem); QTableWidgetItem* powerItem = new QTableWidgetItem(); @@ -610,13 +633,40 @@ void FreqScannerGUI::addRow(qint64 frequency, bool enabled, const QString& notes ui->table->setItem(row, COL_ACTIVE_COUNT, activeCountItem); activeCountItem->setData(Qt::DisplayRole, 0); - QTableWidgetItem* notesItem = new QTableWidgetItem(notes); + QTableWidgetItem* notesItem = new QTableWidgetItem(frequencySettings.m_notes); ui->table->setItem(row, COL_NOTES, notesItem); + + QComboBox *channelComboBox = new QComboBox(); + updateChannelsCombo(channelComboBox, m_availableChannels, frequencySettings.m_channel, true); + ui->table->setCellWidget(row, COL_CHANNEL, channelComboBox); + connect(channelComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_table_channel_currentIndexChanged); + + QTableWidgetItem* channelBandwidthItem = new QTableWidgetItem(frequencySettings.m_channelBandwidth); + ui->table->setItem(row, COL_CHANNEL_BW, channelBandwidthItem); + + QTableWidgetItem* thresholdItem = new QTableWidgetItem(frequencySettings.m_threshold); + ui->table->setItem(row, COL_TH, thresholdItem); + + QTableWidgetItem* squelchItem = new QTableWidgetItem(frequencySettings.m_squelch); + ui->table->setItem(row, COL_SQ, squelchItem); +} + +void FreqScannerGUI::on_table_channel_currentIndexChanged(int index) +{ + if (index >= 0) + { + QComboBox *combo = qobject_cast(sender()); + QModelIndex tableIndex = ui->table->indexAt(combo->pos()); + on_table_cellChanged(tableIndex.row(), tableIndex.column()); + } } void FreqScannerGUI::on_addSingle_clicked() { - addRow(0, true); + FreqScannerSettings::FrequencySettings frequencySettings; + frequencySettings.m_frequency = 0; + frequencySettings.m_enabled = true; + addRow(frequencySettings); } void FreqScannerGUI::on_addRange_clicked() @@ -626,11 +676,15 @@ void FreqScannerGUI::on_addRange_clicked() if (dialog.exec()) { blockApplySettings(true); - for (const auto f : dialog.m_frequencies) { - addRow(f, true); + for (const auto f : dialog.m_frequencies) + { + FreqScannerSettings::FrequencySettings frequencySettings; + frequencySettings.m_frequency = f; + frequencySettings.m_enabled = true; + addRow(frequencySettings); } blockApplySettings(false); - applySetting("frequencies"); + applySetting("frequencySettings"); } } @@ -642,11 +696,9 @@ void FreqScannerGUI::on_remove_clicked() { int row = ui->table->row(item); ui->table->removeRow(row); - m_settings.m_frequencies.removeAt(row); // table_cellChanged isn't called for removeRow - m_settings.m_enabled.removeAt(row); - m_settings.m_notes.removeAt(row); + m_settings.m_frequencySettings.removeAt(row); } - applySetting("frequencies"); + applySetting("frequencySettings"); } void FreqScannerGUI::on_removeInactive_clicked() @@ -656,12 +708,10 @@ void FreqScannerGUI::on_removeInactive_clicked() if (ui->table->item(i, COL_ACTIVE_COUNT)->data(Qt::DisplayRole).toInt() == 0) { ui->table->removeRow(i); - m_settings.m_frequencies.removeAt(i); // table_cellChanged isn't called for removeRow - m_settings.m_enabled.removeAt(i); - m_settings.m_notes.removeAt(i); + m_settings.m_frequencySettings.removeAt(i); } } - applySetting("frequencies"); + applySetting("frequencySettings"); } static QList takeRow(QTableWidget* table, int row) @@ -730,26 +780,49 @@ void FreqScannerGUI::on_table_cellChanged(int row, int column) if (column == COL_FREQUENCY) { qint64 value = item->text().toLongLong(); - while (m_settings.m_frequencies.size() <= row) + while (m_settings.m_frequencySettings.size() <= row) { - m_settings.m_frequencies.append(0); - m_settings.m_enabled.append(true); - m_settings.m_notes.append(""); + FreqScannerSettings::FrequencySettings frequencySettings; + frequencySettings.m_frequency = 0; + frequencySettings.m_enabled = true; + m_settings.m_frequencySettings.append(frequencySettings); } - m_settings.m_frequencies[row] = value; + m_settings.m_frequencySettings[row].m_frequency = value; updateAnnotation(row); - applySetting("frequencies"); + applySetting("frequencySettings"); } else if (column == COL_ENABLE) { - m_settings.m_enabled[row] = item->checkState() == Qt::Checked; - applySetting("frequencies"); + m_settings.m_frequencySettings[row].m_enabled = item->checkState() == Qt::Checked; + applySetting("frequencySettings"); } else if (column == COL_NOTES) { - m_settings.m_notes[row] = item->text(); - applySetting("frequencies"); + m_settings.m_frequencySettings[row].m_notes = item->text(); + applySetting("frequencySettings"); } + else if (column == COL_CHANNEL_BW) + { + m_settings.m_frequencySettings[row].m_channelBandwidth = item->text(); + applySetting("frequencySettings"); + } + else if (column == COL_TH) + { + m_settings.m_frequencySettings[row].m_threshold = item->text(); + applySetting("frequencySettings"); + } + else if (column == COL_SQ) + { + m_settings.m_frequencySettings[row].m_squelch = item->text(); + applySetting("frequencySettings"); + } + } + else if (column == COL_CHANNEL) + { + QComboBox *combo = qobject_cast(ui->table->cellWidget(row, COL_CHANNEL)); + m_settings.m_frequencySettings[row].m_channel = combo->currentText(); + qDebug() << "Setting row" << row << "to" << combo->currentText(); + applySetting("frequencySettings"); } } @@ -959,7 +1032,11 @@ void FreqScannerGUI::resizeTable() ui->table->setItem(row, COL_ENABLE, new QTableWidgetItem("Enable")); ui->table->setItem(row, COL_POWER, new QTableWidgetItem("-100.0")); ui->table->setItem(row, COL_ACTIVE_COUNT, new QTableWidgetItem("10000")); - ui->table->setItem(row, COL_NOTES, new QTableWidgetItem("Enter some notes")); + ui->table->setItem(row, COL_NOTES, new QTableWidgetItem("A channel name")); + ui->table->setItem(row, COL_CHANNEL, new QTableWidgetItem("Enter some notes")); + ui->table->setItem(row, COL_CHANNEL_BW, new QTableWidgetItem("100000000")); + ui->table->setItem(row, COL_TH, new QTableWidgetItem("-100.0")); + ui->table->setItem(row, COL_SQ, new QTableWidgetItem("-100.0")); ui->table->resizeColumnsToContents(); ui->table->setRowCount(row); } diff --git a/plugins/channelrx/freqscanner/freqscannergui.h b/plugins/channelrx/freqscanner/freqscannergui.h index f3759fc08..edc598950 100644 --- a/plugins/channelrx/freqscanner/freqscannergui.h +++ b/plugins/channelrx/freqscanner/freqscannergui.h @@ -32,6 +32,7 @@ class BasebandSampleSink; class FreqScanner; class FreqScannerGUI; class QMenu; +class QComboBox; namespace Ui { class FreqScannerGUI; @@ -81,6 +82,8 @@ private: QMenu *m_menu; + QList m_availableChannels; + explicit FreqScannerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~FreqScannerGUI(); @@ -92,9 +95,10 @@ private: bool handleMessage(const Message& message); void makeUIConnections(); void updateAbsoluteCenterFrequency(); - void addRow(qint64 frequency, bool enabled, const QString& notes = ""); + void addRow(const FreqScannerSettings::FrequencySettings& frequencySettings); void updateAnnotation(int row); void updateAnnotations(); + void updateChannelsCombo(QComboBox *combo, const QList& channels, const QString& channel, bool empty); void updateChannelsList(const QList& channels); void setAllEnabled(bool enable); @@ -110,7 +114,11 @@ private: COL_ENABLE, COL_POWER, COL_ACTIVE_COUNT, - COL_NOTES + COL_NOTES, + COL_CHANNEL, + COL_CHANNEL_BW, + COL_TH, + COL_SQ }; private slots: @@ -125,6 +133,7 @@ private slots: void on_measurement_currentIndexChanged(int index); void on_mode_currentIndexChanged(int index); void on_table_cellChanged(int row, int column); + void on_table_channel_currentIndexChanged(int index); void table_customContextMenuRequested(QPoint pos); void table_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); void table_sectionResized(int logicalIndex, int oldSize, int newSize); diff --git a/plugins/channelrx/freqscanner/freqscannergui.ui b/plugins/channelrx/freqscanner/freqscannergui.ui index 67e02cf93..76b287477 100644 --- a/plugins/channelrx/freqscanner/freqscannergui.ui +++ b/plugins/channelrx/freqscanner/freqscannergui.ui @@ -671,6 +671,46 @@ User notes about this frequency + + + Channel + + + Frequency specific channel to tune + +Leave blank for common setting + + + + + Ch BW (Hz) + + + Frequency specific channel BW + +Leave blank to use common setting + + + + + TH (dB) + + + Frequency specific threshold in dB + +Leave blank to use common setting + + + + + Sq (dB) + + + Frequency specific squelch in dB + +Leave blank for no adjustment + + @@ -783,18 +823,18 @@ QToolButton
gui/buttonswitch.h
- - RollupContents - QWidget -
gui/rollupcontents.h
- 1 -
ValueDialZ QWidget
gui/valuedialz.h
1
+ + RollupContents + QWidget +
gui/rollupcontents.h
+ 1 +
deltaFrequency diff --git a/plugins/channelrx/freqscanner/freqscannersettings.cpp b/plugins/channelrx/freqscanner/freqscannersettings.cpp index 4d166040e..edc83c9ce 100644 --- a/plugins/channelrx/freqscanner/freqscannersettings.cpp +++ b/plugins/channelrx/freqscanner/freqscannersettings.cpp @@ -16,6 +16,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "util/simpleserializer.h" #include "settings/serializable.h" @@ -40,6 +41,7 @@ void FreqScannerSettings::resetToDefaults() m_channelFrequencyOffset = 25000; m_threshold = -60.0f; m_channel = ""; + m_frequencySettings = {}; m_scanTime = 0.1f; m_retransmitTime = 2.0f; m_tuneTime = 100; @@ -73,9 +75,6 @@ QByteArray FreqScannerSettings::serialize() const s.writeS32(2, m_channelBandwidth); s.writeS32(3, m_channelFrequencyOffset); s.writeFloat(4, m_threshold); - s.writeList(5, m_notes); - s.writeList(6, m_enabled); - s.writeList(7, m_frequencies); s.writeString(8, m_channel); s.writeFloat(9, m_scanTime); s.writeFloat(10, m_retransmitTime); @@ -83,6 +82,7 @@ QByteArray FreqScannerSettings::serialize() const s.writeS32(12, (int)m_priority); s.writeS32(13, (int)m_measurement); s.writeS32(14, (int)m_mode); + s.writeList(15, m_frequencySettings); s.writeList(20, m_columnIndexes); s.writeList(21, m_columnSizes); @@ -128,22 +128,37 @@ bool FreqScannerSettings::deserialize(const QByteArray& data) d.readS32(2, &m_channelBandwidth, 25000); d.readS32(3, &m_channelFrequencyOffset, 25000); d.readFloat(4, &m_threshold, -60.0f); - d.readList(5, &m_notes); - d.readList(6, &m_enabled); - d.readList(7, &m_frequencies); d.readString(8, &m_channel); - while (m_notes.size() < m_frequencies.size()) { - m_notes.append(""); - } - while (m_enabled.size() < m_frequencies.size()) { - m_enabled.append(true); - } d.readFloat(9, &m_scanTime, 0.1f); d.readFloat(10, &m_retransmitTime, 2.0f); d.readS32(11, &m_tuneTime, 100); d.readS32(12, (int*)&m_priority, (int)MAX_POWER); d.readS32(13, (int*)&m_measurement, (int)PEAK); d.readS32(14, (int*)&m_mode, (int)CONTINUOUS); + d.readList(15, &m_frequencySettings); + if (m_frequencySettings.size() == 0) + { + // Try reading old settings + QList enabled; + QList frequencies; + + d.readList(6, &enabled); + d.readList(7, &frequencies); + if (frequencies.size() > 0) + { + for (int i = 0; i < frequencies.size(); i++) + { + FrequencySettings frequencySettings; + frequencySettings.m_frequency = frequencies[i]; + if (i < enabled.size()) { + frequencySettings.m_enabled = enabled[i]; + } else { + frequencySettings.m_enabled = true; + } + m_frequencySettings.append(frequencySettings); + } + } + } d.readList(20, &m_columnIndexes); d.readList(21, &m_columnSizes); @@ -200,10 +215,8 @@ void FreqScannerSettings::applySettings(const QStringList& settingsKeys, const F if (settingsKeys.contains("threshold")) { m_threshold = settings.m_threshold; } - if (settingsKeys.contains("frequencies")) { - m_frequencies = settings.m_frequencies; - m_enabled = settings.m_enabled; - m_notes = settings.m_notes; + if (settingsKeys.contains("frequencySettings")) { + m_frequencySettings = settings.m_frequencySettings; } if (settingsKeys.contains("channel")) { m_channel = settings.m_channel; @@ -280,13 +293,13 @@ QString FreqScannerSettings::getDebugString(const QStringList& settingsKeys, boo if (settingsKeys.contains("threshold") || force) { ostr << " m_threshold: " << m_threshold; } - if (settingsKeys.contains("frequencies") || force) + if (settingsKeys.contains("frequencySettings") || force) { QStringList s; - for (auto f : m_frequencies) { - s.append(QString::number(f)); + for (auto f : m_frequencySettings) { + s.append(QString::number(f.m_frequency)); } - ostr << " m_frequencies: " << s.join(",").toStdString(); + ostr << " m_frequencySettings: " << s.join(",").toStdString(); } if (settingsKeys.contains("channel") || force) { ostr << " m_channel: " << m_channel.toStdString(); @@ -348,3 +361,105 @@ QString FreqScannerSettings::getDebugString(const QStringList& settingsKeys, boo return QString(ostr.str().c_str()); } + + +QByteArray FreqScannerSettings::FrequencySettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS64(1, m_frequency); + s.writeBool(2, m_enabled); + s.writeString(3, m_notes); + s.writeString(4, m_threshold); + s.writeString(5, m_channel); + s.writeString(6, m_channelBandwidth); + s.writeString(7, m_squelch); + + return s.final(); +} + +bool FreqScannerSettings::FrequencySettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) { + return false; + } + + if (d.getVersion() == 1) + { + QByteArray blob; + + d.readS64(1, &m_frequency); + d.readBool(2, &m_enabled); + d.readString(3, &m_notes); + d.readString(4, &m_threshold); + d.readString(5, &m_channel); + d.readString(6, &m_channelBandwidth); + d.readString(7, &m_squelch); + + return true; + } + else + { + return false; + } +} + +QDataStream& operator<<(QDataStream& out, const FreqScannerSettings::FrequencySettings& settings) +{ + out << settings.serialize(); + return out; +} + +QDataStream& operator>>(QDataStream& in, FreqScannerSettings::FrequencySettings& settings) +{ + QByteArray data; + in >> data; + settings.deserialize(data); + return in; +} + +Real FreqScannerSettings::getThreshold(FreqScannerSettings::FrequencySettings *frequencySettings) const +{ + Real threshold = m_threshold; + if (!frequencySettings->m_threshold.isEmpty()) + { + bool ok; + Real perFrequencyThreshold = frequencySettings->m_threshold.toFloat(&ok); + if (ok) { + threshold = perFrequencyThreshold; + } else { + qDebug() << "FreqScannerSettings::getThreshold: Failed to parse" << frequencySettings->m_threshold << "as a float"; + } + } + return threshold; +} + +int FreqScannerSettings::getChannelBandwidth(FreqScannerSettings::FrequencySettings *frequencySettings) const +{ + int channelBandwidth = m_channelBandwidth; + if (!frequencySettings->m_channelBandwidth.isEmpty()) + { + bool ok; + Real perFrequencyChannelBandwidth = frequencySettings->m_channelBandwidth.toInt(&ok); + if (ok) { + channelBandwidth = perFrequencyChannelBandwidth; + } else { + qDebug() << "FreqScannerSettings::getChannelBandwidth: Failed to parse" << frequencySettings->m_channelBandwidth << "as an int"; + } + } + return channelBandwidth; +} + +FreqScannerSettings::FrequencySettings *FreqScannerSettings::getFrequencySettings(qint64 frequency) +{ + for (int i = 0; i < m_frequencySettings.size(); i++) + { + if (frequency == m_frequencySettings[i].m_frequency) { + return &this->m_frequencySettings[i]; + } + } + return nullptr; +} + diff --git a/plugins/channelrx/freqscanner/freqscannersettings.h b/plugins/channelrx/freqscanner/freqscannersettings.h index c50e7a899..e07ba7b49 100644 --- a/plugins/channelrx/freqscanner/freqscannersettings.h +++ b/plugins/channelrx/freqscanner/freqscannersettings.h @@ -27,7 +27,7 @@ class Serializable; class ChannelAPI; // Number of columns in the table -#define FREQSCANNER_COLUMNS 6 +#define FREQSCANNER_COLUMNS 10 struct FreqScannerSettings { @@ -41,14 +41,25 @@ struct FreqScannerSettings AvailableChannel& operator=(const AvailableChannel&) = default; }; + struct FrequencySettings { + qint64 m_frequency; + bool m_enabled; + QString m_notes; + QString m_threshold; // QStrings used, as we allow "" for no setting + QString m_channel; + QString m_channelBandwidth; + QString m_squelch; + + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + }; + qint32 m_inputFrequencyOffset; //!< Not modifable in GUI qint32 m_channelBandwidth; //!< Channel bandwidth qint32 m_channelFrequencyOffset;//!< Minium DC offset of tuned channel Real m_threshold; //!< Power threshold in dB - QList m_frequencies; //!< Frequencies to scan - QList m_enabled; //!< Whether corresponding frequency is enabled - QList m_notes; //!< User editable notes about this frequency QString m_channel; //!< Channel (E.g: R1:4) to tune to active frequency + QList m_frequencySettings; //!< Frequencies to scan and corresponding settings float m_scanTime; //!< In seconds float m_retransmitTime; //!< In seconds int m_tuneTime; //!< In milliseconds @@ -92,6 +103,9 @@ struct FreqScannerSettings bool deserialize(const QByteArray& data); void applySettings(const QStringList& settingsKeys, const FreqScannerSettings& settings); QString getDebugString(const QStringList& settingsKeys, bool force = false) const; + Real getThreshold(FreqScannerSettings::FrequencySettings *frequencySettings) const; + int getChannelBandwidth(FreqScannerSettings::FrequencySettings *frequencySettings) const; + FreqScannerSettings::FrequencySettings *getFrequencySettings(qint64 frequency); }; #endif /* INCLUDE_FREQSCANNERSETTINGS_H */ diff --git a/plugins/channelrx/freqscanner/freqscannersink.cpp b/plugins/channelrx/freqscanner/freqscannersink.cpp index a27c0b3cf..263b7b5e8 100644 --- a/plugins/channelrx/freqscanner/freqscannersink.cpp +++ b/plugins/channelrx/freqscanner/freqscannersink.cpp @@ -107,11 +107,11 @@ void FreqScannerSink::processOneSample(Complex &ci) FreqScanner::MsgScanResult* msg = FreqScanner::MsgScanResult::create(m_fftStartTime); QList& results = msg->getScanResults(); - for (int i = 0; i < m_settings.m_frequencies.size(); i++) + for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) { - if (m_settings.m_enabled[i]) + if (m_settings.m_frequencySettings[i].m_enabled) { - qint64 frequency = m_settings.m_frequencies[i]; + qint64 frequency = m_settings.m_frequencySettings[i].m_frequency; qint64 startFrequency = m_centerFrequency - m_scannerSampleRate / 2; qint64 diff = frequency - startFrequency; float binBW = m_scannerSampleRate / (float)m_fftSize; @@ -120,13 +120,24 @@ void FreqScannerSink::processOneSample(Complex &ci) if ((diff < m_scannerSampleRate * 0.875f) && (diff >= m_scannerSampleRate * 0.125f)) { int bin = std::round(diff / binBW); + int channelBins; + + if (m_settings.m_frequencySettings[i].m_channelBandwidth.isEmpty()) + { + channelBins = m_binsPerChannel; + } + else + { + int channelBW = m_settings.getChannelBandwidth(&m_settings.m_frequencySettings[i]); + channelBins = m_fftSize / (m_scannerSampleRate / (float)channelBW); + } // Calculate power at that frequency Real power; if (m_settings.m_measurement == FreqScannerSettings::PEAK) { - power = peakPower(bin); + power = peakPower(bin, channelBins); } else { - power = totalPower(bin); + power = totalPower(bin, channelBins); } //qDebug() << "startFrequency:" << startFrequency << "m_scannerSampleRate:" << m_scannerSampleRate << "m_centerFrequency:" << m_centerFrequency << "frequency" << frequency << "bin" << bin << "power" << power; FreqScanner::MsgScanResult::ScanResult result = {frequency, power}; @@ -144,13 +155,13 @@ void FreqScannerSink::processOneSample(Complex &ci) } // Calculate total power in a channel containing the specified bin (i.e. sums adjacent bins in the same channel) -Real FreqScannerSink::totalPower(int bin) const +Real FreqScannerSink::totalPower(int bin, int channelBins) const { // Skip bin between halfway between channels // Then skip first and last bins, to avoid spectral leakage (particularly at DC) - int startBin = bin - m_binsPerChannel / 2 + 1 + 1; + int startBin = bin - channelBins / 2 + 1 + 1; Real magSqSum = 0.0f; - for (int i = 0; i < m_binsPerChannel - 2 - 1; i++) { + for (int i = 0; i < channelBins - 2 - 1; i++) { int idx = startBin + i; if ((idx < 0) || (idx >= m_fftSize)) { continue; @@ -162,13 +173,13 @@ Real FreqScannerSink::totalPower(int bin) const } // Calculate peak power in a channel containing the specified bin -Real FreqScannerSink::peakPower(int bin) const +Real FreqScannerSink::peakPower(int bin, int channelBins) const { // Skip bin between halfway between channels // Then skip first and last bins, to avoid spectral leakage (particularly at DC) - int startBin = bin - m_binsPerChannel/2 + 1 + 1; + int startBin = bin - channelBins/2 + 1 + 1; Real maxMagSq = std::numeric_limits::min(); - for (int i = 0; i < m_binsPerChannel - 2 - 1; i++) + for (int i = 0; i < channelBins - 2 - 1; i++) { int idx = startBin + i; if ((idx < 0) || (idx >= m_fftSize)) { diff --git a/plugins/channelrx/freqscanner/freqscannersink.h b/plugins/channelrx/freqscanner/freqscannersink.h index bea25bdea..2c84f0310 100644 --- a/plugins/channelrx/freqscanner/freqscannersink.h +++ b/plugins/channelrx/freqscanner/freqscannersink.h @@ -79,8 +79,8 @@ private: void processOneSample(Complex &ci); MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } - Real totalPower(int bin) const; - Real peakPower(int bin) const; + Real totalPower(int bin, int channelBins) const; + Real peakPower(int bin, int channelBins) const; Real magSq(int bin) const; }; diff --git a/plugins/channelrx/freqscanner/readme.md b/plugins/channelrx/freqscanner/readme.md index d5126484f..4995c09be 100644 --- a/plugins/channelrx/freqscanner/readme.md +++ b/plugins/channelrx/freqscanner/readme.md @@ -97,6 +97,10 @@ The frequency table contains the list of frequencies to be scanned, along with r - Power (dB): Displays the measured power in decibels from the last scan. The cell will have a green background if the power was above the threshold (4). - Active Count: Displays the number of scans in which the power for this frequency was above the threshold (4). This allows you to see which frequencies are commonly in use. - Notes: Available for user-entry of notes/information about this frequency. +- Channel: Specifies the channel that should be tuned when this frequency is active. If blank, the common Channel setting (1) is used. +- Ch Bw (Hz): Specifies the channel bandwidth in Hertz. If blank, the common Channel Bandwidth setting (8) is used. +- TH (dB): Specifies the power threshold in dB that determines whether this frequency is active or not. If blank, the common Threshold setting (4) is used. +- Sq (dB): Specifies a squelch level in dB that will be applied to the Channel when active. If blank, the squelch level will not be changed. When an active frequency is found after a scan, the corresponding row in the table will be selected. diff --git a/sdrbase/channel/channelwebapiutils.cpp b/sdrbase/channel/channelwebapiutils.cpp index 198102f50..a23e83a0f 100644 --- a/sdrbase/channel/channelwebapiutils.cpp +++ b/sdrbase/channel/channelwebapiutils.cpp @@ -1293,6 +1293,53 @@ bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsig } } +bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, double value) +{ + SWGSDRangel::SWGChannelSettings channelSettingsResponse; + QString errorResponse; + int httpRC; + ChannelAPI *channel; + + if (getChannelSettings(deviceSetIndex, channelIndex, channelSettingsResponse, channel)) + { + // Patch settings + QJsonObject *jsonObj = channelSettingsResponse.asJsonObject(); + double oldValue; + if (WebAPIUtils::getSubObjectDouble(*jsonObj, setting, oldValue)) + { + WebAPIUtils::setSubObjectDouble(*jsonObj, setting, value); + QStringList channelSettingsKeys; + channelSettingsKeys.append(setting); + channelSettingsResponse.init(); + channelSettingsResponse.fromJsonObject(*jsonObj); + SWGSDRangel::SWGErrorResponse errorResponse2; + + httpRC = channel->webapiSettingsPutPatch(false, channelSettingsKeys, channelSettingsResponse, *errorResponse2.getMessage()); + + if (httpRC/100 == 2) + { + qDebug("ChannelWebAPIUtils::patchChannelSetting: set feature setting %s to %f OK", qPrintable(setting), value); + return true; + } + else + { + qWarning("ChannelWebAPIUtils::patchChannelSetting: set feature setting %s to %f error %d: %s", + qPrintable(setting), value, httpRC, qPrintable(*errorResponse2.getMessage())); + return false; + } + } + else + { + qWarning("ChannelWebAPIUtils::patchChannelSetting: no key %s in feature settings", qPrintable(setting)); + return false; + } + } + else + { + return false; + } +} + bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, const QJsonArray& value) { SWGSDRangel::SWGChannelSettings channelSettingsResponse; diff --git a/sdrbase/channel/channelwebapiutils.h b/sdrbase/channel/channelwebapiutils.h index 79a959e15..1552c2c44 100644 --- a/sdrbase/channel/channelwebapiutils.h +++ b/sdrbase/channel/channelwebapiutils.h @@ -73,6 +73,7 @@ public: static bool patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value); static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value); static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value); + static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, double value); static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, const QJsonArray& value); static bool getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, int &value); static bool getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double &value); diff --git a/sdrbase/util/simpleserializer.h b/sdrbase/util/simpleserializer.h index a25cb949c..de152c330 100644 --- a/sdrbase/util/simpleserializer.h +++ b/sdrbase/util/simpleserializer.h @@ -105,7 +105,7 @@ public: bool readBlob(quint32 id, QByteArray* result, const QByteArray& def = QByteArray()) const; template - bool readList(quint32 id, QList* result) + bool readList(quint32 id, QList* result, const QList& def = {}) { QByteArray data; bool ok = readBlob(id, &data); @@ -115,10 +115,14 @@ public: (*stream) >> *result; delete stream; } + else + { + *result = def; + } return ok; } template - bool readHash(quint32 id, QHash* result) + bool readHash(quint32 id, QHash* result, const QHash& def = {}) { QByteArray data; bool ok = readBlob(id, &data); @@ -128,6 +132,10 @@ public: (*stream) >> *result; delete stream; } + else + { + *result = def; + } return ok; } diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 0770ec523..358e4029f 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -63,6 +63,8 @@ set(sdrgui_SOURCES gui/graphicsviewzoom.cpp gui/httpdownloadmanagergui.cpp gui/indicator.cpp + gui/int64delegate.cpp + gui/int64validator.cpp gui/levelmeter.cpp gui/loggingdialog.cpp gui/logslider.cpp @@ -186,6 +188,8 @@ set(sdrgui_HEADERS gui/graphicsviewzoom.h gui/httpdownloadmanagergui.h gui/indicator.h + gui/int64delegate.h + gui/int64validator.h gui/levelmeter.h gui/loggingdialog.h gui/logslider.h diff --git a/sdrgui/gui/decimaldelegate.cpp b/sdrgui/gui/decimaldelegate.cpp index 3cfe533b4..2b56b74cd 100644 --- a/sdrgui/gui/decimaldelegate.cpp +++ b/sdrgui/gui/decimaldelegate.cpp @@ -18,10 +18,40 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include +#include + #include "decimaldelegate.h" +// Allow "" or double +class DoubleOrEmptyValidator : public QDoubleValidator { +public: + DoubleOrEmptyValidator(double bottom, double top, int decimals, QObject *parent = nullptr) : + QDoubleValidator(bottom, top, decimals, parent) + { + } + + QValidator::State validate(QString& input, int &pos) const + { + if (input == "") { + return QValidator::Acceptable; + } else { + return QDoubleValidator::validate(input, pos); + } + } +}; + DecimalDelegate::DecimalDelegate(int precision) : - m_precision(precision) + m_precision(precision), + m_min(-std::numeric_limits::max()), + m_max(std::numeric_limits::max()) +{ +} + +DecimalDelegate::DecimalDelegate(int precision, double min, double max) : + m_precision(precision), + m_min(min), + m_max(max) { } @@ -36,3 +66,22 @@ QString DecimalDelegate::displayText(const QVariant &value, const QLocale &local return value.toString(); } } + +QWidget *DecimalDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + (void) option; + (void) index; + + QLineEdit* editor = new QLineEdit(parent); + DoubleOrEmptyValidator *validator = new DoubleOrEmptyValidator(m_min, m_max, m_precision); + validator->setBottom(m_min); + validator->setTop(m_max); + editor->setValidator(validator); + return editor; +} + +void DecimalDelegate::setRange(double min, double max) +{ + m_min = min; + m_max = max; +} diff --git a/sdrgui/gui/decimaldelegate.h b/sdrgui/gui/decimaldelegate.h index 9fb2dce71..a88ec4dad 100644 --- a/sdrgui/gui/decimaldelegate.h +++ b/sdrgui/gui/decimaldelegate.h @@ -26,17 +26,23 @@ #include "export.h" // Deligate for table to control precision used to display floating point values - also supports strings +// Min and max values are constraints for editing class SDRGUI_API DecimalDelegate : public QStyledItemDelegate { public: DecimalDelegate(int precision = 2); + DecimalDelegate(int precision, double min, double max); virtual QString displayText(const QVariant &value, const QLocale &locale) const override; + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; int getPrecision() const { return m_precision; } void setPrecision(int precision) { m_precision = precision; } + void setRange(double min, double max); private: int m_precision; + double m_min; + double m_max; }; diff --git a/sdrgui/gui/frequencydelegate.cpp b/sdrgui/gui/frequencydelegate.cpp index da01c1093..057f5d3a4 100644 --- a/sdrgui/gui/frequencydelegate.cpp +++ b/sdrgui/gui/frequencydelegate.cpp @@ -21,6 +21,7 @@ #include #include "frequencydelegate.h" +#include "int64validator.h" FrequencyDelegate::FrequencyDelegate(const QString& units, int precision, bool group) : m_units(units), @@ -112,13 +113,14 @@ QString FrequencyDelegate::displayText(const QVariant &value, const QLocale &loc } } + QWidget* FrequencyDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { (void) option; (void) index; QLineEdit* editor = new QLineEdit(parent); - QIntValidator* validator = new QIntValidator(); + Int64Validator* validator = new Int64Validator(); validator->setBottom(0); editor->setValidator(validator); return editor; diff --git a/sdrgui/gui/int64delegate.cpp b/sdrgui/gui/int64delegate.cpp new file mode 100644 index 000000000..c5a8e7781 --- /dev/null +++ b/sdrgui/gui/int64delegate.cpp @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "int64delegate.h" +#include "int64validator.h" + +Int64Delegate::Int64Delegate() : + m_min(-std::numeric_limits::max()), + m_max(std::numeric_limits::max()) +{ +} + +Int64Delegate::Int64Delegate(qint64 min, qint64 max) : + m_min(min), + m_max(max) +{ +} + +QString Int64Delegate::displayText(const QVariant &value, const QLocale &locale) const +{ + (void) locale; + + return value.toString(); +} + +QWidget *Int64Delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + (void) option; + (void) index; + + QLineEdit* editor = new QLineEdit(parent); + Int64Validator* validator = new Int64Validator(); + validator->setBottom(m_min); + validator->setTop(m_max); + editor->setValidator(validator); + return editor; +} + +void Int64Delegate::setRange(qint64 min, qint64 max) +{ + m_min = min; + m_max = max; +} diff --git a/sdrgui/gui/int64delegate.h b/sdrgui/gui/int64delegate.h new file mode 100644 index 000000000..e69688f07 --- /dev/null +++ b/sdrgui/gui/int64delegate.h @@ -0,0 +1,46 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_GUI_INT64DELGATE_H +#define SDRGUI_GUI_INT64DELGATE_H + +#include + +#include "export.h" + +// Delegate for table to display a qint64 with input range validation +// Also supports "" as a value +class SDRGUI_API Int64Delegate : public QStyledItemDelegate { + +public: + Int64Delegate(); + Int64Delegate(qint64 min, qint64 max); + virtual QString displayText(const QVariant &value, const QLocale &locale) const override; + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setMin(qint64 min) { m_min = min; } + void setMax(qint64 max) { m_max = max; } + void setRange(qint64 min, qint64 max); + qint64 min() const { return m_min; } + qint64 max() const { return m_max; } + +private: + qint64 m_min; + qint64 m_max; + +}; + +#endif // SDRGUI_GUI_INT64DELGATE_H diff --git a/sdrgui/gui/int64validator.cpp b/sdrgui/gui/int64validator.cpp new file mode 100644 index 000000000..09d2863dc --- /dev/null +++ b/sdrgui/gui/int64validator.cpp @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "int64validator.h" + +QValidator::State Int64Validator::validate(QString& input, int &pos) const +{ + if (input == "") { + return QValidator::Acceptable; + } + + if ((m_bottom < 0) && (input == "-")) { + return QValidator::Intermediate; + } + + QRegularExpression re("-?\\d+"); + QRegularExpressionMatch match = re.match(input); + if (match.hasMatch()) + { + qint64 value = input.toLongLong(); + if (value < m_bottom) { + return QValidator::Invalid; + } + if (value > m_top) { + return QValidator::Invalid; + } + return QValidator::Acceptable; + } + else + { + return QValidator::Invalid; + } +} diff --git a/sdrgui/gui/int64validator.h b/sdrgui/gui/int64validator.h new file mode 100644 index 000000000..7a8866392 --- /dev/null +++ b/sdrgui/gui/int64validator.h @@ -0,0 +1,75 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +// Like QIntValidator but for qint64 +class Int64Validator : public QValidator +{ + Q_OBJECT + +public: + + Int64Validator(QObject *parent = nullptr) : + QValidator(parent), + m_bottom(-std::numeric_limits::max()), + m_top(std::numeric_limits::max()) + { + } + + Int64Validator(qint64 bottom, qint64 top, QObject *parent = nullptr) : + QValidator(parent), + m_bottom(bottom), + m_top(top) + { + } + + void setBottom(qint64 bottom) + { + m_bottom = bottom; + } + + void setTop(qint64 top) + { + m_top = top; + } + + void setRange(qint64 bottom, qint64 top) + { + m_bottom = bottom; + m_top = top; + } + + qint64 bottom() const + { + return m_bottom; + } + + qint64 top() const + { + return m_top; + } + + QValidator::State validate(QString& input, int &pos) const; + +private: + qint64 m_bottom; + qint64 m_top; +};