#1190 Tuner management redesign. Updates all tuners with direct threaded support. Incorporates USB Hotplug support. Adds black/white listing of tuners. Updates all tuner editors to have a common look and feel. Changes location of tuner configuration file and changes format to JSON. (#1208)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-04-18 07:30:58 -04:00
committed by GitHub
parent 0fb148d31e
commit 96d3996bec
259 changed files with 11974 additions and 12454 deletions

View File

@@ -0,0 +1,128 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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, either 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 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/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.ui;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationFactory;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.manager.TunerStatus;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
/**
* Editor for Discovered Tuners that displays custom tuner editor for the selected discovered tuner
*/
public class DiscoveredTunerEditor extends Editor<DiscoveredTuner> implements IDiscoveredTunerStatusListener
{
private static final long serialVersionUID = 1L;
private static final Logger mLog = LoggerFactory.getLogger(DiscoveredTunerEditor.class);
private UserPreferences mUserPreferences;
private TunerManager mTunerManager;
private JPanel mEmptyEditor = new EmptyTunerEditor();
private JPanel mEditor = mEmptyEditor;
private JScrollPane mEditorScroller;
/**
* Constructs an instance
* @param userPreferences to use for starting wideband recordings sourced by a tuner
* @param tunerManager for creating tuner editors
*/
public DiscoveredTunerEditor(UserPreferences userPreferences, TunerManager tunerManager)
{
mUserPreferences = userPreferences;
mTunerManager = tunerManager;
init();
}
/**
* Updates the lock state of the tuner editor so that lockable controls are updated
* @param locked true if the tuner is locked and the controls should be locked.
*/
public void setTunerLockState(boolean locked)
{
if(mEditor instanceof TunerEditor tunerEditor)
{
tunerEditor.setTunerLockState(locked);
}
}
public void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mEditorScroller = new JScrollPane(mEditor);
add(mEditorScroller);
}
@Override
public void save()
{
//Unused
}
@Override
public void setItem(DiscoveredTuner tuner)
{
if(hasItem())
{
getItem().removeTunerStatusListener(this);
}
//Cleanup previously selected tuner
if(hasItem() && mEditor instanceof TunerEditor tunerEditor)
{
tunerEditor.dispose();
}
super.setItem(tuner);
if(hasItem())
{
mEditor = TunerConfigurationFactory.getEditor(mUserPreferences, getItem(), mTunerManager);
getItem().addTunerStatusListener(this);
}
else
{
mEditor = mEmptyEditor;
}
remove(mEditorScroller);
mEditorScroller = new JScrollPane(mEditor);
add(mEditorScroller);
revalidate();
repaint();
}
@Override
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
{
//If this is the currently displayed tuner, set it again to re-render the editor
if(hasItem() && getItem().equals(discoveredTuner))
{
setItem(discoveredTuner);
}
}
}

View File

