///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com>                     //
//                                                                               //
// 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 <http://www.gnu.org/licenses/>.          //
///////////////////////////////////////////////////////////////////////////////////

#include <QDebug>
#include <QAction>
#include <QClipboard>
#include <QMenu>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QComboBox>
#include <QFileDialog>
#include <QMessageBox>

#include "device/deviceset.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_freqscannergui.h"
#include "gui/basicchannelsettingsdialog.h"
#include "dsp/dspengine.h"
#include "gui/tabletapandhold.h"
#include "gui/dialogpositioner.h"
#include "gui/decimaldelegate.h"
#include "gui/frequencydelegate.h"
#include "gui/int64delegate.h"
#include "gui/glspectrum.h"
#include "channel/channelwebapiutils.h"
#include "util/csv.h"
#include "maincore.h"

#include "freqscannergui.h"
#include "freqscanneraddrangedialog.h"
#include "freqscanner.h"

FreqScannerGUI* FreqScannerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
    FreqScannerGUI* gui = new FreqScannerGUI(pluginAPI, deviceUISet, rxChannel);
    return gui;
}

void FreqScannerGUI::destroy()
{
    delete this;
}

void FreqScannerGUI::resetToDefaults()
{
    m_settings.resetToDefaults();
    displaySettings();
    applyAllSettings();
}

QByteArray FreqScannerGUI::serialize() const
{
    return m_settings.serialize();
}

bool FreqScannerGUI::deserialize(const QByteArray& data)
{
    if(m_settings.deserialize(data))
    {
        displaySettings();
        applyAllSettings();
        return true;
    }
    else
    {
        resetToDefaults();
        return false;
    }
}

bool FreqScannerGUI::handleMessage(const Message& message)
{
    if (FreqScanner::MsgConfigureFreqScanner::match(message))
    {
        qDebug("FreqScannerGUI::handleMessage: FreqScanner::MsgConfigureFreqScanner");
        const FreqScanner::MsgConfigureFreqScanner& cfg = (FreqScanner::MsgConfigureFreqScanner&) message;
        if (cfg.getForce()) {
            m_settings = cfg.getSettings();
        } else {
            m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings());
        }
        blockApplySettings(true);
        m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
        displaySettings();
        blockApplySettings(false);
        return true;
    }
    else if (DSPSignalNotification::match(message))
    {
        DSPSignalNotification& notif = (DSPSignalNotification&) message;
        m_deviceCenterFrequency = notif.getCenterFrequency();
        m_basebandSampleRate = notif.getSampleRate();
        if (m_basebandSampleRate != 0)
        {
            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, 8, 0, m_basebandSampleRate);
        }
        if (m_channelMarker.getBandwidth() == 0) {
            m_channelMarker.setBandwidth(m_basebandSampleRate);
        }
        updateAbsoluteCenterFrequency();
        return true;
    }
    else if (FreqScanner::MsgReportChannels::match(message))
    {
        FreqScanner::MsgReportChannels& report = (FreqScanner::MsgReportChannels&)message;
        updateChannelsList(report.getChannels(), report.getRenameFrom(), report.getRenameTo());
        return true;
    }
    else if (FreqScanner::MsgStatus::match(message))
    {
        FreqScanner::MsgStatus& report = (FreqScanner::MsgStatus&)message;
        ui->status->setText(report.getText());
        return true;
    }
    else if (FreqScanner::MsgReportScanning::match(message))
    {
        ui->status->setText("Scanning");
        ui->table->clearSelection();
        ui->channelPower->setText("-");
        return true;
    }
    else if (FreqScanner::MsgScanComplete::match(message))
    {
        ui->startStop->setChecked(false);
        return true;
    }
    else if (FreqScanner::MsgReportActiveFrequency::match(message))
    {
        FreqScanner::MsgReportActiveFrequency& report = (FreqScanner::MsgReportActiveFrequency&)message;
        qint64 f = report.getCenterFrequency();
        QString frequency;
        QString annotation;
        QList<QTableWidgetItem*> items = ui->table->findItems(QString::number(f), Qt::MatchExactly);
        if (items.size() > 0)
        {
            ui->table->selectRow(items[0]->row());
            frequency = ui->table->item(items[0]->row(), COL_FREQUENCY)->text();
            annotation = ui->table->item(items[0]->row(), COL_ANNOTATION)->text();
        }
        FrequencyDelegate freqDelegate("Auto", 3);
        QString formattedFrequency = freqDelegate.displayText(frequency, QLocale::system());
        ui->status->setText(QString("Active: %1 %2").arg(formattedFrequency).arg(annotation));
        return true;
    }
    else if (FreqScanner::MsgReportActivePower::match(message))
    {
        FreqScanner::MsgReportActivePower& report = (FreqScanner::MsgReportActivePower&)message;
        float power = report.getPower();
        ui->channelPower->setText(QString::number(power, 'f', 1));
        return true;
    }
    else if (FreqScanner::MsgReportScanRange::match(message))
    {
        FreqScanner::MsgReportScanRange& report = (FreqScanner::MsgReportScanRange&)message;
        m_channelMarker.setCenterFrequency(report.getCenterFrequency());
        m_channelMarker.setBandwidth(report.getTotalBandwidth());
        m_channelMarker.setVisible(report.getTotalBandwidth() < m_basebandSampleRate); // Hide marker if full bandwidth
        return true;
    }
    else if (FreqScanner::MsgScanResult::match(message))
    {
        FreqScanner::MsgScanResult& report = (FreqScanner::MsgScanResult&)message;
        QList<FreqScanner::MsgScanResult::ScanResult> results = report.getScanResults();

        // Clear column
        for (int i = 0; i < ui->table->rowCount(); i++)
        {
            QTableWidgetItem* item = ui->table->item(i, COL_POWER);
            item->setText("");
            item->setBackground(QBrush());
        }
        // Add results
        for (int i = 0; i < results.size(); i++)
        {
            qint64 freq = results[i].m_frequency;
            QList<QTableWidgetItem *> items = ui->table->findItems(QString::number(freq), Qt::MatchExactly);
            for (auto item : items)
            {
                int row = item->row();
                QTableWidgetItem* powerItem = ui->table->item(row, COL_POWER);
                powerItem->setData(Qt::DisplayRole, results[i].m_power);
                QTableWidgetItem* vadItem = ui->table->item(row, COL_VAD);
                vadItem->setData(Qt::DisplayRole, results[i].m_voiceActivityLevel);
                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);
                    QTableWidgetItem* activeCountItem = ui->table->item(row, COL_ACTIVE_COUNT);
                    activeCountItem->setData(Qt::DisplayRole, activeCountItem->data(Qt::DisplayRole).toInt() + 1);
                }
            }
        }

        return true;
    }
    else if (FreqScanner::MsgStartScan::match(message))
    {
        ui->startStop->doToggle(true);
        return true;
    }
    else if (FreqScanner::MsgStopScan::match(message))
    {
        ui->startStop->doToggle(false);
        return true;
    }
    return false;
}

