Java tutorial
/** * * Copyright (C) 2015 - Daniel Hams, Modular Audio Limited * daniel.hams@gmail.com * * Mad 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. * * Mad 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 Mad. If not, see <http://www.gnu.org/licenses/>. * */ package uk.co.modularaudio.mads.base.soundfile_player.ui; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Point; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import uk.co.modularaudio.mads.base.soundfile_player.mu.SoundfilePlayerMadDefinition; import uk.co.modularaudio.mads.base.soundfile_player.mu.SoundfilePlayerMadInstance; import uk.co.modularaudio.service.audioanalysis.AnalysedData; import uk.co.modularaudio.service.audioanalysis.AnalysisFillCompletionListener; import uk.co.modularaudio.service.blockresampler.BlockResamplingClient; import uk.co.modularaudio.util.audio.gui.mad.IMadUiControlInstance; import uk.co.modularaudio.util.audio.gui.madswingcontrols.PacPanel; import uk.co.modularaudio.util.audio.mad.ioqueue.ThreadSpecificTemporaryEventStorage; import uk.co.modularaudio.util.audio.mad.timing.MadTimingParameters; public class SoundfilePlayerWaveOverviewUiJComponent extends PacPanel implements IMadUiControlInstance<SoundfilePlayerMadDefinition, SoundfilePlayerMadInstance, SoundfilePlayerMadUiInstance>, SoundfileSampleEventListener, AnalysisFillCompletionListener { private static final long serialVersionUID = -725580571613103896L; private static Log log = LogFactory.getLog(SoundfilePlayerWaveOverviewUiJComponent.class.getName()); private final static int WAVE_OVERVIEW_INTRO_PIXELS = 5; private final static int WAVE_OVERVIEW_BORDER_PIXELS = 3; private final SoundfilePlayerMadUiInstance uiInstance; private final WaveOverviewPositionClickListener waveOverviewPositionClickListener; private long currentSampleNumFrames; private float desiredNormalisedPositionOffset; private int desiredNormalisedPositionPixel; private int displayedNormalisedPositionPixel; private int lastWidth; private int lastHeight; private int lastOverviewWidth; private int lastOverviewHeight; private int internalPercentComplete = -1; private BufferedImage staticThumbnail; public SoundfilePlayerWaveOverviewUiJComponent(final SoundfilePlayerMadDefinition definition, final SoundfilePlayerMadInstance instance, final SoundfilePlayerMadUiInstance uiInstance, final int controlIndex) { this.uiInstance = uiInstance; setBackground(Color.ORANGE); setOpaque(true); uiInstance.addSampleEventListener(this); waveOverviewPositionClickListener = new WaveOverviewPositionClickListener(this); this.addMouseListener(waveOverviewPositionClickListener); uiInstance.addAnalysisFillListener(this); } @Override public boolean needsDisplayProcessing() { return true; } @Override public void doDisplayProcessing(final ThreadSpecificTemporaryEventStorage tempEventStorage, final MadTimingParameters timingParameters, final long currentGuiTime) { if (displayedNormalisedPositionPixel != desiredNormalisedPositionPixel) { repaint(); } } @Override public Component getControl() { return this; } @Override public void destroy() { // Do nothing } @Override public void paintComponent(final Graphics g) { // log.trace("WaveOverview paint() called"); final int xWaveOffset = WAVE_OVERVIEW_BORDER_PIXELS + WAVE_OVERVIEW_INTRO_PIXELS; g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_BACKGROUND_COLOR); g.fillRect(1, 1, lastWidth - 1, lastHeight - 1); g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_BORDER_COLOR); g.drawRect(0, 0, lastWidth, lastHeight); if (staticThumbnail != null) { g.drawImage(staticThumbnail, xWaveOffset, WAVE_OVERVIEW_BORDER_PIXELS, null); } else { g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_WAVE_BG_COLOR); g.fillRect(xWaveOffset, WAVE_OVERVIEW_BORDER_PIXELS, lastOverviewWidth, lastOverviewHeight); if (internalPercentComplete >= 0) { g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_WAVE_FG_COLOR); final int yOffset = WAVE_OVERVIEW_BORDER_PIXELS + (lastOverviewHeight / 2); final int widthOfLine = (lastOverviewWidth * internalPercentComplete) / 100; g.drawLine(xWaveOffset, yOffset, xWaveOffset + widthOfLine, yOffset); } } g.setColor(SoundfilePlayerColorDefines.WAVE_DISPLAY_CURRENT_POSITION_COLOUR); final int actualPos = xWaveOffset + desiredNormalisedPositionPixel; g.drawLine(actualPos, WAVE_OVERVIEW_BORDER_PIXELS, actualPos, lastOverviewHeight); displayedNormalisedPositionPixel = desiredNormalisedPositionPixel; } @Override public void receiveSampleChangeEvent(final BlockResamplingClient newSample) { currentSampleNumFrames = newSample.getTotalNumFrames(); final long position = newSample.getFramePosition(); recomputeDesiredPositionOffset(position); repaint(); } private void recomputeDesiredPositionOffset(final long newPosition) { desiredNormalisedPositionOffset = ((float) newPosition) / currentSampleNumFrames; desiredNormalisedPositionPixel = (int) (desiredNormalisedPositionOffset * lastOverviewWidth); } @Override public void receiveDeltaPositionEvent(final long newPosition) { recomputeDesiredPositionOffset(newPosition); } @Override public void receiveAbsPositionEvent(final long newPosition) { recomputeDesiredPositionOffset(newPosition); } @Override public void setBounds(final int x, final int y, final int width, final int height) { super.setBounds(x, y, width, height); this.lastWidth = width - 1; this.lastHeight = height - 1; this.lastOverviewWidth = lastWidth - (2 * WAVE_OVERVIEW_INTRO_PIXELS) - (2 * WAVE_OVERVIEW_BORDER_PIXELS); this.lastOverviewHeight = lastHeight - (2 * WAVE_OVERVIEW_BORDER_PIXELS); } public void handleOverviewClickAtPoint(final Point point) { final int clickX = point.x; final float normalisedPosition = (clickX - WAVE_OVERVIEW_INTRO_PIXELS - WAVE_OVERVIEW_BORDER_PIXELS) / (float) lastOverviewWidth; desiredNormalisedPositionOffset = normalisedPosition; desiredNormalisedPositionPixel = (int) (desiredNormalisedPositionOffset * lastOverviewWidth); uiInstance.receiveOverviewPositionRequest(normalisedPosition); repaint(); } @Override public void receiveCacheRefreshCompletionEvent() { } @Override public void receiveAnalysedData(final AnalysedData analysedData) { // Pull in the image file just generated as the thumbnail internalPercentComplete = -1; final String pathToThumbnail = analysedData.getPathToStaticThumbnail(); try { staticThumbnail = ImageIO.read(new File(pathToThumbnail)); if (log.isDebugEnabled()) { log.debug("Read in static thumbnail from " + pathToThumbnail); } } catch (final IOException e) { final String msg = "Exception caught loading static thumbnail: " + e.toString(); log.error(msg, e); } repaint(); } @Override public void notifyAnalysisFailure() { // Clear any state where we were waiting for analysis data internalPercentComplete = -1; repaint(); } @Override public void receivePercentageComplete(final int percentageComplete) { // Set the internal variable to non-negative to indicate // we are processing something internalPercentComplete = percentageComplete; repaint(); } @Override public void receiveAnalysisBegin() { internalPercentComplete = -1; staticThumbnail = null; repaint(); } }