Files
sdrtrunk/src/spectrum/SpectralDisplayPanel.java
Denny 57fab2b7ca #39 WIP - initial efforts to restructure the way tuners and tuner
controllers manager sample buffer processing and channel management.
General cleanup across all modules to remove the executor from each
of the start() methods.  Removed the B100 tuner class since it's not
yet implemented.
2017-12-03 11:10:50 -05:00

942 lines
29 KiB
Java

/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 spectrum;
import com.jidesoft.swing.JideSplitPane;
import controller.channel.Channel;
import controller.channel.ChannelModel;
import controller.channel.ChannelProcessingManager;
import controller.channel.ChannelUtils;
import dsp.filter.Window.WindowType;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import module.decode.DecoderType;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
import settings.ColorSetting.ColorSettingName;
import settings.ColorSettingMenuItem;
import settings.SettingsManager;
import source.ISourceEventProcessor;
import source.SourceEvent;
import source.tuner.Tuner;
import spectrum.OverlayPanel.ChannelDisplay;
import spectrum.converter.ComplexDecibelConverter;
import spectrum.converter.DFTResultsConverter;
import spectrum.menu.AveragingItem;
import spectrum.menu.DFTSizeItem;
import spectrum.menu.FFTWindowTypeItem;
import spectrum.menu.FrameRateItem;
import spectrum.menu.SmoothingItem;
import spectrum.menu.SmoothingTypeItem;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Hashtable;
public class SpectralDisplayPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor, IDFTWidthChangeProcessor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(SpectralDisplayPanel.class);
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat("000.00000");
public static final String FFT_SIZE_PROPERTY = "spectral.display.dft.size";
public static final int NO_ZOOM = 0;
public static final int MAX_ZOOM = 6;
private DFTSize mDFTSize = DFTSize.FFT04096;
private int mZoom = 0;
private int mDFTZoomWindowOffset = 0;
private JScrollPane mScrollPane;
private JLayeredPane mLayeredPanel;
private SpectrumPanel mSpectrumPanel;
private WaterfallPanel mWaterfallPanel;
private OverlayPanel mOverlayPanel;
private DFTProcessor mDFTProcessor;
private DFTResultsConverter mDFTConverter;
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private SettingsManager mSettingsManager;
private Tuner mTuner;
/**
* Spectral Display Panel provides a frequency component display with a
* historical waterfall display and a transparent overlay to show frequency,
* cursor and channel information.
*
* Mouse scrolling and zooming are supported and the waterfall display can
* be paused.
*
* Complex sample buffers are processed by a DFTProcessor and the output of
* the DFT is translated to decibels for display in the spectrum and
* waterfall components.
*/
public SpectralDisplayPanel(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
SettingsManager settingsManager)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mSettingsManager = settingsManager;
mSpectrumPanel = new SpectrumPanel(mSettingsManager);
mOverlayPanel = new OverlayPanel(mSettingsManager, mChannelModel);
mWaterfallPanel = new WaterfallPanel(mSettingsManager);
init();
loadSettings();
}
private void loadSettings()
{
SystemProperties properties = SystemProperties.getInstance();
String rawSize = properties.get(FFT_SIZE_PROPERTY, DFTSize.FFT04096.name());
DFTSize size = null;
if(rawSize != null)
{
try
{
size = DFTSize.valueOf(rawSize);
}
catch(Exception e)
{
//Do nothing
}
}
if(size == null)
{
size = DFTSize.FFT04096;
}
setDFTSize(size, false);
}
public void dispose()
{
/* De-register from receiving samples when the window closes */
clearTuner();
mSettingsManager = null;
mDFTProcessor.dispose();
mDFTProcessor = null;
mDFTConverter.dispose();
mDFTConverter = null;
mSpectrumPanel.dispose();
mSpectrumPanel = null;
mWaterfallPanel.dispose();
mWaterfallPanel = null;
mOverlayPanel.dispose();
mOverlayPanel = null;
mTuner = null;
}
/**
* Queues an FFT size change request. The scheduled executor will apply
* the change when it runs.
*/
public void setDFTSize(DFTSize size, boolean save)
{
mDFTProcessor.setDFTSize(size);
mOverlayPanel.setDFTSize(size);
mDFTSize = size;
if(save)
{
SystemProperties.getInstance().set(FFT_SIZE_PROPERTY, size.name());
}
setZoom(0, 0, 0);
}
public void setDFTSize(DFTSize size)
{
setDFTSize(size, true);
}
@Override
public DFTSize getDFTSize()
{
return mDFTSize;
}
public int getZoom()
{
return mZoom;
}
/**
* Sets the current zoom level which will be 2 to the power of zoom (2^zoom)
*
* 0 No Zoom
* 1 2x Zoom
* 2 4x Zoom
* 3 8x Zoom
* 4 16x Zoom
* 5 32x Zoom
* 6 64x Zoom
*
* @param zoom level, 0 - 6.
* @param frequency under the mouse to maintain while zooming
* @param windowOffset where to maintain the frequency under the mouse
*/
public void setZoom(int zoom, long frequency, double windowOffset)
{
if(zoom < NO_ZOOM)
{
zoom = NO_ZOOM;
}
else if(zoom > MAX_ZOOM)
{
zoom = MAX_ZOOM;
}
if(zoom != mZoom)
{
mZoom = zoom;
//Calculate the bin offset that would place the reference frequency
//at the left edge of the zoom window.
double binOffsetToFrequency = getBinOffset(frequency);
//Calculate the bin offset into the newly sized zoom window that
//would place the frequency in the same proportional window location
//that it was in the previous zoom size
double windowBinOffset = (double)getZoomWindowSizeInBins() * windowOffset;
//Set the overall offset to place the reference frequency in the
//same location in the newly zoomed window
double offset = binOffsetToFrequency - windowBinOffset;
mSpectrumPanel.setZoom(mZoom);
mOverlayPanel.setZoom(mZoom);
mWaterfallPanel.setZoom(mZoom);
setZoomWindowOffset(offset);
}
}
/**
* Sets the offset (in DFT bins) to the first bin that the zoom window displays
*/
public void setZoomWindowOffset(double offset)
{
if(offset < 0)
{
offset = 0;
}
if(offset > (mDFTSize.getSize() - getZoomWindowSizeInBins()))
{
offset = mDFTSize.getSize() - getZoomWindowSizeInBins();
}
mDFTZoomWindowOffset = (int)offset;
mSpectrumPanel.setZoomWindowOffset(mDFTZoomWindowOffset);
mOverlayPanel.setZoomWindowOffset(mDFTZoomWindowOffset);
mWaterfallPanel.setZoomWindowOffset(mDFTZoomWindowOffset);
}
/**
* Calculates the size of the current zoom window in DFT bins
*/
private int getZoomWindowSizeInBins()
{
return mDFTSize.getSize() / getZoomMultiplier();
}
public int getZoomMultiplier()
{
return (int)Math.pow(2.0, mZoom);
}
/**
* Calculates the overall offset of the frequency from the current minimum
* frequency in terms of total FFT width
*
* @param frequency
* @return
*/
private double getBinOffset(long frequency)
{
double offset = 0.0;
if(mOverlayPanel.containsFrequency(frequency))
{
offset = (double)mDFTSize.getSize() *
((double)(frequency - mOverlayPanel.getMinFrequency()) /
(double)mOverlayPanel.getBandwidth());
}
return offset;
}
/**
* Overrides JComponent method to return false, since we have overlapping
* panels with the spectrum and channel panels
*/
public boolean isOptimizedDrawingEnabled()
{
return false;
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow]", "[grow]"));
/**
* The layered pane holds the overlapping spectrum and channel panels
* and manages the sizing of each panel with the resize listener
*/
mLayeredPanel = new JLayeredPane();
mLayeredPanel.addComponentListener(new ResizeListener());
/**
* Create a mouse adapter to handle mouse events over the spectrum
* and waterfall panels
*/
MouseEventProcessor mouser = new MouseEventProcessor();
mOverlayPanel.addMouseListener(mouser);
mOverlayPanel.addMouseMotionListener(mouser);
mOverlayPanel.addMouseWheelListener(mouser);
//Add the spectrum and channel panels to the layered panel
mLayeredPanel.add(mSpectrumPanel, new Integer(0), 0);
mLayeredPanel.add(mOverlayPanel, new Integer(1), 0);
//Create the waterfall
mWaterfallPanel.addMouseListener(mouser);
mWaterfallPanel.addMouseMotionListener(mouser);
mWaterfallPanel.addMouseWheelListener(mouser);
/* Attempt to set a 50/50 split preferred size for the split pane */
double totalHeight = mLayeredPanel.getPreferredSize().getHeight() +
mWaterfallPanel.getPreferredSize().getHeight();
mLayeredPanel.setPreferredSize(new Dimension((int)mLayeredPanel
.getPreferredSize().getWidth(), (int)(totalHeight / 2.0d)));
mWaterfallPanel.setPreferredSize(new Dimension((int)mWaterfallPanel
.getPreferredSize().getWidth(), (int)(totalHeight / 2.0d)));
//Create the split pane to hold the layered pane and the waterfall
JideSplitPane splitPane = new JideSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setDividerSize(5);
splitPane.add(mLayeredPanel);
splitPane.add(mWaterfallPanel);
mScrollPane = new JScrollPane(splitPane);
add(mScrollPane, "grow");
/**
* Setup DFTProcessor to process samples and register the waterfall and
* spectrum panel to receive the processed dft results
*/
mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
mDFTConverter = new ComplexDecibelConverter();
mDFTProcessor.addConverter(mDFTConverter);
mDFTConverter.addListener((DFTResultsListener)mSpectrumPanel);
mDFTConverter.addListener((DFTResultsListener)mWaterfallPanel);
}
/**
* Receives frequency change events -- primarily from tuner components.
*/
public void process(SourceEvent event)
{
mOverlayPanel.process(event);
mDFTProcessor.process(event);
}
/**
* Complex sample buffer receive method
*/
@Override
public void receive(ComplexBuffer sampleBuffer)
{
mDFTProcessor.receive(sampleBuffer);
}
/**
* Responds to tuner event by deregistering from the current
* complex sample buffer source and registering with the tuner argument.
*/
public void showTuner(Tuner tuner)
{
clearTuner();
mDFTProcessor.clearBuffer();
mTuner = tuner;
if(mTuner != null)
{
//Register to receive frequency change events
mTuner.getTunerController().addListener(this);
//Register the dft processor to receive samples from the tuner
mTuner.getSourceManager().addComplexBufferListener(mDFTProcessor);
mSpectrumPanel.setSampleSize(mTuner.getSampleSize());
//Fire frequency and sample rate change events so that the spectrum
//and overlay panels can synchronize
process(SourceEvent.frequencyChange(mTuner.getTunerController().getFrequency()));
process(SourceEvent.sampleRateChange(mTuner.getTunerController().getSampleRate()));
}
}
/**
* Tuner de-selection cleanup method
*/
public void clearTuner()
{
if(mTuner != null)
{
//Deregister for frequency change events from the tuner
mTuner.getTunerController().removeListener(this);
//Deregister the dft processor from receiving samples
mTuner.removeListener((Listener<ComplexBuffer>)mDFTProcessor);
mTuner = null;
}
}
/**
* Monitors the sizing of the layered pane and resizes the spectrum and
* channel panels whenever the layered pane is resized
*/
public class ResizeListener implements ComponentListener
{
@Override
public void componentResized(ComponentEvent e)
{
Component c = e.getComponent();
mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
mOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
}
@Override
public void componentHidden(ComponentEvent arg0)
{
}
@Override
public void componentMoved(ComponentEvent arg0)
{
}
@Override
public void componentShown(ComponentEvent arg0)
{
}
}
/**
* Mouse event handler for the spectral display panel.
*/
public class MouseEventProcessor extends MouseInputAdapter
{
private int mDFTZoomWindowOffsetAtDragStart = 0;
private int mDragStartX = 0;
private double mPixelsPerBin;
public MouseEventProcessor()
{
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
int zoom = mZoom - e.getWheelRotation();
long frequency = mOverlayPanel.getFrequencyFromAxis(e.getX());
double windowOffset = (double)e.getX() / (double)getWidth();
setZoom(zoom, frequency, windowOffset);
}
@Override
public void mouseMoved(MouseEvent event)
{
update(event);
}
@Override
public void mouseDragged(MouseEvent event)
{
update(event);
int dragDistance = mDragStartX - event.getX();
double binDistance = (double)dragDistance / mPixelsPerBin;
int offset = (int)(mDFTZoomWindowOffsetAtDragStart + binDistance);
if(offset < 0)
{
offset = 0;
}
int maxOffset = mDFTSize.getSize() - (mDFTSize.getSize() / getZoomMultiplier());
if(offset > maxOffset)
{
offset = maxOffset;
}
setZoomWindowOffset(offset);
}
@Override
public void mousePressed(MouseEvent e)
{
mDragStartX = e.getX();
mDFTZoomWindowOffsetAtDragStart = mDFTZoomWindowOffset;
mPixelsPerBin = (double)getWidth() /
((double)(mDFTSize.getSize()) /
(double)getZoomMultiplier());
}
/**
* Updates the cursor display while the mouse is performing actions
*/
private void update(MouseEvent event)
{
if(event.getComponent() == mOverlayPanel)
{
mOverlayPanel.setCursorLocation(event.getPoint());
}
else
{
mWaterfallPanel.setCursorLocation(event.getPoint());
mWaterfallPanel.setCursorFrequency(
mOverlayPanel.getFrequencyFromAxis(event.getPoint().x));
}
}
@Override
public void mouseEntered(MouseEvent e)
{
if(e.getComponent() == mOverlayPanel)
{
mOverlayPanel.setCursorVisible(true);
}
else
{
mWaterfallPanel.setCursorVisible(true);
}
}
@Override
public void mouseExited(MouseEvent e)
{
mOverlayPanel.setCursorVisible(false);
mWaterfallPanel.setCursorVisible(false);
}
/**
* Displays the context menu.
*/
@Override
public void mouseClicked(MouseEvent event)
{
if(SwingUtilities.isRightMouseButton(event))
{
JPopupMenu contextMenu = new JPopupMenu();
if(event.getComponent() == mWaterfallPanel)
{
contextMenu.add(new PauseItem(mWaterfallPanel, "Pause"));
}
long frequency =
mOverlayPanel.getFrequencyFromAxis(event.getX());
if(event.getComponent() == mOverlayPanel)
{
ArrayList<Channel> channels =
mOverlayPanel.getChannelsAtFrequency(frequency);
for(Channel channel : channels)
{
JMenu channelMenu = ChannelUtils.getContextMenu(mChannelModel,
mChannelProcessingManager, channel,
SpectralDisplayPanel.this);
if(channelMenu != null)
{
contextMenu.add(channelMenu);
}
}
if(!channels.isEmpty())
{
contextMenu.add(new JSeparator());
}
}
// JMenu frequencyMenu = new JMenu(
// sCURSOR_FORMAT.format((float) frequency / 1000000.0f));
//
// JMenu decoderMenu = new JMenu("Add Decoder");
//
// for(DecoderType type : DecoderType.getPrimaryDecoders())
// {
// decoderMenu.add(new DecoderItem(type, frequency));
// }
//
// frequencyMenu.add(decoderMenu);
//
// contextMenu.add(frequencyMenu);
//
// contextMenu.add(new JSeparator());
/**
* Color Menus
*/
JMenu colorMenu = new JMenu("Color");
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.CHANNEL_CONFIG));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.CHANNEL_CONFIG_PROCESSING));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.CHANNEL_CONFIG_SELECTED));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.SPECTRUM_CURSOR));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.SPECTRUM_LINE));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.SPECTRUM_BACKGROUND));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.SPECTRUM_GRADIENT_BOTTOM));
colorMenu.add(new ColorSettingMenuItem(mSettingsManager,
ColorSettingName.SPECTRUM_GRADIENT_TOP));
contextMenu.add(colorMenu);
/**
* Display items: fft and frame rate
*/
JMenu displayMenu = new JMenu("Display");
contextMenu.add(displayMenu);
if(event.getComponent() != mWaterfallPanel)
{
/**
* Averaging menu
*/
JMenu averagingMenu = new JMenu("Averaging");
averagingMenu.add(
new AveragingItem(mSpectrumPanel, 4));
displayMenu.add(averagingMenu);
/**
* Channel Display setting menu
*/
JMenu channelDisplayMenu = new JMenu("Channel");
channelDisplayMenu.add(new ChannelDisplayItem(
mOverlayPanel, ChannelDisplay.ALL));
channelDisplayMenu.add(new ChannelDisplayItem(
mOverlayPanel, ChannelDisplay.ENABLED));
channelDisplayMenu.add(new ChannelDisplayItem(
mOverlayPanel, ChannelDisplay.NONE));
displayMenu.add(channelDisplayMenu);
}
/**
* FFT width
*/
JMenu fftWidthMenu = new JMenu("FFT Width");
displayMenu.add(fftWidthMenu);
for(DFTSize width : DFTSize.values())
{
fftWidthMenu.add(new DFTSizeItem(SpectralDisplayPanel.this, width));
}
/**
* DFT Processor Frame Rate
*/
JMenu frameRateMenu = new JMenu("Frame Rate");
displayMenu.add(frameRateMenu);
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 14));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 16));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 18));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 20));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 25));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 30));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 40));
frameRateMenu.add(new FrameRateItem(mDFTProcessor, 50));
/**
* FFT Window Type
*/
JMenu fftWindowType = new JMenu("Window Type");
displayMenu.add(fftWindowType);
for(WindowType type : WindowType.values())
{
fftWindowType.add(
new FFTWindowTypeItem(mDFTProcessor, type));
}
if(event.getComponent() != mWaterfallPanel)
{
/**
* Smoothing menu
*/
JMenu smoothingMenu = new JMenu("Smoothing");
if(mSpectrumPanel.getSmoothingType() != SmoothingType.NONE)
{
smoothingMenu.add(new SmoothingItem(mSpectrumPanel, 5));
smoothingMenu.add(new JSeparator());
}
smoothingMenu.add(new SmoothingTypeItem(mSpectrumPanel, SmoothingType.GAUSSIAN));
smoothingMenu.add(new SmoothingTypeItem(mSpectrumPanel, SmoothingType.TRIANGLE));
smoothingMenu.add(new SmoothingTypeItem(mSpectrumPanel, SmoothingType.RECTANGLE));
smoothingMenu.add(new SmoothingTypeItem(mSpectrumPanel, SmoothingType.NONE));
displayMenu.add(smoothingMenu);
}
/*
* Zoom menu
*/
JMenuItem zoomMenu = new JMenu("Zoom");
double windowOffset = (double)event.getX() / (double)getWidth();
zoomMenu.add(new ZoomItem(frequency, windowOffset));
contextMenu.add(zoomMenu);
if(contextMenu != null)
{
if(event.getComponent() == mOverlayPanel)
{
contextMenu.show(mOverlayPanel,
event.getX(),
event.getY());
}
else
{
contextMenu.show(mWaterfallPanel,
event.getX(),
event.getY());
}
}
}
}
}
public class PauseItem extends JCheckBoxMenuItem
{
private static final long serialVersionUID = 1L;
private Pausable mPausable;
public PauseItem(Pausable pausable, String label)
{
super(label);
final boolean paused = pausable.isPaused();
setSelected(paused);
mPausable = pausable;
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mPausable.setPaused(!paused);
}
});
}
});
}
}
public class ZoomItem extends JSlider
{
private static final long serialVersionUID = 1L;
private long mFrequency;
private double mWindowOffset;
public ZoomItem(long frequency, double windowOffset)
{
super(NO_ZOOM, MAX_ZOOM, mZoom);
mFrequency = frequency;
mWindowOffset = windowOffset;
Hashtable<Integer,JComponent> labels = new Hashtable<>();
labels.put(new Integer(0), new JLabel("1x"));
labels.put(new Integer(1), new JLabel("2x"));
labels.put(new Integer(2), new JLabel("4x"));
labels.put(new Integer(3), new JLabel("8x"));
labels.put(new Integer(4), new JLabel("16x"));
labels.put(new Integer(5), new JLabel("32x"));
labels.put(new Integer(6), new JLabel("64x"));
setLabelTable(labels);
setMajorTickSpacing(1);
setMinorTickSpacing(1);
setPaintTicks(true);
setPaintLabels(true);
this.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
setZoom(getValue(), mFrequency, mWindowOffset);
}
});
}
}
public class ChannelDisplayItem extends JCheckBoxMenuItem
{
private static final long serialVersionUID = 1L;
private OverlayPanel mOverlayPanel;
private ChannelDisplay mChannelDisplay;
public ChannelDisplayItem(OverlayPanel panel, ChannelDisplay display)
{
super(display.name());
mOverlayPanel = panel;
mChannelDisplay = display;
setSelected(mOverlayPanel.getChannelDisplay() == mChannelDisplay);
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mOverlayPanel.setChannelDisplay(mChannelDisplay);
}
});
}
});
}
}
/**
* Context menu item to provide one-click access to starting a channel
* processing with the selected decoder
*/
public class DecoderItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
private ChannelModel mChannelModel;
private long mFrequency;
private DecoderType mDecoder;
public DecoderItem(DecoderType type, long frequency)
{
super(type.getDisplayString());
mFrequency = frequency;
mDecoder = type;
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mChannelModel.createChannel(mDecoder, mFrequency);
}
});
}
}
}