void FreqScannerGUI::updateChannelsCombo(QComboBox *combo, const AvailableChannelOrFeatureList& channels, const QString& channel, bool empty)
{
    combo->blockSignals(true);
    combo->clear();
    if (empty) {
        combo->addItem("");
    }

    for (const auto& channel : channels)
    {
        // Add channels in this device set, other than ourself (Don't use ChannelGUI::getDeviceSetIndex()/getIndex() as not valid when this is first called)
        if ((channel.m_superIndex == m_freqScanner->getDeviceSetIndex()) && (channel.m_index != m_freqScanner->getIndexInDeviceSet())) {
            combo->addItem(channel.getId());
        }
    }

    // Channel can be created after this plugin, so select it
    // if the chosen channel appears
    int channelIndex = combo->findText(channel);

    if (channelIndex >= 0) {
        combo->setCurrentIndex(channelIndex);
    } else {
        combo->setCurrentIndex(-1); // return to nothing selected
    }

    combo->blockSignals(false);
}

void FreqScannerGUI::updateChannelsList(const AvailableChannelOrFeatureList& channels, const QStringList& renameFrom, const QStringList& renameTo)
{
    m_availableChannels = channels;

    // Update channel setting if it has been renamed
    if (renameFrom.contains(m_settings.m_channel))
    {
        m_settings.m_channel = renameTo[renameFrom.indexOf(m_settings.m_channel)];
        applySetting("channel");
    }
    bool rename = false;
    for (auto& setting : m_settings.m_frequencySettings)
    {
         if (renameFrom.contains(setting.m_channel))
         {
             setting.m_channel = renameTo[renameFrom.indexOf(setting.m_channel)];
             rename = true;
         }
    }
    if (rename) {
         applySetting("frequencySettings");
    }

    updateChannelsCombo(ui->channels, channels, m_settings.m_channel, false);

    for (int row = 0; row < ui->table->rowCount(); row++)
    {
        QComboBox *combo = qobject_cast<QComboBox *>(ui->table->cellWidget(row, COL_CHANNEL));
        updateChannelsCombo(combo, channels, m_settings.m_frequencySettings[row].m_channel, true);
    }
}

void FreqScannerGUI::on_channels_currentIndexChanged(int index)
{
    if (index >= 0)
    {
        m_settings.m_channel = ui->channels->currentText();
        applySetting("channel");
    }
}

void FreqScannerGUI::handleInputMessages()
{
    Message* message;

    while ((message = getInputMessageQueue()->pop()) != 0)
    {
        if (handleMessage(*message)) {
            delete message;
        }
    }
}

void FreqScannerGUI::channelMarkerChangedByCursor()
{
}

void FreqScannerGUI::channelMarkerHighlightedByCursor()
{
    setHighlighted(m_channelMarker.getHighlighted());
}

void FreqScannerGUI::on_deltaFrequency_changed(qint64 value)
{
    m_settings.m_channelFrequencyOffset = value;
    applySetting("channelFrequencyOffset");
}

void FreqScannerGUI::on_channelBandwidth_changed(qint64 value)
{
    m_settings.m_channelBandwidth = value;
    applySetting("channelBandwidth");
}

void FreqScannerGUI::on_channelShift_changed(qint64 value)
{
    m_settings.m_channelShift = value;
    applySetting("channelShift");
}

void FreqScannerGUI::on_scanTime_valueChanged(int value)
{
    ui->scanTimeText->setText(QString("%1 s").arg(value / 10.0, 0, 'f', 1));
    m_settings.m_scanTime = value / 10.0;
    applySetting("scanTime");
}

void FreqScannerGUI::on_retransmitTime_valueChanged(int value)
{
    ui->retransmitTimeText->setText(QString("%1 s").arg(value / 10.0, 0, 'f', 1));
    m_settings.m_retransmitTime = value / 10.0;
    applySetting("retransmitTime");
}

void FreqScannerGUI::on_tuneTime_valueChanged(int value)
{
    ui->tuneTimeText->setText(QString("%1 ms").arg(value));
    m_settings.m_tuneTime = value;
    applySetting("tuneTime");
}

void FreqScannerGUI::on_thresh_valueChanged(int value)
{
    ui->threshText->setText(QString("%1 dB").arg(value / 10.0, 0, 'f', 1));
    m_settings.m_threshold = value / 10.0;
    applySetting("threshold");
}

void FreqScannerGUI::on_voiceThreshold_valueChanged(int value)
{
    ui->voiceThresholdText->setText(QString("%1").arg(value / 100.0, 0, 'f', 2));
    m_settings.m_voiceSquelchThreshold = value / 100.0;
    applySetting("voiceSquelchThreshold");
}

