package org.signalml.plugin.fftsignaltool; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.io.InvalidClassException; import java.util.Calendar; import javax.swing.JLayeredPane; import javax.swing.SwingUtilities; import org.apache.log4j.Logger; import org.signalml.app.util.IconUtils; import org.signalml.math.fft.FourierTransform; import org.signalml.plugin.export.SvarogAccess; import org.signalml.plugin.export.change.events.PluginSignalChangeEvent; import org.signalml.plugin.export.change.listeners.PluginSignalChangeListener; import org.signalml.plugin.export.signal.AbstractSignalTool; import org.signalml.plugin.export.signal.SignalTool; import org.signalml.plugin.export.view.ExportedSignalPlot; import org.signalml.plugin.export.view.ExportedSignalView; /** * {@link SignalTool Signal tool} which displays the {@link SignalFFTPlot plot} * with the {@link FourierTransform#powerSpectrumReal(double[], double) power * spectrum} of the part of the signal near the cursor when the left button of * the mouse is pressed.<p> * The settings how the power spectrum should be displayed are stored in * {@link SignalFFTSettings}. * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe * Sp. z o.o., Marcin Szumski */ public class SignalFFTTool extends AbstractSignalTool implements PluginSignalChangeListener { protected static final Logger logger = Logger .getLogger(SignalFFTPlot.class); /** * How often should FFT be recalculated. This is useful for online signals. */ private static long MILLISECONDS_BETWEEN_FFT_RECALCULATIONS = 1000; /** * the {@link ExportedSignalPlot signal plot} for which this FFT tool is * created */ private ExportedSignalPlot plot; /** * the {@link SignalFFTPlot plot} on which the power spectrum is displayed */ private SignalFFTPlot fftPlot; /** * the {@link SvarogAccess access} to Svarog logic and GUI */ private SvarogAccess svarogAccess; /** * {@link SignalFFTSettings settings} how the power spectrum should be * displayed */ private SignalFFTSettings settings; private double[] powerSpectrum; private double[] frequencies; private SignalFFTPopupAction popupAction; /** * Constructor. Sets the source of messages and the {@link * ExportedSignalView signal view} for which this FFT tool is * created. * @param signalView the signal view */ public SignalFFTTool(SignalFFTPopupAction popupAction, ExportedSignalView signalView) { super(signalView); fftPlot = new SignalFFTPlot(); settings = new SignalFFTSettings(); this.popupAction = popupAction; } /** * Constructor. Sets the source of messages. The {@link * ExportedSignalView signal view} for which this FFT tool is * created must be set separately. */ public SignalFFTTool(SignalFFTPopupAction popupAction) { super(); fftPlot = new SignalFFTPlot(); settings = new SignalFFTSettings(); this.popupAction = popupAction; } @Override public SignalFFTTool createCopy() { SignalFFTTool copy = new SignalFFTTool(popupAction); copy.setSvarogAccess(svarogAccess); copy.setSettings(getSettings()); return copy; } @Override public Cursor getDefaultCursor() { return IconUtils.getCrosshairCursor(); } /** * Displays the {@link SignalFFTPlot plot} with the power spectrum of the * part of the signal near the cursor. */ @Override public void mousePressed(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { Object source = e.getSource(); if (!(source instanceof ExportedSignalPlot)) { plot = null; return; } plot = (ExportedSignalPlot) source; Point point = e.getPoint(); int channel = plot.toChannelSpace(point); fftPlot.setSettings(settings); if (e.isControlDown()) { Dimension plotSize = settings.getPlotSize(); int width = Math.min(800, plotSize.width * 2); int height = Math.min(600, plotSize.height * 2); Dimension size = new Dimension(width, height); fftPlot.setPlotSize(size); fftPlot.setWindowWidth(2 * settings.getWindowWidth()); } fftPlot.setParameters(plot, point, channel); showFFT(point); selectAround(point); setEngaged(true); e.consume(); } } @Override public void mouseReleased(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { popupAction.setPowerAndFrequencies(fftPlot.getPowerSpectrum(), fftPlot.getFrequencies()); hideFFT(); setEngaged(false); plot = null; e.consume(); } } @Override public void mouseDragged(MouseEvent e) { if (plot != null) { if (SwingUtilities.isLeftMouseButton(e)) { Point point = e.getPoint(); Rectangle r = new Rectangle(point.x, point.y, 1, 1); ((ExportedSignalPlot) e.getSource()).scrollRectToVisible(r); if (settings.isChannelSwitching()) { int channel = plot.toChannelSpace(point); fftPlot.setParameters(point, channel); } else { fftPlot.setFocusPoint(point); } positionFFT(point, null); selectAround(point); } } } /** * Returns the {@link SignalFFTSettings settings} how the power spectrum * should be displayed. * @return the settings how the power spectrum should be displayed */ public SignalFFTSettings getSettings() { return settings; } /** * Sets the {@link SignalFFTSettings settings} how the power spectrum should be * displayed. * @param settings the settings how the power spectrum should be displayed */ public void setSettings(SignalFFTSettings settings) { if (settings == null) { throw new NullPointerException("No settings"); } this.settings = settings; } /** * Calculates the position on the screen where the {@link SignalFFTPlot * plot} with the power spectrum should be displayed and displays it at * that place. * @param point the location of the cursor * @param layeredPane the pane on which the fft plot should be displayed */ private void positionFFT(Point point, JLayeredPane layeredPane) { if (plot != null) { if (layeredPane == null) { layeredPane = plot.getRootPane().getLayeredPane(); } Dimension size = fftPlot.getPreferredSize(); int channel = fftPlot.getChannel(); int channelY = plot.channelToPixel(channel); Point location = SwingUtilities.convertPoint((Component) plot, new Point(point.x, channelY), layeredPane); int y; if (location.y > layeredPane.getHeight() / 2) { y = location.y - size.height; } else { y = location.y + plot.getPixelPerChannel(); } fftPlot.setBounds(location.x - (size.width / 2), y, size.width, size.height); } } /** * Displays the {@link SignalFFTPlot fft plot} with the power spectrum of * the part of the signal near cursor ({@code point}) * @param point the location of the cursor */ private void showFFT(Point point) { if (plot != null) { fftPlot.setVisible(true); JLayeredPane layeredPane = plot.getRootPane().getLayeredPane(); positionFFT(point, layeredPane); layeredPane.add(fftPlot, new Integer(JLayeredPane.DRAG_LAYER)); } } /** * Hides the {@link SignalFFTPlot fft plot} (removes it from * {@link ExportedSignalPlot signal plot}). */ private void hideFFT() { if (plot != null) { JLayeredPane layeredPane = plot.getRootPane().getLayeredPane(); layeredPane.remove(fftPlot); plot.getRootPane().repaint(); } } /** * Draws the rectangle around point on the currently selected channel which * is nearest to the specified {@code point}. * If the point is not located on this channel, * @param point the point */ private void selectAround(Point point) { if (plot != null) { Float centerPosition = plot.toTimeSpace(point); if (centerPosition != null) { double offset = (((float) settings.getWindowWidth()) / plot .getSamplingFrequency()) / 2; Float startPosition = new Float(centerPosition.floatValue() - ((float) offset)); Float endPosition = new Float(centerPosition.floatValue() + ((float) offset)); if (startPosition.equals(endPosition)) { getSignalView().clearSignalSelection(); } else { Integer channel = fftPlot.getChannel(); if (channel != null) { try { getSignalView().setSignalSelection( plot, plot.getChannelSelection(startPosition, endPosition, channel)); } catch (InvalidClassException e) { throw new RuntimeException("invalid plot"); } } } } } } /** * Sets the {@link SvarogAccess access} to elements of Svarog in this class * and in the {@link SignalFFTPlot plot}. * @param access the access to elements of Svarog */ public void setSvarogAccess(SvarogAccess access) { svarogAccess = access; svarogAccess.getChangeSupport().addSignalChangeListener(this); fftPlot.setSvarogAccess(access); } @Override public void newSamplesAdded(PluginSignalChangeEvent e) { if (isEngaged()) { Calendar now = Calendar.getInstance(); Calendar lastFFTRecalculationTime = fftPlot.getLastFFTRecalculationTime(); if (lastFFTRecalculationTime == null) return; long differenceInMillis = now.getTimeInMillis() - lastFFTRecalculationTime.getTimeInMillis(); if (differenceInMillis > MILLISECONDS_BETWEEN_FFT_RECALCULATIONS) { fftPlot.recalculateAndRepaint(); } } } }