Files
sdrtrunk/src/spectrum/SpectralDisplayPanel.java
2016-01-23 14:07:01 -05:00

986 lines
27 KiB
Java

/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 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 java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
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.Arrays;
import java.util.Hashtable;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MouseInputAdapter;
import module.decode.DecoderType;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import playlist.PlaylistManager;
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.SourceException;
import source.tuner.Tuner;
import source.tuner.TunerSelectionListener;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.FrequencyChangeListener;
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 com.jidesoft.swing.JideSplitPane;
import controller.ConfigurationControllerModel;
import controller.channel.Channel;
import controller.channel.ChannelManager;
import controller.channel.ChannelUtils;
import dsp.filter.Window.WindowType;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
public class SpectralDisplayPanel extends JPanel
implements Listener<ComplexBuffer>,
FrequencyChangeListener,
IDFTWidthChangeProcessor,
TunerSelectionListener
{
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 PlaylistManager mPlaylistManager;
private SettingsManager mSettingsManager;
private ConfigurationControllerModel mController;
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( ChannelManager channelManager,
ConfigurationControllerModel controller,
PlaylistManager playlistManager,
SettingsManager settingsManager )
{
mController = controller;
mPlaylistManager = playlistManager;
mSettingsManager = settingsManager;
mSpectrumPanel = new SpectrumPanel( mSettingsManager );
mOverlayPanel = new OverlayPanel( mSettingsManager, channelManager );
mWaterfallPanel = new WaterfallPanel( mSettingsManager );
//Register to receive tuner selection events
if( mController != null )
{
mController.addListener( (TunerSelectionListener)this );
}
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, we couldn't parse the stored value
}
}
if( size == null )
{
size = DFTSize.FFT04096;
}
setDFTSize( size );
}
public void dispose()
{
/* De-register from receiving samples when the window closes */
clearTuner();
if( mController != null )
{
mController.removeListener( (TunerSelectionListener)this );
}
mController = null;
mPlaylistManager = null;
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 )
{
mDFTProcessor.setDFTSize( size );
mOverlayPanel.setDFTSize( size );
mDFTSize = size;
SystemProperties properties = SystemProperties.getInstance();
properties.set( FFT_SIZE_PROPERTY, size.name() );
properties.save();
setZoom( 0, 0, 0 );
}
@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 xAxisOffset 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.
*/
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
mOverlayPanel.frequencyChanged( event );
mDFTProcessor.frequencyChanged( event );
}
/**
* Complex sample buffer receive method
*/
@Override
public void receive( ComplexBuffer sampleBuffer )
{
mDFTProcessor.receive( sampleBuffer );
}
/**
* Responds to tuner selection event by deregistering from the current
* complex sample buffer source and registering with the tuner argument.
*/
@Override
public void tunerSelected( Tuner tuner )
{
clearTuner();
mDFTProcessor.clearBuffer();
mTuner = tuner;
if( mTuner != null )
{
//Register to receive frequency change events
mTuner.addListener( (FrequencyChangeListener)this );
//Register the dft processor to receive samples from the tuner
mTuner.addListener( (Listener<ComplexBuffer>)mDFTProcessor );
mSpectrumPanel.setSampleSize( mTuner.getSampleSize() );
//Fire frequency and sample rate change events so that the spectrum
//and overlay panels can synchronize
try
{
frequencyChanged( new FrequencyChangeEvent(
Event.FREQUENCY_CHANGE_NOTIFICATION, mTuner.getFrequency() ) );
frequencyChanged( new FrequencyChangeEvent(
Event.SAMPLE_RATE_CHANGE_NOTIFICATION, mTuner.getSampleRate() ) );
}
catch ( SourceException e )
{
mLog.info( "DFTProcessor - exception during new tuner setup - "
+ "couldn't get frequency from the tuner", e );
}
}
}
/**
* Tuner de-selection cleanup method
*/
public void clearTuner()
{
if( mTuner != null )
{
//Deregister for frequency change events from the tuner
mTuner.removeListener( (FrequencyChangeListener)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(
mPlaylistManager, 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( mController,
frequency,
type ) );
}
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 ConfigurationControllerModel mController;
private long mFrequency;
private DecoderType mDecoder;
public DecoderItem( ConfigurationControllerModel controller,
long frequency,
DecoderType type )
{
super( type.getDisplayString() );
mController = controller;
mFrequency = frequency;
mDecoder = type;
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mController.createChannel( mFrequency, mDecoder );
}
} );
}
}
}