void FreqScannerGUI::on_voiceSquelchType_currentIndexChanged(int index)
{
    m_settings.m_voiceSquelchType = (FreqScannerSettings::VoiceSquelchType)index;
    QStringList settingsKeys({"voiceSquelchType"});
    
    if (m_settings.m_voiceSquelchType == FreqScannerSettings::VoiceSquelchType::VoiceLsb)
    {
        blockApplySettings(true);
        m_settings.m_channelBandwidth = 3000;
        ui->channelBandwidth->setValue(m_settings.m_channelBandwidth);
        m_settings.m_channelShift = 1500;
        ui->channelShift->setValue(m_settings.m_channelShift);
        settingsKeys.append("channelBandwidth");
        settingsKeys.append("channelShift");
        blockApplySettings(false);
    }
    else if (m_settings.m_voiceSquelchType == FreqScannerSettings::VoiceSquelchType::VoiceUsb)
    {
        blockApplySettings(true);
        m_settings.m_channelBandwidth = 3000;
        ui->channelBandwidth->setValue(m_settings.m_channelBandwidth);
        m_settings.m_channelShift = -1500;
        ui->channelShift->setValue(m_settings.m_channelShift);
        settingsKeys.append("channelBandwidth");
        settingsKeys.append("channelShift");
        blockApplySettings(false);
    }
    
    applySettings(settingsKeys);
}

void FreqScannerGUI::on_lockDeviceFrequency_toggled(bool checked)
{
    m_settings.m_lockDeviceFrequency = checked;
    applySetting("lockDeviceFrequency");
}

void FreqScannerGUI::on_priority_currentIndexChanged(int index)
{
    m_settings.m_priority = (FreqScannerSettings::Priority)index;
    applySetting("priority");
}

void FreqScannerGUI::on_measurement_currentIndexChanged(int index)
{
    m_settings.m_measurement = (FreqScannerSettings::Measurement)index;
    applySetting("measurement");
}

void FreqScannerGUI::on_mode_currentIndexChanged(int index)
{
    m_settings.m_mode = (FreqScannerSettings::Mode)index;
    applySetting("mode");
    bool multiplex = m_settings.m_mode == FreqScannerSettings::MULTIPLEX;
    ui->threshLabel->setEnabled(!multiplex);
    ui->thresh->setEnabled(!multiplex);
    ui->threshText->setEnabled(!multiplex);
    ui->priorityLabel->setEnabled(!multiplex);
    ui->priority->setEnabled(!multiplex);
    if (multiplex)
    {
        ui->retransmitTimeLabel->setText("t<sub>TX</sub>");
        ui->retransmitTime->setToolTip("Time in seconds to listen on each frequency");
    }
    else
    {
        ui->retransmitTimeLabel->setText("t<sub>RTX</sub>");
        ui->retransmitTime->setToolTip("Time in seconds to wait for frequency to become active again, before restarting scan");
    }
}

void FreqScannerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
    (void) widget;
    (void) rollDown;

    getRollupContents()->saveState(m_rollupState);
    applySetting("rollupState");
}

void FreqScannerGUI::onMenuDialogCalled(const QPoint &p)
{
    if (m_contextMenuType == ContextMenuType::ContextMenuChannelSettings)
    {
        BasicChannelSettingsDialog dialog(&m_channelMarker, this);
        dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
        dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
        dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
        dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
        dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
        dialog.setDefaultTitle(m_displayedName);

        if (m_deviceUISet->m_deviceMIMOEngine)
        {
            dialog.setNumberOfStreams(m_freqScanner->getNumberOfDeviceStreams());
            dialog.setStreamIndex(m_settings.m_streamIndex);
        }

        dialog.move(p);
        new DialogPositioner(&dialog, false);
        dialog.exec();

        m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
        m_settings.m_title = m_channelMarker.getTitle();
        m_settings.m_useReverseAPI = dialog.useReverseAPI();
        m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
        m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
        m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
        m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();

        setWindowTitle(m_settings.m_title);
        setTitle(m_channelMarker.getTitle());
        setTitleColor(m_settings.m_rgbColor);

        QList<QString> settingsKeys({
            "rgbColor",
            "title",
            "useReverseAPI",
            "reverseAPIAddress",
            "reverseAPIPort",
            "reverseAPIDeviceIndex",
            "reverseAPIChannelIndex"
        });

        if (m_deviceUISet->m_deviceMIMOEngine)
        {
            m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
            m_channelMarker.clearStreamIndexes();
            m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
            updateIndexLabel();
        }

        applySettings(settingsKeys);
    }

    resetContextMenuType();
}

