From 9ef156b06162383637abdc969c6950e2e306ae73 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 26 Feb 2017 05:47:26 -0500 Subject: [PATCH] Resolves issue with spectral display rendering artifacts from cursor frequency readout when accessing menu items directly above the spectral display. --- src/spectrum/OverlayPanel.java | 1474 +++++++++++++++----------------- 1 file changed, 711 insertions(+), 763 deletions(-) diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java index d826a61c..ae0c23fc 100644 --- a/src/spectrum/OverlayPanel.java +++ b/src/spectrum/OverlayPanel.java @@ -1,44 +1,30 @@ /******************************************************************************* - * 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 + * 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 + * ******************************************************************************/ package spectrum; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.Stroke; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.concurrent.CopyOnWriteArrayList; - -import javax.swing.JPanel; - +import controller.channel.Channel; +import controller.channel.Channel.ChannelType; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEventListener; +import controller.channel.ChannelModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import settings.ColorSetting; import settings.ColorSetting.ColorSettingName; import settings.Setting; @@ -47,151 +33,151 @@ import settings.SettingsManager; import source.tuner.TunerChannel; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.IFrequencyChangeProcessor; -import controller.channel.Channel; -import controller.channel.Channel.ChannelType; -import controller.channel.ChannelEvent; -import controller.channel.ChannelEventListener; -import controller.channel.ChannelModel; -public class OverlayPanel extends JPanel - implements ChannelEventListener, - IFrequencyChangeProcessor, - SettingChangeListener +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; + +public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequencyChangeProcessor, + SettingChangeListener { - private static final long serialVersionUID = 1L; - - private final static Logger mLog = - LoggerFactory.getLogger( OverlayPanel.class ); + private static final long serialVersionUID = 1L; - private final static RenderingHints RENDERING_HINTS = - new RenderingHints( RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); + private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class); - static - { - RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY ); - } - - private final static BasicStroke DASHED_STROKE = new BasicStroke(0.8f, - BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5.0f, new float[] {2.0f, 4.0f}, 0.0f); - - private static DecimalFormat CURSOR_FORMAT = new DecimalFormat( "000.00000" ); - private long mFrequency = 0; - private int mBandwidth = 0; - private Point mCursorLocation = new Point( 0, 0 ); - private boolean mCursorVisible = false; + private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); - private DFTSize mDFTSize = DFTSize.FFT04096; - private int mZoom = 0; - private int mDFTZoomWindowOffset = 0; - - /** - * Colors used by this component - */ - private Color mColorChannelConfig; - private Color mColorChannelConfigProcessing; - private Color mColorChannelConfigSelected; - private Color mColorSpectrumBackground; - private Color mColorSpectrumCursor; - private Color mColorSpectrumLine; - - //Currently visible/displayable channels - private CopyOnWriteArrayList mVisibleChannels = new CopyOnWriteArrayList(); - private ChannelDisplay mChannelDisplay = ChannelDisplay.ALL; - - //Defines the offset at the bottom of the spectral display to account for - //the frequency labels - private double mSpectrumInset = 20.0d; - private LabelSizeManager mLabelSizeMonitor = new LabelSizeManager(); - - private SettingsManager mSettingsManager; - private ChannelModel mChannelModel; - /** - * Translucent overlay panel for displaying channel configurations, - * processing channels, selected channels, frequency labels and lines, and - * a cursor with a frequency readout. - */ - public OverlayPanel( SettingsManager settingsManager, - ChannelModel channelModel ) + static { - mSettingsManager = settingsManager; - - if( mSettingsManager != null ) - { - mSettingsManager.addListener( this ); - } - - mChannelModel = channelModel; - - if( mChannelModel != null ) - { - mChannelModel.addListener( this ); - } - - addComponentListener( mLabelSizeMonitor ); - - //Set the background transparent, so the spectrum display can be seen - setOpaque( false ); - - //Fetch color settings from settings manager - setColors(); + RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + } + + private final static BasicStroke DASHED_STROKE = new BasicStroke(0.8f, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, 5.0f, new float[]{2.0f, 4.0f}, 0.0f); + + private static DecimalFormat CURSOR_FORMAT = new DecimalFormat("000.00000"); + private long mFrequency = 0; + private int mBandwidth = 0; + private Point mCursorLocation = new Point(0, 0); + private boolean mCursorVisible = false; + + private DFTSize mDFTSize = DFTSize.FFT04096; + private int mZoom = 0; + private int mDFTZoomWindowOffset = 0; + + /** + * Colors used by this component + */ + private Color mColorChannelConfig; + private Color mColorChannelConfigProcessing; + private Color mColorChannelConfigSelected; + private Color mColorSpectrumBackground; + private Color mColorSpectrumCursor; + private Color mColorSpectrumLine; + + //Currently visible/displayable channels + private CopyOnWriteArrayList mVisibleChannels = new CopyOnWriteArrayList(); + private ChannelDisplay mChannelDisplay = ChannelDisplay.ALL; + + //Defines the offset at the bottom of the spectral display to account for + //the frequency labels + private double mSpectrumInset = 20.0d; + private LabelSizeManager mLabelSizeMonitor = new LabelSizeManager(); + + private SettingsManager mSettingsManager; + private ChannelModel mChannelModel; + + /** + * Translucent overlay panel for displaying channel configurations, + * processing channels, selected channels, frequency labels and lines, and + * a cursor with a frequency readout. + */ + public OverlayPanel(SettingsManager settingsManager, ChannelModel channelModel) + { + mSettingsManager = settingsManager; + + if(mSettingsManager != null) + { + mSettingsManager.addListener(this); + } + + mChannelModel = channelModel; + + if(mChannelModel != null) + { + mChannelModel.addListener(this); + } + + addComponentListener(mLabelSizeMonitor); + + //Set the background transparent, so the spectrum display can be seen + setOpaque(false); + + //Fetch color settings from settings manager + setColors(); + } + + public void dispose() + { + if(mChannelModel != null) + { + mChannelModel.removeListener(this); + } + + mChannelModel = null; + + mVisibleChannels.clear(); + + if(mSettingsManager != null) + { + mSettingsManager.removeListener(this); + } + + mSettingsManager = null; + } + + /** + * Sets/changes the DFT bin size + */ + public void setDFTSize(DFTSize size) + { + mDFTSize = size; + } + + public ChannelDisplay getChannelDisplay() + { + return mChannelDisplay; + } + + public void setChannelDisplay(ChannelDisplay display) + { + mChannelDisplay = display; + } + + public void setCursorLocation(Point point) + { + mCursorLocation = point; + + repaint(); + } + + public void setCursorVisible(boolean visible) + { + mCursorVisible = visible; + + repaint(); } - - public void dispose() - { - if( mChannelModel != null ) - { - mChannelModel.removeListener( this ); - } - - mChannelModel = null; - - mVisibleChannels.clear(); - - if( mSettingsManager != null ) - { - mSettingsManager.removeListener( this ); - } - - mSettingsManager = null; - } - - /** - * Sets/changes the DFT bin size - */ - public void setDFTSize( DFTSize size ) - { - mDFTSize = size; - } - - public ChannelDisplay getChannelDisplay() - { - return mChannelDisplay; - } - public void setChannelDisplay( ChannelDisplay display ) - { - mChannelDisplay = display; - } - - public void setCursorLocation( Point point ) - { - mCursorLocation = point; - - repaint(); - } - - public void setCursorVisible( boolean visible ) - { - mCursorVisible = visible; - - repaint(); - } - /** * Sets the current zoom level (2^zoom) - * + * * 0 No Zoom * 1 2x Zoom * 2 4x Zoom @@ -199,745 +185,707 @@ public class OverlayPanel extends JPanel * 4 16x Zoom * 5 32x Zoom * 6 64x Zoom - * + * * @param zoom level, 0 - 6. * @param offset into the FFT bins for start zoom window */ - public void setZoom( int zoom ) - { - mZoom = zoom; - - mLabelSizeMonitor.update(); - } - - public void setZoomWindowOffset( int offset ) - { - mDFTZoomWindowOffset = offset; - } - - /** - * Fetches the color settings from the settings manager - */ - private void setColors() - { - mColorChannelConfig = getColor( ColorSettingName.CHANNEL_CONFIG ); - - mColorChannelConfigProcessing = - getColor( ColorSettingName.CHANNEL_CONFIG_PROCESSING ); - - mColorChannelConfigSelected = - getColor( ColorSettingName.CHANNEL_CONFIG_SELECTED ); - - mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR ); - - mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE ); - - mColorSpectrumBackground = - getColor( ColorSettingName.SPECTRUM_BACKGROUND ); - } - - /** - * Fetches a named color setting from the settings manager. If the setting - * doesn't exist, creates the setting using the defaultColor - */ - private Color getColor( ColorSettingName name ) - { - ColorSetting setting = mSettingsManager.getColorSetting( name ); - - return setting.getColor(); - } - - /** - * Monitors for setting changes. Colors can be changed by external actions - * and will automatically update in this class - */ - @Override - public void settingChanged( Setting setting ) + public void setZoom(int zoom) { - if( setting instanceof ColorSetting ) - { - ColorSetting colorSetting = (ColorSetting)setting; - - switch( colorSetting.getColorSettingName() ) - { - case CHANNEL_CONFIG: - mColorChannelConfig = colorSetting.getColor(); - break; - case CHANNEL_CONFIG_PROCESSING: - mColorChannelConfigProcessing = colorSetting.getColor(); - break; - case CHANNEL_CONFIG_SELECTED: - mColorChannelConfigSelected = colorSetting.getColor(); - break; - case SPECTRUM_BACKGROUND: - mColorSpectrumBackground = colorSetting.getColor(); - break; - case SPECTRUM_CURSOR: - mColorSpectrumCursor = colorSetting.getColor(); - break; - case SPECTRUM_LINE: - mColorSpectrumLine = colorSetting.getColor(); - break; - default: - break; - } - } + mZoom = zoom; + + mLabelSizeMonitor.update(); } - - /** - * Renders the channel configs, lines, labels, and cursor - */ - @Override - public void paintComponent( Graphics g ) - { - super.paintComponent( g ); - - Graphics2D graphics = (Graphics2D) g; - graphics.setBackground( mColorSpectrumBackground ); - graphics.setRenderingHints( RENDERING_HINTS ); - - drawFrequencies( graphics ); - drawChannels( graphics ); - drawCursor( graphics ); + public void setZoomWindowOffset(int offset) + { + mDFTZoomWindowOffset = offset; } /** - * Draws a cursor on the panel, whenever the mouse is hovering over the + * Fetches the color settings from the settings manager + */ + private void setColors() + { + mColorChannelConfig = getColor(ColorSettingName.CHANNEL_CONFIG); + + mColorChannelConfigProcessing = getColor(ColorSettingName.CHANNEL_CONFIG_PROCESSING); + + mColorChannelConfigSelected = getColor(ColorSettingName.CHANNEL_CONFIG_SELECTED); + + mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR); + + mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE); + + mColorSpectrumBackground = getColor(ColorSettingName.SPECTRUM_BACKGROUND); + } + + /** + * Fetches a named color setting from the settings manager. If the setting + * doesn't exist, creates the setting using the defaultColor + */ + private Color getColor(ColorSettingName name) + { + ColorSetting setting = mSettingsManager.getColorSetting(name); + + return setting.getColor(); + } + + /** + * Monitors for setting changes. Colors can be changed by external actions + * and will automatically update in this class + */ + @Override + public void settingChanged(Setting setting) + { + if(setting instanceof ColorSetting) + { + ColorSetting colorSetting = (ColorSetting)setting; + + switch(colorSetting.getColorSettingName()) + { + case CHANNEL_CONFIG: + mColorChannelConfig = colorSetting.getColor(); + break; + case CHANNEL_CONFIG_PROCESSING: + mColorChannelConfigProcessing = colorSetting.getColor(); + break; + case CHANNEL_CONFIG_SELECTED: + mColorChannelConfigSelected = colorSetting.getColor(); + break; + case SPECTRUM_BACKGROUND: + mColorSpectrumBackground = colorSetting.getColor(); + break; + case SPECTRUM_CURSOR: + mColorSpectrumCursor = colorSetting.getColor(); + break; + case SPECTRUM_LINE: + mColorSpectrumLine = colorSetting.getColor(); + break; + default: + break; + } + } + } + + /** + * Renders the channel configs, lines, labels, and cursor + */ + @Override + public void paintComponent(Graphics g) + { + super.paintComponent(g); + + Graphics2D graphics = (Graphics2D)g; + graphics.setBackground(mColorSpectrumBackground); + + graphics.setRenderingHints(RENDERING_HINTS); + + drawFrequencies(graphics); + drawChannels(graphics); + drawCursor(graphics); + } + + /** + * Draws a cursor on the panel, whenever the mouse is hovering over the * panel */ - private void drawCursor( Graphics2D graphics ) + private void drawCursor(Graphics2D graphics) { - if( mCursorVisible ) - { - drawFrequencyLine( graphics, - mCursorLocation.x, - mColorSpectrumCursor ); + if(mCursorVisible) + { + drawFrequencyLine(graphics, mCursorLocation.x, mColorSpectrumCursor); - String frequency = CURSOR_FORMAT.format( - getFrequencyFromAxis( mCursorLocation.getX() ) / 1E6D ); + String frequency = CURSOR_FORMAT.format(getFrequencyFromAxis(mCursorLocation.getX()) / 1E6D); - graphics.drawString( frequency , - mCursorLocation.x + 5, - mCursorLocation.y ); + FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont()); - if( mZoom != 0 ) - { - graphics.drawString( "Zoom: " + (int)Math.pow( 2.0, mZoom ) + "x", - mCursorLocation.x + 17, mCursorLocation.y + 11 ); - } - } + Rectangle2D rect = fontMetrics.getStringBounds(frequency, graphics); + + if(mCursorLocation.y > rect.getHeight()) + { + graphics.drawString(frequency, mCursorLocation.x + 5, mCursorLocation.y); + } + + if(mZoom != 0) + { + graphics.drawString("Zoom: " + (int)Math.pow(2.0, mZoom) + "x", mCursorLocation.x + 17, + mCursorLocation.y + 11); + } + } } - + /** * Draws the frequency lines and labels every 10kHz */ - private void drawFrequencies( Graphics2D graphics ) + private void drawFrequencies(Graphics2D graphics) { - Stroke currentStroke = graphics.getStroke(); - -// graphics.setStroke( DASHED_STROKE ); + Stroke currentStroke = graphics.getStroke(); - long minFrequency = getMinDisplayFrequency(); - long maxFrequency = getMaxDisplayFrequency(); + long minFrequency = getMinDisplayFrequency(); + long maxFrequency = getMaxDisplayFrequency(); - //Frequency increments for label and tick spacing - int label = mLabelSizeMonitor.getLabelIncrement( graphics ); - int major = mLabelSizeMonitor.getMajorTickIncrement( graphics ); - int minor = mLabelSizeMonitor.getMinorTickIncrement( graphics ); + //Frequency increments for label and tick spacing + int label = mLabelSizeMonitor.getLabelIncrement(graphics); + int major = mLabelSizeMonitor.getMajorTickIncrement(graphics); + int minor = mLabelSizeMonitor.getMinorTickIncrement(graphics); - //Avoid divide by zero error - if( minor == 0 ) - { - minor = 1; - } - - //Adjust the start frequency to a multiple of the minor tick spacing - long frequency = minFrequency - ( minFrequency % minor ); - - while( frequency < maxFrequency ) - { - if( frequency % label == 0 ) - { - drawFrequencyLineAndLabel( graphics, frequency ); - } - else if( frequency % major == 0 ) - { - drawTickLine( graphics, frequency, true ); - } - else - { - drawTickLine( graphics, frequency, false ); - } - - frequency += minor; - } - -// graphics.setStroke( currentStroke ); + //Avoid divide by zero error + if(minor == 0) + { + minor = 1; + } + + //Adjust the start frequency to a multiple of the minor tick spacing + long frequency = minFrequency - (minFrequency % minor); + + while(frequency < maxFrequency) + { + if(frequency % label == 0) + { + drawFrequencyLineAndLabel(graphics, frequency); + } + else if(frequency % major == 0) + { + drawTickLine(graphics, frequency, true); + } + else + { + drawTickLine(graphics, frequency, false); + } + + frequency += minor; + } } - + /** * Draws a vertical line and a corresponding frequency label at the bottom */ - private void drawFrequencyLineAndLabel( Graphics2D graphics, long frequency ) + private void drawFrequencyLineAndLabel(Graphics2D graphics, long frequency) { - double xAxis = getAxisFromFrequency( frequency ); - - drawFrequencyLine( graphics, xAxis, mColorSpectrumLine ); - - drawTickLine( graphics, frequency, false ); + double xAxis = getAxisFromFrequency(frequency); - graphics.setColor( mColorSpectrumLine ); + drawFrequencyLine(graphics, xAxis, mColorSpectrumLine); - drawFrequencyLabel( graphics, xAxis, frequency ); + drawTickLine(graphics, frequency, false); + + graphics.setColor(mColorSpectrumLine); + + drawFrequencyLabel(graphics, xAxis, frequency); } /** * Draws a vertical line at the xaxis */ - private void drawTickLine( Graphics2D graphics, long frequency, boolean major ) + private void drawTickLine(Graphics2D graphics, long frequency, boolean major) { - graphics.setColor( mColorSpectrumLine ); - - double xAxis = getAxisFromFrequency( frequency ); + graphics.setColor(mColorSpectrumLine); - double start = getSize().getHeight() - mSpectrumInset; - double end = start + ( major ? 9.0d : 3.0d ); - - graphics.draw( new Line2D.Double( xAxis, start, xAxis, end ) ); + double xAxis = getAxisFromFrequency(frequency); + + double start = getSize().getHeight() - mSpectrumInset; + double end = start + (major ? 9.0d : 3.0d); + + graphics.draw(new Line2D.Double(xAxis, start, xAxis, end)); } /** * Draws a vertical line at the xaxis */ - private void drawFrequencyLine( Graphics2D graphics, double xaxis, Color color ) + private void drawFrequencyLine(Graphics2D graphics, double xaxis, Color color) { - graphics.setColor( color ); - - graphics.draw( new Line2D.Double( xaxis, 0.0d, - xaxis, getSize().getHeight() - mSpectrumInset ) ); + graphics.setColor(color); + + graphics.draw(new Line2D.Double(xaxis, 0.0d, xaxis, getSize().getHeight() - mSpectrumInset)); } /** * Draws a vertical line at the xaxis */ - private void drawChannelCenterLine( Graphics2D graphics, double xaxis ) + private void drawChannelCenterLine(Graphics2D graphics, double xaxis) { - double height = getSize().getHeight() - mSpectrumInset; + double height = getSize().getHeight() - mSpectrumInset; - graphics.setColor( Color.LIGHT_GRAY ); + graphics.setColor(Color.LIGHT_GRAY); - graphics.draw( new Line2D.Double( xaxis, height * 0.65d, - xaxis, height - 1.0d ) ); + graphics.draw(new Line2D.Double(xaxis, height * 0.65d, xaxis, height - 1.0d)); } - + /** * Draws a vertical line at the xaxis */ - private void drawAFC( Graphics2D graphics, double frequencyAxis, double errorAxis ) + private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis) { - double height = getSize().getHeight() - mSpectrumInset; - double lineHeight = height * 0.75d; + double height = getSize().getHeight() - mSpectrumInset; + double lineHeight = height * 0.75d; - graphics.setColor( Color.YELLOW ); + graphics.setColor(Color.YELLOW); - //Vertical frequency error line - graphics.draw( new Line2D.Double( errorAxis, lineHeight, - errorAxis, height - 1.0d ) ); + //Vertical frequency error line + graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d)); - //Horizontal line connecting frequency and error lines - graphics.draw( new Line2D.Double( frequencyAxis, lineHeight, - errorAxis, lineHeight ) ); + //Horizontal line connecting frequency and error lines + graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight)); } - + /** * Returns the x-axis value corresponding to the frequency */ - private double getAxisFromFrequency( long frequency ) + private double getAxisFromFrequency(long frequency) { - double screenWidth = (double)getSize().getWidth(); + double screenWidth = (double)getSize().getWidth(); - double pixelsPerBin = screenWidth / (double)mDFTSize.getSize(); - - double pixelOffsetToMinDisplayFrequency = pixelsPerBin * 2.0d; + double pixelsPerBin = screenWidth / (double)mDFTSize.getSize(); - //Calculate frequency offset from the min frequency - double frequencyOffset = (double)( frequency - getMinDisplayFrequency() ); + double pixelOffsetToMinDisplayFrequency = pixelsPerBin * 2.0d; - //Determine ratio of frequency offset to overall bandwidth - double ratio = frequencyOffset / (double)getDisplayBandwidth(); + //Calculate frequency offset from the min frequency + double frequencyOffset = (double)(frequency - getMinDisplayFrequency()); - //Apply the ratio to the screen width minus 1 bin width - double screenOffset = screenWidth * ratio; - - return pixelOffsetToMinDisplayFrequency + screenOffset; + //Determine ratio of frequency offset to overall bandwidth + double ratio = frequencyOffset / (double)getDisplayBandwidth(); + + //Apply the ratio to the screen width minus 1 bin width + double screenOffset = screenWidth * ratio; + + return pixelOffsetToMinDisplayFrequency + screenOffset; } /** * Returns the frequency corresponding to the x-axis value using the current * zoom level. */ - public long getFrequencyFromAxis( double xAxis ) + public long getFrequencyFromAxis(double xAxis) { - double width = getSize().getWidth(); - - double offset = xAxis / width; - - long frequency = getMinDisplayFrequency() + - Math.round( (double)getDisplayBandwidth() * offset ); - - if( frequency > ( getMaxFrequency() ) ) - { - frequency = getMaxFrequency(); - } - - return frequency; + double width = getSize().getWidth(); + + double offset = xAxis / width; + + long frequency = getMinDisplayFrequency() + Math.round((double)getDisplayBandwidth() * offset); + + if(frequency > (getMaxFrequency())) + { + frequency = getMaxFrequency(); + } + + return frequency; } /** * Draws a frequency label at the x-axis position, at the bottom of the panel */ - private void drawFrequencyLabel( Graphics2D graphics, - double xaxis, - long frequency ) + private void drawFrequencyLabel(Graphics2D graphics, double xaxis, long frequency) { - String label = mLabelSizeMonitor.format( frequency ); - - FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() ); + String label = mLabelSizeMonitor.format(frequency); - Rectangle2D rect = fontMetrics.getStringBounds( label, graphics ); + FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont()); - float xOffset = (float)rect.getWidth() / 2; + Rectangle2D rect = fontMetrics.getStringBounds(label, graphics); - graphics.drawString( label, (float)( xaxis - xOffset ), - (float)( getSize().getHeight() - 2.0f ) ); + float xOffset = (float)rect.getWidth() / 2; + + graphics.drawString(label, (float)(xaxis - xOffset), (float)(getSize().getHeight() - 2.0f)); } - + /** * Draws visible channel configs as translucent shaded frequency regions */ - private void drawChannels( Graphics2D graphics ) + private void drawChannels(Graphics2D graphics) { - for( Channel channel: mVisibleChannels ) - { - if( mChannelDisplay == ChannelDisplay.ALL || - ( mChannelDisplay == ChannelDisplay.ENABLED && - channel.getEnabled() ) ) - { - //Choose the correct background color to use - if( channel.isSelected() ) - { - graphics.setColor( mColorChannelConfigSelected ); - } - else if( channel.getEnabled() ) - { - graphics.setColor( mColorChannelConfigProcessing ); - } - else - { - graphics.setColor( mColorChannelConfig ); - } - - TunerChannel tunerChannel = channel.getTunerChannel(); - - if( tunerChannel != null ) + for(Channel channel : mVisibleChannels) + { + if(mChannelDisplay == ChannelDisplay.ALL || + (mChannelDisplay == ChannelDisplay.ENABLED && channel.getEnabled())) + { + //Choose the correct background color to use + if(channel.isSelected()) { - double xAxis = getAxisFromFrequency( tunerChannel.getFrequency() ); + graphics.setColor(mColorChannelConfigSelected); + } + else if(channel.getEnabled()) + { + graphics.setColor(mColorChannelConfigProcessing); + } + else + { + graphics.setColor(mColorChannelConfig); + } - double width = (double)( tunerChannel.getBandwidth() ) / - (double)getDisplayBandwidth() * getSize().getWidth(); - - Rectangle2D.Double box = - new Rectangle2D.Double( xAxis - ( width / 2.0d ), - 0.0d, width, getSize().getHeight() - mSpectrumInset ); + TunerChannel tunerChannel = channel.getTunerChannel(); + + if(tunerChannel != null) + { + double xAxis = getAxisFromFrequency(tunerChannel.getFrequency()); + + double width = (double)(tunerChannel.getBandwidth()) / (double)getDisplayBandwidth() * getSize().getWidth(); + + Rectangle2D.Double box = + new Rectangle2D.Double(xAxis - (width / 2.0d), 0.0d, width, getSize().getHeight() - mSpectrumInset); //Fill the box with the correct color - graphics.fill( box ); + graphics.fill(box); - graphics.draw( box ); + graphics.draw(box); //Change to the line color to render the channel name, etc. - graphics.setColor( mColorSpectrumLine ); + graphics.setColor(mColorSpectrumLine); //Draw the labels starting at yAxis position 0 double yAxis = 0; - + //Draw the system label and adjust the y-axis position String system = channel.hasSystem() ? channel.getSystem() : " "; - - yAxis += drawLabel( graphics, - system, - this.getFont(), - xAxis, - yAxis, - width ); + + yAxis += drawLabel(graphics, system, this.getFont(), xAxis, yAxis, width); //Draw the site label and adjust the y-axis position String site = channel.hasSite() ? channel.getSite() : " "; - yAxis += drawLabel( graphics, - site, - this.getFont(), - xAxis, - yAxis, - width ); + yAxis += drawLabel(graphics, site, this.getFont(), xAxis, yAxis, width); //Draw the channel label and adjust the y-axis position - yAxis += drawLabel( graphics, - channel.getName(), - this.getFont(), - xAxis, - yAxis, - width ); - + yAxis += drawLabel(graphics, channel.getName(), this.getFont(), xAxis, yAxis, width); + //Draw the decoder label - drawLabel( graphics, channel.getDecodeConfiguration().getDecoderType().getShortDisplayString(), - this.getFont(), xAxis, yAxis, width ); - - long frequency = tunerChannel.getFrequency(); - - double frequencyAxis = getAxisFromFrequency( frequency ); - - drawChannelCenterLine( graphics, frequencyAxis ); + drawLabel(graphics, channel.getDecodeConfiguration().getDecoderType().getShortDisplayString(), + this.getFont(), xAxis, yAxis, width); + + long frequency = tunerChannel.getFrequency(); + + double frequencyAxis = getAxisFromFrequency(frequency); + + drawChannelCenterLine(graphics, frequencyAxis); /* Draw Automatic Frequency Control line */ int correction = channel.getChannelFrequencyCorrection(); - - if( correction != 0 ) + + if(correction != 0) { - long error = frequency + correction; - - drawAFC( graphics, frequencyAxis, getAxisFromFrequency( error ) ); + long error = frequency + correction; + + drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error)); } } - } - } + } + } } - - + + /** * Draws a textual label at the x/y position, clipping the end of the text * to fit within the maxwidth value. - * + * * @return height of the drawn label */ - private double drawLabel( Graphics2D graphics, String text, Font font, - double x, double baseY, double maxWidth ) + private double drawLabel(Graphics2D graphics, String text, Font font, double x, double baseY, double maxWidth) { - FontMetrics fontMetrics = graphics.getFontMetrics( font ); - - Rectangle2D label = fontMetrics.getStringBounds( text, graphics ); - + FontMetrics fontMetrics = graphics.getFontMetrics(font); + + Rectangle2D label = fontMetrics.getStringBounds(text, graphics); + double offset = label.getWidth() / 2.0d; double y = baseY + label.getHeight(); - + /** * If the label is wider than the max width, left justify the text and * clip the end of it */ - if( offset > ( maxWidth / 2.0d ) ) + if(offset > (maxWidth / 2.0d)) { - label.setRect( x - ( maxWidth / 2.0d ), - y - label.getHeight(), - maxWidth, - label.getHeight() ); - - graphics.setClip( label ); + label.setRect(x - (maxWidth / 2.0d), y - label.getHeight(), maxWidth, label.getHeight()); - graphics.drawString( text, - (float)( x - ( maxWidth / 2.0d ) ), (float)y ); - - graphics.setClip( null ); + graphics.setClip(label); + + graphics.drawString(text, (float)(x - (maxWidth / 2.0d)), (float)y); + + graphics.setClip(null); } else { - graphics.drawString( text, (float)( x - offset ), (float)y ); + graphics.drawString(text, (float)(x - offset), (float)y); } - + return label.getHeight(); } /** * Frequency change event handler */ - @Override - public void frequencyChanged( FrequencyChangeEvent event ) - { - switch( event.getEvent() ) - { - case NOTIFICATION_SAMPLE_RATE_CHANGE: - mBandwidth = event.getValue().intValue(); - mLabelSizeMonitor.update(); - break; - case NOTIFICATION_FREQUENCY_CHANGE: - mFrequency = event.getValue().longValue(); - mLabelSizeMonitor.update(); - break; - default: - break; - } - - /** - * Reset the visible channels list - */ - mVisibleChannels.clear(); - mVisibleChannels.addAll( - mChannelModel.getChannelsInFrequencyRange( getMinFrequency(), - getMaxFrequency() ) ); - } - - /** - * Channel change event handler - */ @Override - @SuppressWarnings( "incomplete-switch" ) - public void channelChanged( ChannelEvent event ) - { - Channel channel = event.getChannel(); - - switch( event.getEvent() ) - { - case NOTIFICATION_ADD: - case NOTIFICATION_PROCESSING_START: - if( !mVisibleChannels.contains( channel ) && - channel.isWithin( getMinFrequency(), getMaxFrequency() ) ) - { - mVisibleChannels.add( channel ); - } - break; - case NOTIFICATION_DELETE: - mVisibleChannels.remove( channel ); - break; - case NOTIFICATION_PROCESSING_STOP: - if( channel.getChannelType() == ChannelType.TRAFFIC ) - { - mVisibleChannels.remove( channel ); - } - break; - case NOTIFICATION_CONFIGURATION_CHANGE: - if( mVisibleChannels.contains( channel ) && - !channel.isWithin( getMinFrequency(), getMaxFrequency() ) ) - { - mVisibleChannels.remove( channel ); - } - - if( !mVisibleChannels.contains( channel ) && - channel.isWithin( getMinFrequency(), getMaxFrequency() ) ) - { - mVisibleChannels.add( channel ); - } - break; - default: - break; - } - - repaint(); - } - - public int getBandwidth() + public void frequencyChanged(FrequencyChangeEvent event) { - return mBandwidth; - } - - /** - * Currently displayed minimum frequency - */ - public long getMinFrequency() - { - return mFrequency - ( mBandwidth / 2 ); - } - - /** - * Currently displayed maximum frequency - */ - public long getMaxFrequency() - { - return mFrequency + ( mBandwidth / 2 ); - } - - public boolean containsFrequency( long frequency ) - { - return Math.abs( mFrequency - frequency ) <= ( mBandwidth / 2 ); - } - - private long getMinDisplayFrequency() - { - double bandwidthPerBin = (double)mBandwidth / (double)( mDFTSize.getSize() ); - - return getMinFrequency() + (int)( ( mDFTZoomWindowOffset ) * bandwidthPerBin ); - } - - private long getMaxDisplayFrequency() - { - return getMinDisplayFrequency() + getDisplayBandwidth(); - } - - private int getDisplayBandwidth() - { - if( mZoom != 0 ) - { - return mBandwidth / (int)Math.pow( 2.0, mZoom ); - } - - return mBandwidth; - } - - /** - * Returns a list of channel configs that contain the frequency within their - * min/max frequency settings. - */ - public ArrayList getChannelsAtFrequency( long frequency ) - { - ArrayList configs = new ArrayList(); - - for( Channel config: mVisibleChannels ) - { - TunerChannel channel = config.getTunerChannel(); - - if( channel != null && - channel.getMinFrequency() <= frequency && - channel.getMaxFrequency() >= frequency ) - { - configs.add( config ); - } - } - - return configs; - } - - @Override - public void settingDeleted( Setting setting ) { /* not implemented */ } - - /** - * Calculates correct spacing and format for frequency labels and major/minor - * tick lines based on current frequency, bandwidth, zoom and screen size. - */ - public class LabelSizeManager implements ComponentListener - { - private static final double LABEL_FILL_THRESHOLD = 0.5d; - - private DecimalFormat mFrequencyFormat = new DecimalFormat( "0.0" ); - - private boolean mUpdateRequired = true; - private int mLabelIncrement = 1; - private int mMajorTickIncrement = 1; - private int mMinorTickIncrement = 1; - - public String format( long frequency ) - { - return mFrequencyFormat.format( (double)frequency / 1E6D ); - } - - private void setPrecision( int precision ) - { - if( precision < 1 ) - { - precision = 1; - } - - if( precision > 5 ) - { - precision = 5; - } - - mFrequencyFormat.setMinimumFractionDigits( precision ); - mFrequencyFormat.setMaximumFractionDigits( precision ); - } - - private void update( Graphics2D graphics ) - { - if( mUpdateRequired ) - { - //Set maximum precision as a starting point - setPrecision( 5 ); - - FontMetrics fontMetrics = - graphics.getFontMetrics( OverlayPanel.this.getFont() ); - - int maxLabelWidth = fontMetrics.stringWidth( format( getMaxDisplayFrequency() ) ); - - double maxLabels = ( (double)OverlayPanel.this.getWidth() * - LABEL_FILL_THRESHOLD ) / - (double)maxLabelWidth; - - //Calculate the next smallest base 10 value for the major increment - int power = (int)Math.log10( (double)getDisplayBandwidth() / maxLabels ); - - //Set the number of decimal places to display in frequency labels - int precision = 5 - power; - - int start = (int)Math.pow( 10.0, power + 1 ); - - int minimum = (int)Math.pow( 10.0, power ); - - int labelIncrement = start; - - while( ( (double)getDisplayBandwidth() / - (double)labelIncrement ) < maxLabels && - labelIncrement >= minimum ) - { - labelIncrement /= 2; - precision++; - } - - if( labelIncrement == minimum ) - { - precision = 5 - power; - } - - setPrecision( precision ); - - mLabelIncrement = labelIncrement; - mMajorTickIncrement = labelIncrement / 2; - mMinorTickIncrement = labelIncrement / 10; - - mUpdateRequired = false; - } - } - - /** - * Forces the display to update the label and frequency display - * calculations - */ - public void update() - { - mUpdateRequired = true; - } - - public int getMajorTickIncrement( Graphics2D graphics ) - { - //Check to see if a calculation update is scheduled - update( graphics ); - - return mMajorTickIncrement; - } - - public int getMinorTickIncrement( Graphics2D graphics ) - { - return mMinorTickIncrement; - } - - public int getLabelIncrement( Graphics2D graphics ) - { - return mLabelIncrement; - } - - @Override - public void componentResized( ComponentEvent arg0 ) + switch(event.getEvent()) { - update(); + case NOTIFICATION_SAMPLE_RATE_CHANGE: + mBandwidth = event.getValue().intValue(); + mLabelSizeMonitor.update(); + break; + case NOTIFICATION_FREQUENCY_CHANGE: + mFrequency = event.getValue().longValue(); + mLabelSizeMonitor.update(); + break; + default: + break; } - public void componentHidden( ComponentEvent arg0 ) {} - public void componentMoved( ComponentEvent arg0 ) {} - public void componentShown( ComponentEvent arg0 ) {} + /** + * Reset the visible channels list + */ + mVisibleChannels.clear(); + mVisibleChannels.addAll(mChannelModel.getChannelsInFrequencyRange(getMinFrequency(), getMaxFrequency())); + } - } + /** + * Channel change event handler + */ + @Override + @SuppressWarnings("incomplete-switch") + public void channelChanged(ChannelEvent event) + { + Channel channel = event.getChannel(); - public enum ChannelDisplay - { - ALL, ENABLED, NONE; - } + switch(event.getEvent()) + { + case NOTIFICATION_ADD: + case NOTIFICATION_PROCESSING_START: + if(!mVisibleChannels.contains(channel) && channel.isWithin(getMinFrequency(), getMaxFrequency())) + { + mVisibleChannels.add(channel); + } + break; + case NOTIFICATION_DELETE: + mVisibleChannels.remove(channel); + break; + case NOTIFICATION_PROCESSING_STOP: + if(channel.getChannelType() == ChannelType.TRAFFIC) + { + mVisibleChannels.remove(channel); + } + break; + case NOTIFICATION_CONFIGURATION_CHANGE: + if(mVisibleChannels.contains(channel) && !channel.isWithin(getMinFrequency(), getMaxFrequency())) + { + mVisibleChannels.remove(channel); + } + + if(!mVisibleChannels.contains(channel) && channel.isWithin(getMinFrequency(), getMaxFrequency())) + { + mVisibleChannels.add(channel); + } + break; + default: + break; + } + + repaint(); + } + + public int getBandwidth() + { + return mBandwidth; + } + + /** + * Currently displayed minimum frequency + */ + public long getMinFrequency() + { + return mFrequency - (mBandwidth / 2); + } + + /** + * Currently displayed maximum frequency + */ + public long getMaxFrequency() + { + return mFrequency + (mBandwidth / 2); + } + + public boolean containsFrequency(long frequency) + { + return Math.abs(mFrequency - frequency) <= (mBandwidth / 2); + } + + private long getMinDisplayFrequency() + { + double bandwidthPerBin = (double)mBandwidth / (double)(mDFTSize.getSize()); + + return getMinFrequency() + (int)((mDFTZoomWindowOffset) * bandwidthPerBin); + } + + private long getMaxDisplayFrequency() + { + return getMinDisplayFrequency() + getDisplayBandwidth(); + } + + private int getDisplayBandwidth() + { + if(mZoom != 0) + { + return mBandwidth / (int)Math.pow(2.0, mZoom); + } + + return mBandwidth; + } + + /** + * Returns a list of channel configs that contain the frequency within their + * min/max frequency settings. + */ + public ArrayList getChannelsAtFrequency(long frequency) + { + ArrayList configs = new ArrayList(); + + for(Channel config : mVisibleChannels) + { + TunerChannel channel = config.getTunerChannel(); + + if(channel != null && channel.getMinFrequency() <= frequency && channel.getMaxFrequency() >= frequency) + { + configs.add(config); + } + } + + return configs; + } + + @Override + public void settingDeleted(Setting setting) + { /* not implemented */ } + + /** + * Calculates correct spacing and format for frequency labels and major/minor + * tick lines based on current frequency, bandwidth, zoom and screen size. + */ + public class LabelSizeManager implements ComponentListener + { + private static final double LABEL_FILL_THRESHOLD = 0.5d; + + private DecimalFormat mFrequencyFormat = new DecimalFormat("0.0"); + + private boolean mUpdateRequired = true; + private int mLabelIncrement = 1; + private int mMajorTickIncrement = 1; + private int mMinorTickIncrement = 1; + + public String format(long frequency) + { + return mFrequencyFormat.format((double)frequency / 1E6D); + } + + private void setPrecision(int precision) + { + if(precision < 1) + { + precision = 1; + } + + if(precision > 5) + { + precision = 5; + } + + mFrequencyFormat.setMinimumFractionDigits(precision); + mFrequencyFormat.setMaximumFractionDigits(precision); + } + + private void update(Graphics2D graphics) + { + if(mUpdateRequired) + { + //Set maximum precision as a starting point + setPrecision(5); + + FontMetrics fontMetrics = + graphics.getFontMetrics(OverlayPanel.this.getFont()); + + int maxLabelWidth = fontMetrics.stringWidth(format(getMaxDisplayFrequency())); + + double maxLabels = ((double)OverlayPanel.this.getWidth() * LABEL_FILL_THRESHOLD) / (double)maxLabelWidth; + + //Calculate the next smallest base 10 value for the major increment + int power = (int)Math.log10((double)getDisplayBandwidth() / maxLabels); + + //Set the number of decimal places to display in frequency labels + int precision = 5 - power; + + int start = (int)Math.pow(10.0, power + 1); + + int minimum = (int)Math.pow(10.0, power); + + int labelIncrement = start; + + while(((double)getDisplayBandwidth() / (double)labelIncrement) < maxLabels && labelIncrement >= minimum) + { + labelIncrement /= 2; + precision++; + } + + if(labelIncrement == minimum) + { + precision = 5 - power; + } + + setPrecision(precision); + + mLabelIncrement = labelIncrement; + mMajorTickIncrement = labelIncrement / 2; + mMinorTickIncrement = labelIncrement / 10; + + mUpdateRequired = false; + } + } + + /** + * Forces the display to update the label and frequency display + * calculations + */ + public void update() + { + mUpdateRequired = true; + } + + public int getMajorTickIncrement(Graphics2D graphics) + { + //Check to see if a calculation update is scheduled + update(graphics); + + return mMajorTickIncrement; + } + + public int getMinorTickIncrement(Graphics2D graphics) + { + return mMinorTickIncrement; + } + + public int getLabelIncrement(Graphics2D graphics) + { + return mLabelIncrement; + } + + @Override + public void componentResized(ComponentEvent arg0) + { + update(); + } + + public void componentHidden(ComponentEvent arg0) + { + } + + public void componentMoved(ComponentEvent arg0) + { + } + + public void componentShown(ComponentEvent arg0) + { + } + + } + + public enum ChannelDisplay + { + ALL, ENABLED, NONE; + } }