@@ -0,0 +1,500 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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, either 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 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/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.ui;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.DiscoveredUSBTuner;
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
import io.github.dsheirer.source.tuner.manager.TunerStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Model for discovered tuners
*/
public class DiscoveredTunerModel extends AbstractTableModel implements Listener<TunerEvent>,
IDiscoveredTunerStatusListener
{
private static final long serialVersionUID = 1L;
private static final Logger mLog = LoggerFactory.getLogger(DiscoveredTunerModel.class);
//Model columns
public static final int COLUMN_TUNER_STATUS = 0;
public static final int COLUMN_TUNER_CLASS = 1;
public static final int COLUMN_TUNER_TYPE = 2;
public static final int COLUMN_FREQUENCY = 3;
public static final int COLUMN_CHANNEL_COUNT = 4;
private static final String MHZ = " MHz";
private static final String[] COLUMN_HEADERS = {"Status","Class", "Type", "Frequency", "Channels"};
private List<DiscoveredTuner> mDiscoveredTuners = new CopyOnWriteArrayList<>();
private List<Listener<TunerEvent>> mTunerEventListeners = new ArrayList<>();
private DecimalFormat mFrequencyFormat = new DecimalFormat("0.00000");
private Lock mLock = new ReentrantLock();
/**
* Constructs an instance
*/
public DiscoveredTunerModel()
{
}
/**
* List of currently enabled and available tuners
*/
public List<DiscoveredTuner> getAvailableTuners()
{
return mDiscoveredTuners.stream().filter(discoveredTuner -> discoveredTuner.hasTuner()).toList();
}
/**
* Find the discovered tuner that matches the instantiated tuner
* @param tuner to match
* @return matching discovered tuner
*/
private DiscoveredTuner getDiscoveredTuner(Tuner tuner)
{
DiscoveredTuner match = null;
mLock.lock();
try
{
for(DiscoveredTuner discoveredTuner: mDiscoveredTuners)
{
if(discoveredTuner.hasTuner() && discoveredTuner.getTuner().equals(tuner))
{
match = discoveredTuner;
break;
}
}
}
finally
{
mLock.unlock();
}
return match;
}
/**
* Access the discovered tuner at the specified row index
* @param index to lookup
* @return discovered tuner or null
*/
public DiscoveredTuner getDiscoveredTuner(int index)
{
mLock.lock();
try
{
if(index < mDiscoveredTuners.size())
{
return mDiscoveredTuners.get(index);
}
}
finally
{
mLock.unlock();
}
return null;
}
/**
* Adds the Tuner to this model
*/
public void addDiscoveredTuner(DiscoveredTuner discoveredTuner)
{
mLock.lock();
try
{
if(!mDiscoveredTuners.contains(discoveredTuner))
{
mDiscoveredTuners.add(discoveredTuner);
int index = mDiscoveredTuners.indexOf(discoveredTuner);
EventQueue.invokeLater(() -> fireTableRowsInserted(index, index));
if(discoveredTuner.hasTuner())
{
discoveredTuner.getTuner().addTunerEventListener(this);
}
discoveredTuner.addTunerStatusListener(this);
}
}
finally
{
mLock.unlock();
}
}
/**
* Stops and removes all discovered tuners, in preparation for shutdown.
*/
public void releaseDiscoveredTuners()
{
mLock.lock();
try
{
List<DiscoveredTuner> discoveredTuners = new ArrayList<>(mDiscoveredTuners);
for(DiscoveredTuner discoveredTuner: discoveredTuners)
{
if(discoveredTuner.hasTuner())
{
discoveredTuner.getTuner().removeTunerEventListener(this);
}
removeDiscoveredTuner(discoveredTuner);
}
}
finally
{
mLock.unlock();
}
}
/**
* Removes the Tuner from this model
*/
public void removeDiscoveredTuner(DiscoveredTuner discoveredTuner)
{
mLock.lock();
try
{
if(mDiscoveredTuners.contains(discoveredTuner))
{
int index = mDiscoveredTuners.indexOf(discoveredTuner);
mDiscoveredTuners.remove(discoveredTuner);
EventQueue.invokeLater(() -> fireTableRowsDeleted(index, index));
discoveredTuner.stop();
}
}
finally
{
mLock.unlock();
}
}
/**
* Indicates if the discovered tuner is still being managed by this tuner model and hasn't been removed.
*/
public boolean hasTuner(DiscoveredTuner discoveredTuner)
{
return mDiscoveredTuners.contains(discoveredTuner);
}
/**
* Indicates if this model currently has the discovered tuner that is plugged into the specified bus and port.
* @param bus number
* @param port number
* @return true if it already has the discovered device
*/
public boolean hasUsbTuner(int bus, int port)
{
boolean hasDevice;
mLock.lock();
try
{
hasDevice = mDiscoveredTuners.stream()
.filter(tuner -> tuner instanceof DiscoveredUSBTuner usbTuner && usbTuner.isAt(bus, port))
.findFirst()
.isPresent();
}
finally
{
mLock.unlock();
}
return hasDevice;
}
/**
* Removes the USB tuner at bus and port number
* @param bus usb
* @param port usb
*/
public void removeUsbTuner(int bus, int port)
{
mLock.lock();
try
{
DiscoveredTuner discoveredTuner = mDiscoveredTuners.stream()
.filter(tuner -> tuner instanceof DiscoveredUSBTuner usbTuner &&
usbTuner.isAt(bus, port)).findFirst().get();
if(discoveredTuner != null)
{
removeDiscoveredTuner(discoveredTuner);
}
}
finally
{
mLock.unlock();
}
}
/**
* Adds a tuner event listener
*/
public void addListener(Listener<TunerEvent> listener)
{
mTunerEventListeners.add(listener);
}
/**
* Removes a tuner event listener
*/
public void removeListener(Listener<TunerEvent> listener)
{
mTunerEventListeners.remove(listener);
}
public void broadcast(TunerEvent event)
{
for(Listener<TunerEvent> listener : mTunerEventListeners)
{
listener.receive(event);
}
}
/**
* Requests to display the first tuner in this model. Invoke this method
* after all listeners have registered and tuners have been added to this
* model, in order to inform the primary display to use the first tuner.
*/
public void requestFirstTunerDisplay()
{
//TODO: move this out of the model ... is not part of the scope of this model
// SystemProperties properties = SystemProperties.getInstance();
// boolean enabled = properties.get(SpectralDisplayPanel.SPECTRAL_DISPLAY_ENABLED, true);
//
// if(enabled && mDiscoveredTuners.size() > 0)
// {
// //Hack: the airspy tuner would lockup aperiodically and refuse to produce
// //transfer buffers ... delaying registering for buffers for 500 ms seems
// //to allow the airspy to stabilize before we start asking for samples.
// ThreadPool.SCHEDULED.schedule(new Runnable()
// {
// @Override
// public void run()
// {
// broadcast(new TunerEvent(mDiscoveredTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
// }
// }, 500, TimeUnit.MILLISECONDS);
// }
// else
// {
// broadcast(new TunerEvent(null, Event.CLEAR_MAIN_SPECTRAL_DISPLAY));
// }
}
@Override
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
{
if(current == TunerStatus.ENABLED && discoveredTuner.hasTuner())
{
discoveredTuner.getTuner().addTunerEventListener(this);
}
int row = mDiscoveredTuners.indexOf(discoveredTuner);
EventQueue.invokeLater(() -> fireTableRowsUpdated(row, row));
}
@Override
public void receive(TunerEvent event)
{
if(event.hasTuner())
{
mLock.lock();
try
{
DiscoveredTuner matching = getDiscoveredTuner(event.getTuner());
int index = mDiscoveredTuners.indexOf(matching);
if(index >= 0)
{
switch(event.getEvent())
{
case UPDATE_CHANNEL_COUNT:
EventQueue.invokeLater(() -> fireTableCellUpdated(index, COLUMN_CHANNEL_COUNT));
break;
case UPDATE_FREQUENCY:
EventQueue.invokeLater(() -> fireTableCellUpdated(index, COLUMN_FREQUENCY));
break;
case NOTIFICATION_ERROR_STATE:
EventQueue.invokeLater(() -> fireTableRowsUpdated(index, index));
break;
default:
break;
}
}
}
finally
{
mLock.unlock();
}
}
else
{
mLog.error("Got a tuner event without a tuner - " + event);
}
broadcast(event);
}
@Override
public int getRowCount()
{
return mDiscoveredTuners.size();
}
@Override
public int getColumnCount()
{
return COLUMN_HEADERS.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
if(rowIndex < mDiscoveredTuners.size())
{
DiscoveredTuner discoveredTuner = mDiscoveredTuners.get(rowIndex);
switch(columnIndex)
{
case COLUMN_TUNER_STATUS:
return discoveredTuner.getTunerStatus();
case COLUMN_TUNER_CLASS:
return discoveredTuner.getTunerClass().toString();
case COLUMN_TUNER_TYPE:
if(discoveredTuner.hasTuner())
{
return discoveredTuner.getTuner().getTunerType().getLabel();
}
else
{
return "";
}
// case COLUMN_TUNER_ID:
// if(discoveredTuner.hasTuner())
// {
// return discoveredTuner.getTuner().getUniqueID();
// }
// else
// {
// return discoveredTuner.getId();
// }
// case COLUMN_SAMPLE_RATE:
// if(discoveredTuner.hasTuner())
// {
// double sampleRate = discoveredTuner.getTuner().getTunerController().getSampleRate();
// return mSampleRateFormat.format(sampleRate / 1E6D) + MHZ;
// }
// else
// {
// return "";
// }
case COLUMN_FREQUENCY:
if(discoveredTuner.hasTuner())
{
long frequency = discoveredTuner.getTuner().getTunerController().getFrequency();
return mFrequencyFormat.format(frequency / 1E6D) + MHZ;
}
else
{
return "";
}
case COLUMN_CHANNEL_COUNT:
if(discoveredTuner.hasTuner())
{
int channelCount = discoveredTuner.getTuner().getChannelSourceManager().getTunerChannelCount();
return channelCount + " (" + (discoveredTuner.getTuner().getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)");
}
else
{
return "";
}
// case COLUMN_FREQUENCY_ERROR:
// if(discoveredTuner.hasTuner())
// {
// double ppm = discoveredTuner.getTuner().getTunerController().getFrequencyCorrection();
// return mFrequencyErrorPPMFormat.format(ppm);
// }
// else
// {
// return "";
// }
// case COLUMN_MEASURED_FREQUENCY_ERROR:
// if(discoveredTuner.hasTuner())
// {
// if(discoveredTuner.getTuner().getTunerController().hasMeasuredFrequencyError())
// {
// StringBuilder sb = new StringBuilder();
// sb.append(discoveredTuner.getTuner().getTunerController().getMeasuredFrequencyError());
// sb.append("Hz (");
// sb.append(mFrequencyErrorPPMFormat.format(discoveredTuner.getTuner().getTunerController().getPPMFrequencyError()));
// sb.append("ppm)");
// return sb.toString();
// }
// }
// return "";
// case COLUMN_ERROR_OR_SPECTRAL_DISPLAY_NEW:
// if(discoveredTuner.hasErrorMessage())
// {
// return discoveredTuner.getErrorMessage();
// }
// else if(discoveredTuner.hasTuner())
// {
// return "New";
// }
// return "";
default:
break;
}
}
return null;
}
@Override
public String getColumnName(int columnIndex)
{
return COLUMN_HEADERS[columnIndex];
}
}