FreqScannerGUI::FreqScannerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
    ChannelGUI(parent),
    ui(new Ui::FreqScannerGUI),
    m_pluginAPI(pluginAPI),
    m_deviceUISet(deviceUISet),
    m_channelMarker(this),
    m_deviceCenterFrequency(0),
    m_doApplySettings(true)
{
    setAttribute(Qt::WA_DeleteOnClose, true);
    m_helpURL = "plugins/channelrx/freqscanner/readme.md";
    RollupContents *rollupContents = getRollupContents();
    ui->setupUi(rollupContents);
    setSizePolicy(rollupContents->sizePolicy());
    rollupContents->arrangeRollups();
    connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
    connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));

    m_freqScanner = reinterpret_cast<FreqScanner*>(rxChannel);
    m_freqScanner->setMessageQueueToGUI(getInputMessageQueue());

    ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
    ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
    ui->deltaFrequency->setValueRange(true, 8, 0, 9999999);

    ui->channelBandwidth->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
    ui->channelBandwidth->setValueRange(true, 8, 0, 9999999);

    ui->channelShift->setColorMapper(ColorMapper(ColorMapper::GrayGold));
    ui->channelShift->setValueRange(false, 5, -50000, 50000);

    m_channelMarker.setColor(Qt::yellow);
    m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
    m_channelMarker.setTitle("Frequency Scanner");
    m_channelMarker.blockSignals(false);
    m_channelMarker.setVisible(true);

    setTitleColor(m_channelMarker.getColor());
    m_settings.setChannelMarker(&m_channelMarker);
    m_settings.setRollupState(&m_rollupState);

    m_deviceUISet->addChannelMarker(&m_channelMarker);

    connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
    connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
    connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));

    // Resize the table using dummy data
    resizeTable();
    // Allow user to reorder columns
    ui->table->horizontalHeader()->setSectionsMovable(true);
    // Add context menu to allow hiding/showing of columns
    m_menu = new QMenu(ui->table);
    for (int i = 0; i < ui->table->horizontalHeader()->count(); i++)
    {
        QString text = ui->table->horizontalHeaderItem(i)->text();
        m_menu->addAction(createCheckableItem(text, i, true));
    }
    ui->table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->table->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
    // Get signals when columns change
    connect(ui->table->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(table_sectionMoved(int, int, int)));
    connect(ui->table->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(table_sectionResized(int, int, int)));
    // Context menu
    ui->table->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->table, &QTableWidget::customContextMenuRequested, this, &FreqScannerGUI::table_customContextMenuRequested);
    TableTapAndHold* tableTapAndHold = new TableTapAndHold(ui->table);
    connect(tableTapAndHold, &TableTapAndHold::tapAndHold, this, &FreqScannerGUI::table_customContextMenuRequested);

    ui->startStop->setStyleSheet(QString("QToolButton{ background-color: blue; } QToolButton:checked{ background-color: green; }"));

    displaySettings();
    makeUIConnections();
    applyAllSettings();
    m_resizer.enableChildMouseTracking();

    ui->table->setItemDelegateForColumn(COL_FREQUENCY, new FrequencyDelegate("Auto", 3, true, ui->table));
    ui->table->setItemDelegateForColumn(COL_POWER, new DecimalDelegate(1, ui->table));
    ui->table->setItemDelegateForColumn(COL_VAD, new DecimalDelegate(2, ui->table));
    ui->table->setItemDelegateForColumn(COL_CHANNEL_BW, new Int64Delegate(0, 10000000, ui->table));
    ui->table->setItemDelegateForColumn(COL_TH, new DecimalDelegate(1, -120.0, 0.0, ui->table));
    ui->table->setItemDelegateForColumn(COL_SQ, new DecimalDelegate(1, -120.0, 0.0, ui->table));

    connect(m_deviceUISet->m_spectrum->getSpectrumView(), &GLSpectrumView::updateAnnotations, this, &FreqScannerGUI::updateAnnotations);
}

FreqScannerGUI::~FreqScannerGUI()
{
    delete ui;
}

void FreqScannerGUI::blockApplySettings(bool block)
{
    m_doApplySettings = !block;
}

void FreqScannerGUI::applySetting(const QString& settingsKey)
{
    applySettings({settingsKey});
}

void FreqScannerGUI::applySettings(const QStringList& settingsKeys, bool force)
{
    m_settingsKeys.append(settingsKeys);
    if (m_doApplySettings)
    {
        FreqScanner::MsgConfigureFreqScanner* message = FreqScanner::MsgConfigureFreqScanner::create(m_settings, m_settingsKeys, force);
        m_freqScanner->getInputMessageQueue()->push(message);
        m_settingsKeys.clear();
    }
}

void FreqScannerGUI::applyAllSettings()
{
    applySettings(QStringList(), true);
}

void FreqScannerGUI::displaySettings()
{
    m_channelMarker.blockSignals(true);
    m_channelMarker.setBandwidth(m_basebandSampleRate);
    m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
    m_channelMarker.setTitle(m_settings.m_title);
    m_channelMarker.blockSignals(false);
    m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only

    setTitleColor(m_settings.m_rgbColor);
    setWindowTitle(m_channelMarker.getTitle());
    setTitle(m_channelMarker.getTitle());

    blockApplySettings(true);
    int channelIndex = ui->channels->findText(m_settings.m_channel);
    if (channelIndex >= 0) {
        ui->channels->setCurrentIndex(channelIndex);
    }
    ui->deltaFrequency->setValue(m_settings.m_channelFrequencyOffset);
    ui->deviceFreqLock->setChecked(m_settings.m_lockDeviceFrequency);
    ui->channelBandwidth->setValue(m_settings.m_channelBandwidth);
    ui->channelShift->setValue(m_settings.m_channelShift);
    ui->scanTime->setValue(m_settings.m_scanTime * 10.0);
    ui->scanTimeText->setText(QString("%1 s").arg(m_settings.m_scanTime, 0, 'f', 1));
    ui->retransmitTime->setValue(m_settings.m_retransmitTime * 10.0);
    ui->retransmitTimeText->setText(QString("%1 s").arg(m_settings.m_retransmitTime, 0, 'f', 1));
    ui->tuneTime->setValue(m_settings.m_tuneTime);
    ui->tuneTimeText->setText(QString("%1 ms").arg(m_settings.m_tuneTime));
    ui->thresh->setValue(m_settings.m_threshold * 10.0);
    ui->threshText->setText(QString("%1 dB").arg(m_settings.m_threshold, 0, 'f', 1));
    ui->voiceThreshold->setValue(m_settings.m_voiceSquelchThreshold * 100.0);
    ui->voiceThresholdText->setText(QString("%1").arg(m_settings.m_voiceSquelchThreshold, 0, 'f', 2));
    ui->voiceSquelch->setCurrentIndex((int)m_settings.m_voiceSquelchType);
    ui->priority->setCurrentIndex((int)m_settings.m_priority);
    ui->measurement->setCurrentIndex((int)m_settings.m_measurement);
    ui->mode->setCurrentIndex((int)m_settings.m_mode);

    ui->table->blockSignals(true);
    ui->table->setRowCount(0);
    for (int i = 0; i < m_settings.m_frequencySettings.size(); i++)
    {
        addRow(m_settings.m_frequencySettings[i]);
        updateAnnotation(i);
    }
    ui->table->blockSignals(false);

    // Order and size columns
    QHeaderView* header = ui->table->horizontalHeader();
    for (int i = 0; i < m_settings.m_columnSizes.size(); i++)
    {
        bool hidden = m_settings.m_columnSizes[i] == 0;
        header->setSectionHidden(i, hidden);
        m_menu->actions().at(i)->setChecked(!hidden);
        if (m_settings.m_columnSizes[i] > 0) {
            ui->table->setColumnWidth(i, m_settings.m_columnSizes[i]);
        }
        header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
    }

    updateIndexLabel();

    getRollupContents()->restoreState(m_rollupState);
    updateAbsoluteCenterFrequency();
    blockApplySettings(false);
}

