mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user