View File

@@ -0,0 +1,42 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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, either 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 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/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.ui;
import net.miginfocom.swing.MigLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* Empty tuner editor panel
*/
public class EmptyTunerEditor extends JPanel
{
private static final long serialVersionUID = 1L;
/**
* Constructs an instance
*/
public EmptyTunerEditor()
{
setLayout(new MigLayout("insets 0 0 0 0", "[center,grow]", "[center,grow]"));
add(new JLabel("No tuner selected"), "wrap");
}
}

View File

@@ -0,0 +1,707 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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, either 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 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/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.ui;
import io.github.dsheirer.gui.control.JFrequencyControl;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.wave.IRecordingStatusListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.manager.TunerStatus;
import io.github.dsheirer.util.SwingUtils;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.EventQueue;
import java.text.CharacterIterator;
import java.text.DecimalFormat;
import java.text.StringCharacterIterator;
/**
* Base tuner configuration editor.
*/
public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration> extends JPanel
implements IDiscoveredTunerStatusListener, Listener<TunerEvent>
{
private Logger mLog = LoggerFactory.getLogger(TunerEditor.class);
private static final String BUTTON_STATUS_ENABLE = "Enable";
private static final String BUTTON_STATUS_DISABLE = "Disable";
private static final String BUTTON_STATUS_RESET = "Reset Error";
private static final long serialVersionUID = 1L;
private UserPreferences mUserPreferences;
private TunerManager mTunerManager;
private DiscoveredTuner mDiscoveredTuner;
private C mTunerConfiguration;
private FrequencyAndCorrectionChangeListener mFrequencyAndCorrectionChangeListener = new FrequencyAndCorrectionChangeListener();
private JFrequencyControl mFrequencyControl;
private JSpinner mFrequencyCorrectionSpinner;
private JButton mChangeStatusButton;
private JButton mViewSpectrumButton;
private JButton mNewSpectrumButton;
private JToggleButton mRecordButton;
private ButtonPanel mButtonsPanel;
private FrequencyPanel mFrequencyPanel;
private JLabel mTunerIdLabel;
private JCheckBox mAutoPPMCheckBox;
private JLabel mMeasuredPPMLabel;
private JLabel mRecordingStatusLabel;
private JLabel mTunerStatusLabel;
private JLabel mTunerLockedStatusLabel;
private boolean mLoading = false;
/**
* Constructs an instance
* @param tunerManager for requesting configuration saves.
*/
public TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
{
mUserPreferences = userPreferences;
mTunerManager = tunerManager;
mDiscoveredTuner = discoveredTuner;
if(mDiscoveredTuner != null && mDiscoveredTuner.hasTunerConfiguration())
{
mTunerConfiguration = (C)mDiscoveredTuner.getTunerConfiguration();
}
if(mDiscoveredTuner != null)
{
mDiscoveredTuner.addTunerStatusListener(this);
if(mDiscoveredTuner.hasTuner())
{
mDiscoveredTuner.getTuner().addTunerEventListener(this);
}
}
}
/**
* Indicates if the controls are currently being loaded with values.
*/
protected boolean isLoading()
{
return mLoading;
}
/**
* Changes the loading status
*/
protected void setLoading(boolean loading)
{
mLoading = loading;
}
/**
* Measured PPM value received from any decoders that may be running.
*/
protected JLabel getMeasuredPPMLabel()
{
if(mMeasuredPPMLabel == null)
{
mMeasuredPPMLabel = new JLabel("");
mMeasuredPPMLabel.setToolTipText("Displays the measured frequency error and PPM when provided by compatible channel decoders");
}
return mMeasuredPPMLabel;
}
/**
* Tuner locked status label that can be turned on/off depending on tuner lock state.
*/
protected JLabel getTunerLockedStatusLabel()
{
if(mTunerLockedStatusLabel == null)
{
mTunerLockedStatusLabel = new JLabel("Channel(s) active - frequency and sample rate controls are locked");
mTunerLockedStatusLabel.setToolTipText("Indicates that the tuner is providing channel(s) and you can't " +
"change the tuner frequency or sample rate");
mTunerLockedStatusLabel.setVisible(false);
}
return mTunerLockedStatusLabel;
}
/**
* Tuner status label
*/
protected JLabel getTunerStatusLabel()
{
if(mTunerStatusLabel == null)
{
mTunerStatusLabel = new JLabel(" ");
}
return mTunerStatusLabel;
}
/**
* Label to display current file size for a wide-band recording
*/
protected JLabel getRecordingStatusLabel()
{
if(mRecordingStatusLabel == null)
{
mRecordingStatusLabel = new JLabel(" ");
mRecordingStatusLabel.setToolTipText("Shows the status of the latest baseband recording when active");
mRecordingStatusLabel.setVisible(false);
}
return mRecordingStatusLabel;
}
/**
* Check box for enable/disable automatic PPM adjustment from decoder(s) frequency error feedback.
*/
protected JCheckBox getAutoPPMCheckBox()
{
if(mAutoPPMCheckBox == null)
{
mAutoPPMCheckBox = new JCheckBox("Enable decoder(s) to auto-adjust PPM");
mAutoPPMCheckBox.setToolTipText("Allow decoders to measure channel frequency error and correct tuner PPM");
mAutoPPMCheckBox.addActionListener(e ->
{
if(!isLoading())
{
Tuner tuner = getTuner();
if(tuner != null)
{
boolean enabled = getAutoPPMCheckBox().isSelected();
getTuner().getTunerController().getFrequencyErrorCorrectionManager().setEnabled(enabled);
save();
}
}
});
}
return mAutoPPMCheckBox;
}
/**
* Label for displaying the tuner ID
*/
protected JLabel getTunerIdLabel()
{
if(mTunerIdLabel == null)
{
mTunerIdLabel = new JLabel(" ");
}
return mTunerIdLabel;
}
protected FrequencyPanel getFrequencyPanel()
{
if(mFrequencyPanel == null)
{
mFrequencyPanel = new FrequencyPanel();
mFrequencyPanel.setToolTipText("Tuner frequency and PPM controls");
}
return mFrequencyPanel;
}
protected JSpinner getFrequencyCorrectionSpinner()
{
if(mFrequencyCorrectionSpinner == null)
{
SpinnerModel model = new SpinnerNumberModel(0.0, -1000.0, 1000.0, 0.1);
mFrequencyCorrectionSpinner = new JSpinner(model);
mFrequencyCorrectionSpinner.setToolTipText("Adjust the PPM value to compensate for tuner frequency error");
mFrequencyCorrectionSpinner.setEnabled(false);
JSpinner.NumberEditor editor = (JSpinner.NumberEditor) mFrequencyCorrectionSpinner.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits(1);
editor.getTextField().setHorizontalAlignment(SwingConstants.CENTER);
mFrequencyCorrectionSpinner.addChangeListener(mFrequencyAndCorrectionChangeListener);
}
return mFrequencyCorrectionSpinner;
}
protected ButtonPanel getButtonPanel()
{
if(mButtonsPanel == null)
{
mButtonsPanel = new ButtonPanel();
mButtonsPanel.setToolTipText("Button controls for the selected tuner");
}
return mButtonsPanel;
}
protected JFrequencyControl getFrequencyControl()
{
if(mFrequencyControl == null)
{
mFrequencyControl = new JFrequencyControl();
}
return mFrequencyControl;
}
/**
* Button requesting to show tuner in new spectral display
*/
protected JButton getNewSpectrumButton()
{
if(mNewSpectrumButton == null)
{
mNewSpectrumButton = new JButton("New Spectrum Display");
mNewSpectrumButton.setToolTipText("Show this tuner in a new (separate) spectral display window");
mNewSpectrumButton.addActionListener(e ->
{
Tuner tuner = getTuner();
if(tuner != null)
{
mTunerManager.getDiscoveredTunerModel().broadcast(new TunerEvent(tuner,
TunerEvent.Event.REQUEST_NEW_SPECTRAL_DISPLAY));
}
});
}
return mNewSpectrumButton;
}
/**
* Button requesting to show tuner in main spectral display
*/
protected JButton getViewSpectrumButton()
{
if(mViewSpectrumButton == null)
{
mViewSpectrumButton = new JButton("View Spectrum");
mViewSpectrumButton.setToolTipText("Show this tuner in the spectral display");
mViewSpectrumButton.addActionListener(e ->
{
Tuner tuner = getTuner();
if(tuner != null)
{
mTunerManager.getDiscoveredTunerModel().broadcast(new TunerEvent(tuner,
TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
}
});
}
return mViewSpectrumButton;
}
/**
* Button to change enable, disable, or error restart status.
*/
protected JButton getChangeStatusButton()
{
if(mChangeStatusButton == null)
{
mChangeStatusButton = new JButton(BUTTON_STATUS_ENABLE);
mChangeStatusButton.setToolTipText("Enable or disable the tuner for use by sdrtrunk, or attempt to reset a tuner error");
mChangeStatusButton.addActionListener(e ->
{
switch(getChangeStatusButton().getText())
{
case BUTTON_STATUS_DISABLE:
mLog.info("Disabling " + getDiscoveredTuner().getTunerClass() + " tuner");
getDiscoveredTuner().setEnabled(false);
break;
case BUTTON_STATUS_ENABLE:
mLog.info("Enabling " + getDiscoveredTuner().getTunerClass() + " tuner");
getDiscoveredTuner().setEnabled(true);
break;
case BUTTON_STATUS_RESET:
mLog.info("Resetting " + getDiscoveredTuner().getTunerClass() + " tuner");
getDiscoveredTuner().reset();
break;
default:
mLog.info("None matched");
}
});
}
return mChangeStatusButton;
}
/**
* Turns off the recorder, if it's currently recording.
*/
protected void turnOffRecorder()
{
if(getRecordButton().isSelected())
{
getRecordButton().setSelected(false);
}
}
/**
* Records the tuner's wide-band sample stream.
*/
protected JToggleButton getRecordButton()
{
if(mRecordButton == null)
{
mRecordButton = new JToggleButton("Record");
mRecordButton.setToolTipText("Create a baseband recording for this tuner");
mRecordButton.setEnabled(false);
mRecordButton.addActionListener(e ->
{
if(hasTuner())
{
if(getRecordButton().isSelected())
{
getRecordingStatusLabel().setVisible(true);
getRecordingStatusLabel().setText(" ");
getTuner().getTunerController().startRecorder(mUserPreferences, new RecordingStatusListener(),
getDiscoveredTuner().getTunerClass().name());
}
else
{
getTuner().getTunerController().stopRecorder();
}
}
});
}
return mRecordButton;
}
protected abstract void save();
/**
* Access the tuner configuration
*/
protected C getConfiguration()
{
return mTunerConfiguration;
}
/**
* Indicates if this editor has a tuner configuration
*/
protected boolean hasConfiguration()
{
return mTunerConfiguration != null;
}
/**
* Discovered tuner for this editor
*/
protected DiscoveredTuner getDiscoveredTuner()
{
return mDiscoveredTuner;
}
/**
* Notification that the status of the discovered tuner has changed and that the editor implementation must
* refresh the UI controls with the current tuner state.
*/
protected abstract void tunerStatusUpdated();
/**
* Implements the tuner status listener interface to send notification to editor implementations that the
* status of the discovered tuner has changed.
* @param discoveredTuner that has a status change.
* @param previous tuner status
* @param current tuner status
*/
@Override
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
{
SwingUtils.run(() -> tunerStatusUpdated());
if(current == TunerStatus.ENABLED)
{
if(hasTuner())
{
getTuner().addTunerEventListener(this);
}
}
}
@Override
public void receive(TunerEvent tunerEvent)
{
if(tunerEvent.getEvent().equals(TunerEvent.Event.UPDATE_MEASURED_FREQUENCY_ERROR))
{
SwingUtils.run(() -> getFrequencyPanel().updateFrequencyError());
}
}
/**
* Prepare this editor for disposal
*/
public void dispose()
{
if(mDiscoveredTuner != null)
{
mDiscoveredTuner.removeTunerStatusListener(this);
if(mDiscoveredTuner.hasTuner())
{
mDiscoveredTuner.getTuner().removeTunerEventListener(this);
}
}
turnOffRecorder();
}
/**
* Indicates if the discovered tuner has a usable tuner
*/
public boolean hasTuner()
{
return mDiscoveredTuner != null && mDiscoveredTuner.hasTuner();
}
/**
* Access the usable tuner from the discovered tuner
* Note: use hasTuner() to check before invoking this method.
* @return tuner or null.
*/
public T getTuner()
{
if(hasTuner())
{
return (T)mDiscoveredTuner.getTuner();
}
return null;
}
/**
* Request to save the state of this configuration.
*/
protected void saveConfiguration()
{
mTunerManager.saveConfigurations();
}
/**
* Sets the lock state for the tuner so that the frequency and sample rate controls can be enabled/disabled.
*
* @param locked true if the tuner is locked.
*/
public void setTunerLockState(boolean locked)
{
getFrequencyPanel().updateControls();
}
/**
* Tuner buttons panel
*/
public class ButtonPanel extends JPanel
{
/**
* Constructs an instance
*/
public ButtonPanel()
{
setLayout(new MigLayout("insets 0,fill", "[][][][][grow,fill]", ""));
add(getChangeStatusButton());
add(getRecordButton());
add(getViewSpectrumButton());
add(getNewSpectrumButton(), "wrap");
// add(new JLabel(""), "wrap"); //Spacer to allow recording status label to grow and not affect buttons
add(getRecordingStatusLabel(), "span");
}
/**
* Updates the state and text of the buttons based on the tuner status.
*/
public void updateControls()
{
TunerStatus tunerStatus = getDiscoveredTuner().getTunerStatus();
getRecordButton().setEnabled(tunerStatus.isAvailable() && getDiscoveredTuner().hasTuner());
getRecordingStatusLabel().setText(" ");
getViewSpectrumButton().setEnabled(tunerStatus.isAvailable() && getDiscoveredTuner().hasTuner());
getNewSpectrumButton().setEnabled(tunerStatus.isAvailable() && getDiscoveredTuner().hasTuner());
switch(tunerStatus)
{
case ENABLED:
getChangeStatusButton().setText(BUTTON_STATUS_DISABLE);
break;
case DISABLED:
getChangeStatusButton().setText(BUTTON_STATUS_ENABLE);
break;
case ERROR:
getChangeStatusButton().setText(BUTTON_STATUS_RESET);
break;
}
}
}
/**
* Sub panel that displays frequency control, ppm spinner and control, and tuner locked status label.
*/
public class FrequencyPanel extends JPanel
{
public FrequencyPanel()
{
setLayout(new MigLayout("insets 0,fill", "[][][][][grow,fill]", ""));
add(getFrequencyControl(), "spany 2");
add(new JLabel("PPM:"));
add(getFrequencyCorrectionSpinner());
add(getMeasuredPPMLabel(), "wrap");
add(getAutoPPMCheckBox(), "span");
add(getTunerLockedStatusLabel(), "span");
}
/**
* Update the state of the frequency panel controls
*/
public void updateControls()
{
getFrequencyControl().clearListeners();
getFrequencyControl().addListener(mFrequencyAndCorrectionChangeListener);
getFrequencyControl().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked());
getTunerLockedStatusLabel().setVisible(hasTuner() && getTuner().getTunerController().isLocked());
getFrequencyCorrectionSpinner().setEnabled(hasTuner());
getAutoPPMCheckBox().setEnabled(hasTuner());
Tuner tuner = getTuner();
if(tuner != null)
{
getFrequencyControl().setFrequency(tuner.getTunerController().getFrequency(), false);
getFrequencyCorrectionSpinner().setValue(tuner.getTunerController().getFrequencyCorrection());
getAutoPPMCheckBox().setSelected(tuner.getTunerController().getFrequencyErrorCorrectionManager().isEnabled());
getFrequencyControl().addListener(getTuner().getTunerController());
getTuner().getTunerController().addListener(getFrequencyControl());
getMeasuredPPMLabel().setText(tuner.getTunerController().getMeasuredErrorStatus());
}
else
{
getFrequencyControl().setFrequency(0, false);
getFrequencyCorrectionSpinner().setValue(0);
getAutoPPMCheckBox().setSelected(false);
getMeasuredPPMLabel().setText("");
}
}
/**
* Updates the measured frequency error label
*/
public void updateFrequencyError()
{
SwingUtils.run(() ->
{
if(hasTuner())
{
getMeasuredPPMLabel().setText(getTuner().getTunerController().getMeasuredErrorStatus());
}
else
{
getMeasuredPPMLabel().setText("");
}
});
}
}
/**
* Implements status listener to receive updates for wide-band recordings
*/
public class RecordingStatusListener implements IRecordingStatusListener
{
private DecimalFormat mSizeFormat = new DecimalFormat("0.0");
@Override
public void update(int fileCount, String file, long size)
{
StringBuilder sb = new StringBuilder();
sb.append("Recording Size: ").append(humanReadableByteCount(size));
sb.append(" File #").append(fileCount).append(": ").append(file);
final String status = sb.toString();
EventQueue.invokeLater(() -> getRecordingStatusLabel().setText(status));
}
public static String humanReadableByteCount(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %cB", value / 1024.0, ci.current());
}
}
/**
* Monitors the frequency correction spinner for changed value.
*/
private class FrequencyAndCorrectionChangeListener implements ChangeListener, ISourceEventProcessor
{
//This monitors the frequency correction spinner, applies the changes to the tuner, and saves configuration.
@Override
public void stateChanged(ChangeEvent e)
{
final double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
if(hasTuner() && !isLoading())
{
try
{
getTuner().getTunerController().setFrequencyCorrection(value);
}
catch(SourceException e1)
{
mLog.error("Error setting frequency correction value", e1);
}
save();
}
}
//This monitors the frequency control and saves configuration.
@Override
public void process(SourceEvent event) throws SourceException
{
if(hasTuner() && !isLoading())
{
save();
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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, either 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 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/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.ui;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
import io.github.dsheirer.spectrum.SpectrumFrame;
import io.github.dsheirer.util.SwingUtils;
import io.github.dsheirer.util.ThreadPool;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TunerSpectralDisplayManager implements Listener<TunerEvent>
{
private SpectralDisplayPanel mSpectralDisplayPanel;
private PlaylistManager mPlaylistManager;
private SettingsManager mSettingsManager;
private DiscoveredTunerModel mDiscoveredTunerModel;
public TunerSpectralDisplayManager(SpectralDisplayPanel panel, PlaylistManager playlistManager,
SettingsManager settingsManager, DiscoveredTunerModel discoveredTunerModel)
{
mSpectralDisplayPanel = panel;
mPlaylistManager = playlistManager;
mSettingsManager = settingsManager;
mDiscoveredTunerModel = discoveredTunerModel;
}
/**
* Shows the first available tuner from the discovered tuner model
*/
public Tuner showFirstTuner()
{
List<DiscoveredTuner> availableTuners = mDiscoveredTunerModel.getAvailableTuners();
for(DiscoveredTuner discoveredTuner: availableTuners)
{
if(discoveredTuner.hasTuner())
{
mSpectralDisplayPanel.showTuner(discoveredTuner.getTuner());
return discoveredTuner.getTuner();
}
}
return null;
}
@Override
public void receive(TunerEvent event)
{
switch(event.getEvent())
{
case REQUEST_CLEAR_MAIN_SPECTRAL_DISPLAY:
SwingUtils.run(() -> mSpectralDisplayPanel.clearTuner());
break;
case REQUEST_MAIN_SPECTRAL_DISPLAY:
SwingUtils.run(() -> mSpectralDisplayPanel.showTuner(event.getTuner()));
break;
case REQUEST_NEW_SPECTRAL_DISPLAY:
final SpectrumFrame frame = new SpectrumFrame(mPlaylistManager, mSettingsManager, mDiscoveredTunerModel, event.getTuner());
SwingUtils.run(() -> frame.setVisible(true));
break;
case NOTIFICATION_ERROR_STATE:
case NOTIFICATION_SHUTTING_DOWN:
if(event.getTuner().equals(mSpectralDisplayPanel.getTuner()))
{
SwingUtils.run(() ->
{
mSpectralDisplayPanel.clearTuner();
ThreadPool.SCHEDULED.schedule(() -> SwingUtils.run(() ->
{
showFirstTuner();
}), 1, TimeUnit.SECONDS);
});
}
default:
break;
}
}
}

View File

@@ -0,0 +1,210 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* 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, either 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 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/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.ui;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.manager.TunerStatus;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableRowSorter;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
/**
* Panel containing a discovered tuners table and a tuner editor for a selected tuner.
*/
public class TunerViewPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(TunerViewPanel.class);
private static final String TABLE_PREFERENCE_KEY = "tuner.view.panel";
private DiscoveredTunerModel mDiscoveredTunerModel;
private JTable mTunerTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private TableRowSorter<DiscoveredTunerModel> mRowSorter;
private JideSplitPane mSplitPane;
private DiscoveredTunerEditor mDiscoveredTunerEditor;
private UserPreferences mUserPreferences;
/**
* Constructs an instance
* @param tunerManager for tuners
* @param userPreferences for making recordings in the tuner editor
*/
public TunerViewPanel(TunerManager tunerManager, UserPreferences userPreferences)
{
mDiscoveredTunerModel = tunerManager.getDiscoveredTunerModel();
mDiscoveredTunerEditor = new DiscoveredTunerEditor(userPreferences, tunerManager);
mUserPreferences = userPreferences;
init();
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[fill,grow]", "[fill,grow]"));
mRowSorter = new TableRowSorter<>(mDiscoveredTunerModel);
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
sortKeys.add(new RowSorter.SortKey(DiscoveredTunerModel.COLUMN_TUNER_TYPE, SortOrder.ASCENDING));
mRowSorter.setSortKeys(sortKeys);
mTunerTable = new JTable(mDiscoveredTunerModel);
mTunerTable.setRowSorter(mRowSorter);
mTunerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mTunerTable.getSelectionModel().addListSelectionListener(event ->
{
if(!event.getValueIsAdjusting())
{
int row = mTunerTable.getSelectedRow();
if(row >= 0)
{
int modelRow = mTunerTable.convertRowIndexToModel(row);
mDiscoveredTunerEditor.setItem(mDiscoveredTunerModel.getDiscoveredTuner(modelRow));
}
}
});
//Monitor for tuner removal events so we can update the editor when our selected tuner is removed
mDiscoveredTunerModel.addTableModelListener(e ->
{
//Detect when status is for the currently selected tuner
if(e.getType() == TableModelEvent.DELETE &&
mDiscoveredTunerEditor.hasItem() &&
!mDiscoveredTunerModel.hasTuner(mDiscoveredTunerEditor.getItem()))
{
mDiscoveredTunerEditor.setItem(null);
}
});
mDiscoveredTunerModel.addListener(tunerEvent ->
{
switch(tunerEvent.getEvent())
{
case UPDATE_LOCK_STATE:
if(tunerEvent.getTuner() != null)
{
int row = mTunerTable.getSelectedRow();
if(row >= 0)
{
int modelRow = mTunerTable.convertRowIndexToModel(row);
DiscoveredTuner selectedTuner = mDiscoveredTunerModel.getDiscoveredTuner(modelRow);
if(selectedTuner != null && selectedTuner.hasTuner() && tunerEvent.getTuner() == selectedTuner.getTuner())
{
mDiscoveredTunerEditor.setTunerLockState(selectedTuner.getTuner().getTunerController().isLocked());
}
}
}
break;
}
});
TableCellRenderer errorCellRenderer = new TunerStatusCellRenderer();
mTunerTable.getColumnModel().getColumn(DiscoveredTunerModel.COLUMN_TUNER_STATUS).setCellRenderer(errorCellRenderer);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTunerTable, TABLE_PREFERENCE_KEY);
JScrollPane tunerTableScroller = new JScrollPane(mTunerTable);
JPanel tunerTablePanel = new JPanel();
tunerTablePanel.setLayout(new MigLayout("insets 0 0 0 0", "[fill,grow][]", "[fill,grow][]"));
tunerTablePanel.add(tunerTableScroller, "span");
tunerTablePanel.add(new JLabel("")); //Empty spacer
// JButton addRecordingTunerButton = new JButton("Add Recording Tuner");
// addRecordingTunerButton.addActionListener(e ->
// {
// AddRecordingTunerDialog dialog = new AddRecordingTunerDialog(mUserPreferences, mDiscoveredTunerModel);
// dialog.setLocationRelativeTo(TunerViewPanel.this);
// EventQueue.invokeLater(() -> dialog.setVisible(true));
// });
//
// tunerTablePanel.add(addRecordingTunerButton);
tunerTablePanel.setPreferredSize(new Dimension(200,200));
JScrollPane editorScroller = new JScrollPane(mDiscoveredTunerEditor);
editorScroller.setPreferredSize(new Dimension(200, 200));
mSplitPane = new JideSplitPane();
mSplitPane.setOrientation(JideSplitPane.HORIZONTAL_SPLIT);
mSplitPane.add(tunerTablePanel);
mSplitPane.add(editorScroller);
add(mSplitPane);
}
/**
* Custom cell renderer for the TunerStatus enumeration column
*/
public class TunerStatusCellRenderer extends DefaultTableCellRenderer
{
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(value instanceof TunerStatus tunerStatus)
{
if(tunerStatus == TunerStatus.ERROR)
{
component.setForeground(Color.RED);
}
else if(tunerStatus == TunerStatus.DISABLED)
{
component.setForeground(Color.DARK_GRAY);
}
else
{
component.setForeground(table.getForeground());
}
}
else
{
component.setForeground(table.getForeground());
}
return component;
}
}
}