void FreqScannerGUI::leaveEvent(QEvent* event)
{
    m_channelMarker.setHighlighted(false);
    ChannelGUI::leaveEvent(event);
}

void FreqScannerGUI::enterEvent(EnterEventType* event)
{
    m_channelMarker.setHighlighted(true);
    ChannelGUI::enterEvent(event);
}

void FreqScannerGUI::on_startStop_toggled(bool checked)
{
    if (checked)
    {
        FreqScanner::MsgStartScan* message = FreqScanner::MsgStartScan::create();
        m_freqScanner->getInputMessageQueue()->push(message);
    }
    else
    {
        FreqScanner::MsgStopScan* message = FreqScanner::MsgStopScan::create();
        m_freqScanner->getInputMessageQueue()->push(message);
    }
}

void FreqScannerGUI::on_continueScan_clicked()
{
    FreqScanner::MsgContinueScan* message = FreqScanner::MsgContinueScan::create();
    m_freqScanner->getInputMessageQueue()->push(message);
}

void FreqScannerGUI::addRow(const FreqScannerSettings::FrequencySettings& frequencySettings)
{
    int row = ui->table->rowCount();
    ui->table->setRowCount(row + 1);

    // Must create before frequency so updateAnnotation can work
    QTableWidgetItem* annotationItem = new QTableWidgetItem();
    annotationItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
    ui->table->setItem(row, COL_ANNOTATION, annotationItem);

    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(frequencySettings.m_enabled ? Qt::Checked : Qt::Unchecked);
    ui->table->setItem(row, COL_ENABLE, enableItem);

    QTableWidgetItem* powerItem = new QTableWidgetItem();
    powerItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
    ui->table->setItem(row, COL_POWER, powerItem);

    QTableWidgetItem* vadItem = new QTableWidgetItem();
    vadItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
    ui->table->setItem(row, COL_VAD, vadItem);

    QTableWidgetItem *activeCountItem = new QTableWidgetItem();
    activeCountItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
    ui->table->setItem(row, COL_ACTIVE_COUNT, activeCountItem);
    activeCountItem->setData(Qt::DisplayRole, 0);

    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<int>::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<QComboBox *>(sender());
        QModelIndex tableIndex = ui->table->indexAt(combo->pos());
        on_table_cellChanged(tableIndex.row(), tableIndex.column());
    }
}

void FreqScannerGUI::on_addSingle_clicked()
{
    FreqScannerSettings::FrequencySettings frequencySettings;
    frequencySettings.m_frequency = 0;
    frequencySettings.m_enabled = true;
    addRow(frequencySettings);
}

void FreqScannerGUI::on_addRange_clicked()
{
    FreqScannerAddRangeDialog dialog(m_settings.m_channelBandwidth, this);
    new DialogPositioner(&dialog, false);
    if (dialog.exec())
    {
        blockApplySettings(true);
        for (const auto f : dialog.m_frequencies)
        {
            FreqScannerSettings::FrequencySettings frequencySettings;
            frequencySettings.m_frequency = f;
            frequencySettings.m_enabled = true;
            addRow(frequencySettings);
        }
        blockApplySettings(false);
        applySetting("frequencySettings");
    }
}

void FreqScannerGUI::on_remove_clicked()
{
    QList<QTableWidgetItem*> items = ui->table->selectedItems();

    for (auto item : items)
    {
        int row = ui->table->row(item);
        if (row >= 0)
        {
            ui->table->removeRow(row);
            m_settings.m_frequencySettings.removeAt(row);
        }
    }
    applySetting("frequencySettings");
}

void FreqScannerGUI::on_removeInactive_clicked()
{
    for (int i = ui->table->rowCount() - 1; i >= 0; i--)
    {
        if (ui->table->item(i, COL_ACTIVE_COUNT)->data(Qt::DisplayRole).toInt() == 0)
        {
            ui->table->removeRow(i);
            m_settings.m_frequencySettings.removeAt(i);
        }
    }
    applySetting("frequencySettings");
}

void FreqScannerGUI::on_removeAll_clicked()
{
    ui->table->setRowCount(0);
    m_settings.m_frequencySettings.clear();
    applySetting("frequencySettings");
}

static QList<QTableWidgetItem*> takeRow(QTableWidget* table, int row)
{
    QList<QTableWidgetItem*> rowItems;

    for (int col = 0; col < table->columnCount(); col++) {
        rowItems.append(table->takeItem(row, col));
    }
    return rowItems;
}

static void setRow(QTableWidget* table, int row, const QList<QTableWidgetItem*>& rowItems)
{
    for (int col = 0; col < rowItems.size(); col++) {
        table->setItem(row, col, rowItems.at(col));
    }
}

void FreqScannerGUI::on_up_clicked()
{
    QList<QTableWidgetItem*> items = ui->table->selectedItems();
    for (auto item : items)
    {
        int row = ui->table->row(item);
        if (row > 0)
        {
            QList<QTableWidgetItem*> sourceItems = takeRow(ui->table, row);
            QList<QTableWidgetItem*> destItems = takeRow(ui->table, row - 1);
            setRow(ui->table, row - 1, sourceItems);
            setRow(ui->table, row, destItems);
            ui->table->setCurrentCell(row - 1, 0);
        }
    }
}

void FreqScannerGUI::on_down_clicked()
{
    QList<QTableWidgetItem*> items = ui->table->selectedItems();
    for (auto item : items)
    {
        int row = ui->table->row(item);
        if (row < ui->table->rowCount() - 1)
        {
            QList<QTableWidgetItem*> sourceItems = takeRow(ui->table, row);
            QList<QTableWidgetItem*> destItems = takeRow(ui->table, row + 1);
            setRow(ui->table, row + 1, sourceItems);
            setRow(ui->table, row, destItems);
            ui->table->setCurrentCell(row + 1, 0);
        }
    }
}

void FreqScannerGUI::on_clearActiveCount_clicked()
{
    for (int i = 0; i < ui->table->rowCount(); i++) {
        ui->table->item(i, COL_ACTIVE_COUNT)->setData(Qt::DisplayRole, 0);
    }
}

void FreqScannerGUI::on_importFreqs_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this, "Select file to read frequencies from", "", "*.csv");

    if (!filename.isEmpty())
    {
        QFile file(filename);

        if (file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            QTextStream in(&file);

            QString error;
            QStringList colNames = {"Freq (Hz)", "Enable", "Notes", "Channel", "Ch BW (Hz)", "TH (dB)", "Sq (dB)"};
            QHash<QString, int> colIndexes = CSV::readHeader(in, colNames, error);
            if (error.isEmpty())
            {
                // Clear existing entries
                blockApplySettings(true);
                ui->table->setRowCount(0);

                int freqCol = colIndexes.value("Freq (Hz)");
                int enableCol = colIndexes.value("Enable");
                int notesCol = colIndexes.value("Notes");
                int channelCol = colIndexes.value("Channel");
                int chBWCol = colIndexes.value("Ch BW (Hz)");
                int thCol = colIndexes.value("TH (dB)");
                int sqCol = colIndexes.value("Sq (dB)");
                QStringList cols;

                while (CSV::readRow(in, &cols))
                {
                    if (cols.size() >= 7)
                    {
                        FreqScannerSettings::FrequencySettings setting;

                        setting.m_frequency = cols[freqCol].toLongLong();
                        setting.m_enabled = cols[enableCol].toLower() == "true";
                        setting.m_notes = cols[notesCol];
                        setting.m_threshold = cols[thCol];
                        setting.m_channel = cols[channelCol];
                        setting.m_channelBandwidth = cols[chBWCol];
                        setting.m_squelch = cols[sqCol];

                        addRow(setting);
                    }
                }

                updateAnnotations();
                blockApplySettings(false);
                applySetting("frequencySettings");
            }
            else
            {
                QString actualColNames = colIndexes.keys().join(" ");
                QString expectedColNames = colNames.join(" ");
                QMessageBox::critical(this, "Frequency Scanner", QString("Failed to read expected header in CSV file. %1 != %2").arg(actualColNames).arg(expectedColNames));
                return;
            }
        }
        else
        {
            QMessageBox::warning(this, "Frequency Scanner", QString("Failed to open file %1").arg(filename));
        }
    }
}

void FreqScannerGUI::on_exportFreqs_clicked()
{
    QString filename = QFileDialog::getSaveFileName(this, "Select file to save frequencies to", "", "*.csv");

    if (!filename.isEmpty())
    {
        QFile file(filename);

        if (file.open(QIODevice::WriteOnly | QIODevice::Text))
        {
            QTextStream out(&file);

            out << "Freq (Hz),Enable,Notes,Channel,Ch BW (Hz),TH (dB),Sq (dB)\n";

            for (int i = 0; i < ui->table->rowCount(); i++)
            {
                QTableWidgetItem *frequencyItem = ui->table->item(i, COL_FREQUENCY);
                QTableWidgetItem *enabledItem = ui->table->item(i, COL_ENABLE);
                QTableWidgetItem *notesItem = ui->table->item(i, COL_NOTES);
                QComboBox *channelItem = qobject_cast<QComboBox *>(ui->table->cellWidget(i, COL_CHANNEL));
                QTableWidgetItem *chBWItem = ui->table->item(i, COL_CHANNEL_BW);
                QTableWidgetItem *thItem = ui->table->item(i, COL_TH);
                QTableWidgetItem *sqItem = ui->table->item(i, COL_SQ);

                out << frequencyItem->text()
                    << "," << ((enabledItem->checkState() == Qt::Checked) ? "true" : "false")
                    << "," << notesItem->text()
                    << "," << channelItem->currentText()
                    << "," << chBWItem->text()
                    << "," << thItem->text()
                    << "," << sqItem->text()
                    << "\n";
            }
        }
        else
        {
            QMessageBox::warning(this, "Frequency Scanner", QString("Failed to open file %1").arg(filename));
        }
    }
}

void FreqScannerGUI::on_table_cellChanged(int row, int column)
{
    QTableWidgetItem* item = ui->table->item(row, column);
    if (item)
    {
        if (column == COL_FREQUENCY)
        {
            qint64 value = item->text().toLongLong();
            while (m_settings.m_frequencySettings.size() <= row)
            {
                FreqScannerSettings::FrequencySettings frequencySettings;
                frequencySettings.m_frequency = 0;
                frequencySettings.m_enabled = true;
                m_settings.m_frequencySettings.append(frequencySettings);
            }
            m_settings.m_frequencySettings[row].m_frequency = value;
            updateAnnotation(row);
            applySetting("frequencySettings");
        }
        else if (column == COL_ENABLE)
        {
            m_settings.m_frequencySettings[row].m_enabled = item->checkState() == Qt::Checked;
            applySetting("frequencySettings");
        }
        else if (column == COL_NOTES)
        {
            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<QComboBox *>(ui->table->cellWidget(row, COL_CHANNEL));
        m_settings.m_frequencySettings[row].m_channel = combo->currentText();
        qDebug() << "Setting row" << row << "to" << combo->currentText();
        applySetting("frequencySettings");
    }
}

void FreqScannerGUI::updateAnnotation(int row)
{
    QTableWidgetItem* item = ui->table->item(row, COL_FREQUENCY);
    QTableWidgetItem* annotationItem = ui->table->item(row, COL_ANNOTATION);
    if (item && annotationItem)
    {
        qint64 frequency = item->text().toLongLong();
        const QList<SpectrumAnnotationMarker>& markers = m_deviceUISet->m_spectrum->getAnnotationMarkers();
        const SpectrumAnnotationMarker* closest = nullptr;
        for (const auto& marker : markers)
        {
            qint64 start1 = marker.m_startFrequency;
            qint64 stop1 = marker.m_startFrequency + marker.m_bandwidth;
            qint64 start2 = frequency - m_settings.m_channelBandwidth / 2;
            qint64 stop2 = frequency + m_settings.m_channelBandwidth / 2;
            if (   ((start2 >= start1) && (start2 <= stop1))
                || ((stop2 >= start1) && (stop2 <= stop1))
               )
            {
                if (marker.m_bandwidth == (unsigned)m_settings.m_channelBandwidth) {
                    // Exact match
                    annotationItem->setText(marker.m_text);
                    return;
                }
                else if (!closest)
                {
                    closest = &marker;
                }
                else
                {
                    if (marker.m_bandwidth < closest->m_bandwidth) {
                        closest = &marker;
                    }
                }
            }
        }
        if (closest) {
            annotationItem->setText(closest->m_text);
        }
    }
}

void FreqScannerGUI::updateAnnotations()
{
    for (int i = 0; i < ui->table->rowCount(); i++) {
        updateAnnotation(i);
    }
}

void FreqScannerGUI::setAllEnabled(bool enable)
{
    for (int i = 0; i < ui->table->rowCount(); i++) {
        ui->table->item(i, COL_ENABLE)->setCheckState(enable ? Qt::Checked : Qt::Unchecked);
    }
}

// Mute all channels
void FreqScannerGUI::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);
        }
    }

    for (const auto& channel : channels)
    {
        unsigned int deviceSetIndex, channelIndex;

        if (MainCore::getDeviceAndChannelIndexFromId(channel, deviceSetIndex, channelIndex)) {
            ChannelWebAPIUtils::setAudioMute(deviceSetIndex, channelIndex, true);
        }
    }
}

void FreqScannerGUI::table_customContextMenuRequested(QPoint pos)
{
    QTableWidgetItem* item = ui->table->itemAt(pos);
    if (item)
    {
        int row = item->row();

        QMenu* tableContextMenu = new QMenu(ui->table);
        connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);

        // Copy current cell

        QAction* copyAction = new QAction("Copy", tableContextMenu);
        const QString text = item->text();
        connect(copyAction, &QAction::triggered, this, [text]()->void {
            QClipboard* clipboard = QGuiApplication::clipboard();
            clipboard->setText(text);
            });
        tableContextMenu->addAction(copyAction);

        tableContextMenu->addSeparator();

        // Enable all

        QAction* enableAllAction = new QAction("Enable all", tableContextMenu);
        connect(enableAllAction, &QAction::triggered, this, [this]()->void {
            setAllEnabled(true);
            });
        tableContextMenu->addAction(enableAllAction);

        // Disable all

        QAction* disableAllAction = new QAction("Disable all", tableContextMenu);
        connect(disableAllAction, &QAction::triggered, this, [this]()->void {
            setAllEnabled(false);
            });
        tableContextMenu->addAction(disableAllAction);

        // Remove selected rows

        QAction* removeAction = new QAction("Remove", tableContextMenu);
        connect(removeAction, &QAction::triggered, this, [this]()->void {
            on_remove_clicked();
            });
        tableContextMenu->addAction(removeAction);

        tableContextMenu->addSeparator();

        // Tune to frequency

        qint64 frequency = ui->table->item(row, COL_FREQUENCY)->text().toLongLong();
        FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(frequency);
        QString channel = m_settings.getChannel(frequencySettings);
        unsigned int scanDeviceSetIndex, scanChannelIndex;

        if (MainCore::getDeviceAndChannelIndexFromId(channel, scanDeviceSetIndex, scanChannelIndex))
        {
            ButtonSwitch *startStop = ui->startStop;

            QAction* findChannelMapAction = new QAction(QString("Tune %1 to %2").arg(channel).arg(frequency), tableContextMenu);
            connect(findChannelMapAction, &QAction::triggered, this, [this, scanDeviceSetIndex, scanChannelIndex, frequency, startStop]()->void {

                // Stop scanning
                if (startStop->isChecked()) {
                    startStop->click();
                }

                // Mute all channels
                muteAll();

                // Tune to frequency
                if ((frequency - m_settings.m_channelBandwidth / 2 < m_deviceCenterFrequency - m_basebandSampleRate / 2)
                    || (frequency + m_settings.m_channelBandwidth / 2 >= m_deviceCenterFrequency + m_basebandSampleRate / 2))
                {
                    qint64 centerFrequency = frequency;
                    int offset = 0;
                    while (frequency - centerFrequency < m_settings.m_channelFrequencyOffset)
                    {
                        centerFrequency -= m_settings.m_channelBandwidth;
                        offset += m_settings.m_channelBandwidth;
                    }

                    if (!ChannelWebAPIUtils::setCenterFrequency(getDeviceSetIndex(), centerFrequency)) {
                        qWarning() << "Scanner failed to set frequency" << centerFrequency;
                    }
                    ChannelWebAPIUtils::setFrequencyOffset(scanDeviceSetIndex, scanChannelIndex, offset);
                }
                else
                {
                    int offset = frequency - m_deviceCenterFrequency;
                    ChannelWebAPIUtils::setFrequencyOffset(scanDeviceSetIndex, scanChannelIndex, offset);
                }

                // Unmute channel
                ChannelWebAPIUtils::setAudioMute(scanDeviceSetIndex, scanChannelIndex, false);

                });
            tableContextMenu->addAction(findChannelMapAction);
        }
        else
        {
            qDebug() << "Failed to parse channel" << m_settings.m_channel;
        }

        tableContextMenu->popup(ui->table->viewport()->mapToGlobal(pos));
    }
}

// Columns in table reordered
void FreqScannerGUI::table_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
    (void)oldVisualIndex;
    m_settings.m_columnIndexes[logicalIndex] = newVisualIndex;
}

// Column in table resized (when hidden size is 0)
void FreqScannerGUI::table_sectionResized(int logicalIndex, int oldSize, int newSize)
{
    (void)oldSize;
    m_settings.m_columnSizes[logicalIndex] = newSize;
}

// Right click in ADSB table header - show column select menu
void FreqScannerGUI::columnSelectMenu(QPoint pos)
{
    m_menu->popup(ui->table->horizontalHeader()->viewport()->mapToGlobal(pos));
}

// Hide/show column when menu selected
void FreqScannerGUI::columnSelectMenuChecked(bool checked)
{
    (void)checked;
    QAction* action = qobject_cast<QAction*>(sender());
    if (action != nullptr)
    {
        int idx = action->data().toInt(nullptr);
        ui->table->setColumnHidden(idx, !action->isChecked());
    }
}

// Create column select menu item
QAction* FreqScannerGUI::createCheckableItem(QString& text, int idx, bool checked)
{
    QAction* action = new QAction(text, this);
    action->setCheckable(true);
    action->setChecked(checked);
    action->setData(QVariant(idx));
    connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked()));
    return action;
}

void FreqScannerGUI::resizeTable()
{
    // Fill table with a row of dummy data that will size the columns nicely
    int row = ui->table->rowCount();
    ui->table->setRowCount(row + 1);
    ui->table->setItem(row, COL_FREQUENCY, new QTableWidgetItem("800,000.5 MHz"));
    ui->table->setItem(row, COL_ANNOTATION, new QTableWidgetItem("London VOLMET"));
    ui->table->setItem(row, COL_ENABLE, new QTableWidgetItem("Enable"));
    ui->table->setItem(row, COL_POWER, new QTableWidgetItem("-100.0"));
    ui->table->setItem(row, COL_VAD, new QTableWidgetItem("0.00"));
    ui->table->setItem(row, COL_ACTIVE_COUNT, new QTableWidgetItem("10000"));
    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);
}

void FreqScannerGUI::makeUIConnections()
{
    QObject::connect(ui->channels, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_channels_currentIndexChanged);
    QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &FreqScannerGUI::on_deltaFrequency_changed);
    QObject::connect(ui->channelBandwidth, &ValueDialZ::changed, this, &FreqScannerGUI::on_channelBandwidth_changed);
    QObject::connect(ui->channelShift, &ValueDialZ::changed, this, &FreqScannerGUI::on_channelShift_changed);
    QObject::connect(ui->scanTime, &QDial::valueChanged, this, &FreqScannerGUI::on_scanTime_valueChanged);
    QObject::connect(ui->retransmitTime, &QDial::valueChanged, this, &FreqScannerGUI::on_retransmitTime_valueChanged);
    QObject::connect(ui->tuneTime, &QDial::valueChanged, this, &FreqScannerGUI::on_tuneTime_valueChanged);
    QObject::connect(ui->thresh, &QDial::valueChanged, this, &FreqScannerGUI::on_thresh_valueChanged);
    QObject::connect(ui->voiceSquelch, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_voiceSquelchType_currentIndexChanged);
    QObject::connect(ui->voiceThreshold, &QDial::valueChanged, this, &FreqScannerGUI::on_voiceThreshold_valueChanged);
    QObject::connect(ui->deviceFreqLock, &QToolButton::toggled, this, &FreqScannerGUI::on_lockDeviceFrequency_toggled);
    QObject::connect(ui->priority, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_priority_currentIndexChanged);
    QObject::connect(ui->measurement, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_measurement_currentIndexChanged);
    QObject::connect(ui->mode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FreqScannerGUI::on_mode_currentIndexChanged);
    QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &FreqScannerGUI::on_startStop_toggled);
    QObject::connect(ui->continueScan, &QPushButton::clicked, this, &FreqScannerGUI::on_continueScan_clicked);
    QObject::connect(ui->table, &QTableWidget::cellChanged, this, &FreqScannerGUI::on_table_cellChanged);
    QObject::connect(ui->addSingle, &QToolButton::clicked, this, &FreqScannerGUI::on_addSingle_clicked);
    QObject::connect(ui->addRange, &QToolButton::clicked, this, &FreqScannerGUI::on_addRange_clicked);
    QObject::connect(ui->remove, &QToolButton::clicked, this, &FreqScannerGUI::on_remove_clicked);
    QObject::connect(ui->removeInactive, &QToolButton::clicked, this, &FreqScannerGUI::on_removeInactive_clicked);
    QObject::connect(ui->removeAll, &QToolButton::clicked, this, &FreqScannerGUI::on_removeAll_clicked);
    QObject::connect(ui->up, &QToolButton::clicked, this, &FreqScannerGUI::on_up_clicked);
    QObject::connect(ui->down, &QToolButton::clicked, this, &FreqScannerGUI::on_down_clicked);
    QObject::connect(ui->clearActiveCount, &QToolButton::clicked, this, &FreqScannerGUI::on_clearActiveCount_clicked);
    QObject::connect(ui->importFreqs, &QToolButton::clicked, this, &FreqScannerGUI::on_importFreqs_clicked);
    QObject::connect(ui->exportFreqs, &QToolButton::clicked, this, &FreqScannerGUI::on_exportFreqs_clicked);
}

void FreqScannerGUI::updateAbsoluteCenterFrequency()
{
    setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}
