/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
/*
* The org.opensourcephysics.media.core package defines the Open Source Physics
* media framework for working with video and other media.
*
* Copyright (c) 2014 Douglas Brown and Wolfgang Christian.
*
* This 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 2 of the License, or
* (at your option) any later version.
*
* This software 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; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* For additional information and documentation on Open Source Physics,
* please see <http://www.opensourcephysics.org/>.
*/
package org.opensourcephysics.media.core;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.TreeMap;
import java.util.Hashtable;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MouseInputAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.tools.FontSizer;
import org.opensourcephysics.tools.ResourceLoader;
/**
* This is a GUI component for playing a VideoClip.
* It uses a subclass of ClipControl to control the clip and updates
* its display based on PropertyChangeEvents it receives from the
* ClipControl.
*
* @author Douglas Brown
* @version 1.0
*/
public class VideoPlayer extends JComponent implements PropertyChangeListener {
// static fields
protected static Icon inOutIcon, playIcon, grayPlayIcon, pauseIcon;
protected static Icon resetIcon, loopIcon, noloopIcon, videoClipIcon;
protected static Icon stepIcon, grayStepIcon, backIcon, grayBackIcon;
private static GoToDialog goToDialog;
private static NumberFormat timeFormat = NumberFormat.getNumberInstance();
static {
String path = "/org/opensourcephysics/resources/media/images/in_out.gif"; //$NON-NLS-1$
inOutIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/play.gif"; //$NON-NLS-1$
playIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/play_gray.gif"; //$NON-NLS-1$
grayPlayIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/pause.gif"; //$NON-NLS-1$
pauseIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/reset.gif"; //$NON-NLS-1$
resetIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/looping_on.gif"; //$NON-NLS-1$
loopIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/looping_off.gif"; //$NON-NLS-1$
noloopIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/video_clip.gif"; //$NON-NLS-1$
videoClipIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/step.gif"; //$NON-NLS-1$
stepIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/step_gray.gif"; //$NON-NLS-1$
grayStepIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/back.gif"; //$NON-NLS-1$
backIcon = ResourceLoader.getIcon(path);
path = "/org/opensourcephysics/resources/media/images/back_gray.gif"; //$NON-NLS-1$
grayBackIcon = ResourceLoader.getIcon(path);
}
// instance fields
protected VideoPanel vidPanel;
protected ClipControl clipControl;
private String[] readoutTypes;
private String readoutType;
private boolean inspectorButtonVisible = true;
protected int height = 54;
// GUI elements
private JToolBar toolbar;
protected JButton readout;
private JButton playButton, resetButton;
private JSpinner rateSpinner;
private JButton stepButton;
private JButton stepSizeButton;
private JButton backButton;
private JButton loopButton;
private JButton inspectorButton;
private JSlider slider;
private Hashtable<Integer, JLabel> sliderLabels;
private JLabel inLabel, outLabel;
private ActionListener readoutListener, timeSetListener, goToListener;
private String active;
private boolean disabled = false;
/**
* Constructs a VideoPlayer to play the specified video clip.
*
* @param panel the video panel
* @param clip the video clip
*/
public VideoPlayer(VideoPanel panel, VideoClip clip) {
this(panel);
setVideoClip(clip);
}
/**
* Constructs a VideoPlayer.
*
* @param panel the video panel
*/
public VideoPlayer(VideoPanel panel) {
vidPanel = panel;
vidPanel.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
if(vidPanel.isPlayerVisible()) {
setBounds();
vidPanel.repaint();
}
}
});
timeFormat.setMinimumIntegerDigits(1);
timeFormat.setMaximumFractionDigits(3);
timeFormat.setMinimumFractionDigits(3);
createGUI();
clipControl = ClipControl.getControl(new VideoClip(null));
clipControl.addPropertyChangeListener(this);
getVideoClip().addPropertyChangeListener(this);
updatePlayButtons(false);
updateSlider();
setReadoutTypes("frame time step", "frame"); //$NON-NLS-1$ //$NON-NLS-2$
refresh();
}
/**
* Sets the video clip.
*
* @param clip the video clip
*/
public void setVideoClip(VideoClip clip) {
boolean playing = clipControl.isPlaying();
stop();
if(getVideoClip()==clip) {
// save current control state
boolean looping = clipControl.isLooping();
double rate = clipControl.getRate();
double duration = clipControl.getMeanFrameDuration();
// replace clip control
clipControl.removePropertyChangeListener(this);
clipControl.dispose();
clipControl = ClipControl.getControl(clip);
clipControl.addPropertyChangeListener(this);
// set state of new control
clipControl.setLooping(looping);
clipControl.setRate(rate);
clipControl.setFrameDuration(duration);
if(playing) {
clipControl.play();
}
ClipInspector inspector = getVideoClip().inspector;
if(inspector!=null) {
inspector.clipControl = clipControl;
}
}
else {
// clean up and replace old clip
VideoClip oldClip = getVideoClip();
oldClip.removePropertyChangeListener(this);
oldClip.hideClipInspector();
// dispose of old video, if any
Video video = oldClip.getVideo();
if(video!=null) {
video.dispose();
}
oldClip.video = null;
if(clip==null) {
clip = new VideoClip(null);
}
clip.addPropertyChangeListener(this);
// clean up and replace old clip control
clipControl.removePropertyChangeListener(this);
clipControl.dispose();
clipControl = ClipControl.getControl(clip);
clipControl.addPropertyChangeListener(this);
// update display
setReadoutTypes("frame time step", clip.readoutType); //$NON-NLS-1$
updatePlayButtons(clipControl.isPlaying());
updateLoopButton(clipControl.isLooping());
updateReadout();
updateSlider();
firePropertyChange("videoclip", oldClip, clip); //$NON-NLS-1$
System.gc();
}
}
/**
* Gets the video clip.
*
* @return the video clip
*/
public VideoClip getVideoClip() {
return clipControl.getVideoClip();
}
/**
* Gets the current clip control.
*
* @return the clip control
*/
public ClipControl getClipControl() {
return clipControl;
}
/**
* Sets the readout data types made available to the user.
*
* @param types a list of data types. Supported types are "time", "step", "frame".
* @param typeToSelect the initially selected type
*/
public void setReadoutTypes(String types, String typeToSelect) {
// put supported types into map sorted by list order
TreeMap<Integer, String> map = new TreeMap<Integer, String>();
String list = types.toLowerCase();
int i = list.indexOf("time"); //$NON-NLS-1$
if(i>=0) {
map.put(new Integer(i), "time"); //$NON-NLS-1$
}
i = list.indexOf("step"); //$NON-NLS-1$
if(i>=0) {
map.put(new Integer(i), "step"); //$NON-NLS-1$
}
i = list.indexOf("frame"); //$NON-NLS-1$
if(i>=0) {
map.put(new Integer(i), "frame"); //$NON-NLS-1$
}
if(map.isEmpty()) {
return;
}
readoutTypes = map.values().toArray(new String[0]);
if (typeToSelect==null)
typeToSelect = readoutTypes[0];
setReadoutType(typeToSelect);
}
/**
* Sets the type of data displayed in the readout.
*
* @param type "time", "step", or "frame"
*/
public void setReadoutType(String type) {
String name = type.toLowerCase();
String tip = " "+MediaRes.getString("VideoPlayer.Readout.ToolTip"); //$NON-NLS-1$ //$NON-NLS-2$
if(name.indexOf("time")>=0) { //$NON-NLS-1$
readoutType = "time"; //$NON-NLS-1$
readout.setToolTipText(MediaRes.getString("VideoPlayer.Readout.ToolTip.Time")+tip); //$NON-NLS-1$
} else if(name.indexOf("step")>=0) { //$NON-NLS-1$
readoutType = "step"; //$NON-NLS-1$
readout.setToolTipText(MediaRes.getString("VideoPlayer.Readout.ToolTip.Step")+tip); //$NON-NLS-1$
} else if(name.indexOf("frame")>=0) { //$NON-NLS-1$
readoutType = "frame"; //$NON-NLS-1$
readout.setToolTipText(MediaRes.getString("VideoPlayer.Readout.ToolTip.Frame")+tip); //$NON-NLS-1$
}
// add type to readoutTypes if not already present
boolean isListed = false;
for(int i = 0; i<readoutTypes.length; i++) {
isListed = isListed||(readoutTypes[i].equals(readoutType));
}
if(!isListed) {
String[] newList = new String[readoutTypes.length+1];
newList[0] = readoutType;
for(int i = 0; i<readoutTypes.length; i++) {
newList[i+1] = readoutTypes[i];
}
readoutTypes = newList;
}
getVideoClip().readoutType = readoutType;
updateReadout();
}
/**
* Plays the clip.
*/
public void play() {
clipControl.play();
}
/**
* Stops at the next step.
*/
public void stop() {
clipControl.stop();
}
/**
* Steps forward one step.
*/
public void step() {
stop();
clipControl.step();
}
/**
* Steps back one step.
*/
public void back() {
stop();
clipControl.back();
}
/**
* Sets the play rate.
*
* @param rate the desired rate
*/
public void setRate(double rate) {
clipControl.setRate(rate);
}
/**
* Gets the play rate.
*
* @return the current rate
*/
public double getRate() {
return clipControl.getRate();
}
/**
* Turns on/off looping.
*
* @param looping <code>true</code> to turn looping on
*/
public void setLooping(boolean looping) {
clipControl.setLooping(looping);
}
/**
* Gets the looping status.
*
* @return <code>true</code> if looping is on
*/
public boolean isLooping() {
return clipControl.isLooping();
}
/**
* Sets the step number.
*
* @param n the desired step number
*/
public void setStepNumber(int n) {
clipControl.setStepNumber(n);
}
/**
* Gets the step number.
*
* @return the current step number
*/
public int getStepNumber() {
return clipControl.getStepNumber();
}
/**
* Gets the current frame number.
*
* @return the frame number
*/
public int getFrameNumber() {
return clipControl.getFrameNumber();
}
/**
* Gets the current time in milliseconds. Includes the start time defined by
* the video clip.
*
* @return the current time
*/
public double getTime() {
return clipControl.getTime()+clipControl.clip.getStartTime();
}
/**
* Gets the start time of the specified step in milliseconds.
* Includes the start time defined by the video clip.
*
* @param stepNumber the step number
* @return the time
*/
public double getStepTime(int stepNumber) {
if (stepNumber<0 || stepNumber>=clipControl.clip.getStepCount())
return Double.NaN;
return clipControl.getStepTime(stepNumber)+clipControl.clip.getStartTime();
}
/**
* Gets the start time of the specified frame in milliseconds.
* Includes the start time defined by the video clip.
*
* @param frameNumber the frame number
* @return the time
*/
public double getFrameTime(int frameNumber) {
return clipControl.clip.getStartTime()
+(frameNumber-clipControl.clip.getStartFrameNumber())
*clipControl.getMeanFrameDuration();
}
/**
* Gets the mean step duration in milliseconds for the current video clip.
*
* @return the mean step duration
*/
public double getMeanStepDuration() {
double duration = getClipControl().getMeanFrameDuration()*getVideoClip().getStepSize();
return duration;
}
/**
* Shows or hides the inspector button. The inspector button shows
* and hides the clip inspector.
*
* @param visible <code>true</code> to show the inspector button
*/
public void setInspectorButtonVisible(final boolean visible) {
if(visible==inspectorButtonVisible) {
return;
}
Runnable runner = new Runnable() {
public void run() {
inspectorButtonVisible = visible;
if(visible) {
toolbar.add(inspectorButton);
} else {
toolbar.remove(inspectorButton);
}
toolbar.revalidate();
}
};
EventQueue.invokeLater(runner);
}
/**
* Shows or hides the looping button.
*
* @param visible <code>true</code> to show the looping button
*/
public void setLoopingButtonVisible(final boolean visible) {
Runnable runner = new Runnable() {
public void run() {
if(visible) {
toolbar.add(loopButton);
} else {
toolbar.remove(loopButton);
}
toolbar.revalidate();
}
};
EventQueue.invokeLater(runner);
}
/**
* Responds to property change events. VideoPlayer listens for the
* following events: "playing", "stepnumber". "frameduration" and "looping"
* from ClipControl, and "startframe", "stepsize", "stepcount" and "starttime"
* from VideoClip.
*
* @param e the property change event
*/
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if(name.equals("stepnumber")) { // from ClipControl //$NON-NLS-1$
updateReadout();
updatePlayButtons(clipControl.isPlaying());
firePropertyChange("stepnumber", null, e.getNewValue()); // to VideoPanel //$NON-NLS-1$
} else if(name.equals("frameduration")) { // from ClipControl //$NON-NLS-1$
updateReadout();
firePropertyChange("frameduration", null, e.getNewValue()); // to VideoPanel //$NON-NLS-1$
} else if(name.equals("playing")) { // from ClipControl //$NON-NLS-1$
boolean playing = ((Boolean) e.getNewValue()).booleanValue();
updatePlayButtons(playing);
firePropertyChange("playing", null, e.getNewValue()); // to VideoPanel //$NON-NLS-1$
} else if(name.equals("looping")) { // from ClipControl //$NON-NLS-1$
boolean looping = ((Boolean) e.getNewValue()).booleanValue();
updateLoopButton(looping);
} else if(name.equals("rate")) { // from ClipControl //$NON-NLS-1$
rateSpinner.setValue(new Double(getRate()));
} else if(name.equals("stepcount")) { // from VideoClip //$NON-NLS-1$
updatePlayButtons(clipControl.isPlaying());
updateReadout();
updateSlider();
} else if(name.equals("framecount")) { // from VideoClip //$NON-NLS-1$
updateSlider();
} else if(name.equals("stepsize")) { // from VideoClip //$NON-NLS-1$
updateReadout();
updateSlider();
} else if(name.equals("startframe")) { // from VideoClip //$NON-NLS-1$
updateReadout();
updateSlider();
} else if(name.equals("starttime")) { // from VideoClip //$NON-NLS-1$
updateReadout();
}
}
/**
* Refreshes the GUI.
*/
public void refresh() {
stepButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.StepForward.ToolTip")); //$NON-NLS-1$
backButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.StepBack.ToolTip")); //$NON-NLS-1$
resetButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.Reset.ToolTip")); //$NON-NLS-1$
inspectorButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.ClipSettings.ToolTip")); //$NON-NLS-1$
loopButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.Looping.ToolTip")); //$NON-NLS-1$
setReadoutType(readoutType);
updatePlayButtons(clipControl.isPlaying());
updateLoopButton(clipControl.isLooping());
if(getVideoClip().inspector!=null) {
getVideoClip().inspector.refresh();
}
}
public void setLocale(Locale locale) {
timeFormat = NumberFormat.getNumberInstance(locale);
}
/**
* Enables and disables this component.
*
* @param enabled true to enable
*/
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
disabled = !enabled;
}
//_________________ private methods and inner classes __________________
/**
* Sets the bounds of this player.
*/
private void setBounds() {
toolbar.revalidate();
height = playButton.getPreferredSize().height+8;
int y = vidPanel.getHeight()-height;
int w = vidPanel.getWidth();
setBounds(0, y, w, height);
toolbar.revalidate();
}
/**
* Creates the visible components of this player.
*/
private void createGUI() {
setLayout(new BorderLayout());
// create toolbar
toolbar = new JToolBar();
toolbar.setFloatable(false);
add(toolbar, BorderLayout.SOUTH);
setBorder(BorderFactory.createEtchedBorder());
playButton = new PlayerButton(playIcon, pauseIcon);
playButton.setDisabledIcon(grayPlayIcon);
playButton.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (disabled || !playButton.isEnabled()) return;
if(playButton.isSelected()) {
stop();
}
else {
play();
}
}
});
playButton.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (disabled || !playButton.isEnabled()) return;
if(e.getKeyCode()==KeyEvent.VK_SPACE) {
if(playButton.isSelected()) {
stop();
}
else {
play();
}
}
}
});
// resetButton
resetButton = new PlayerButton(resetIcon);
resetButton.setPressedIcon(resetIcon);
resetButton.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (disabled) return;
stop();
clipControl.setStepNumber(0);
updatePlayButtons(false);
}
});
// create rate spinner
final double minRate=0.01, maxRate=10;
final SpinnerNumberModel model = new SpinnerNumberModel(1, minRate, maxRate, 0.1);
rateSpinner = new JSpinner(model) {
// override size methods so has same height as buttons
public Dimension getPreferredSize() {
return getMinimumSize();
}
public Dimension getMinimumSize() {
Dimension dim = super.getMinimumSize();
dim.height = Math.max(playButton.getPreferredSize().height, dim.height);
dim.width = 5*getFont().getSize()-10*FontSizer.getLevel();
return dim;
}
public Dimension getMaximumSize() {
return getMinimumSize();
}
};
final JSpinner.NumberEditor editor = new JSpinner.NumberEditor(rateSpinner, "0%"); //$NON-NLS-1$
editor.getTextField().setHorizontalAlignment(SwingConstants.LEFT);
editor.getTextField().setFont(new Font("Dialog", Font.PLAIN, 12)); //$NON-NLS-1$
rateSpinner.setEditor(editor);
rateSpinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
Double rate = (Double)rateSpinner.getValue();
setRate(rate);
model.setStepSize(rate>=2? 0.5: rate>=0.2? 0.1: 0.01);
}
});
editor.getTextField().addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode()==KeyEvent.VK_ENTER) {
double prev = ((Double)rateSpinner.getValue()).doubleValue();
try {
// remove trailing %, if any
String s = editor.getTextField().getText();
if (s.endsWith("%")) s = s.substring(0, s.length()-1); //$NON-NLS-1$
int i = Integer.parseInt(s);
double rate = Math.max(i/100.0, minRate);
rate = Math.min(rate, maxRate);
if (rate!=prev)
rateSpinner.setValue(new Double(rate));
else {
int r = (int)(prev*100);
editor.getTextField().setText(String.valueOf(r)+"%"); //$NON-NLS-1$
}
} catch (NumberFormatException ex) {
int r = (int)(prev*100);
editor.getTextField().setText(String.valueOf(r)+"%"); //$NON-NLS-1$
}
editor.getTextField().selectAll();
}
}
});
// create step button
stepButton = new PlayerButton(stepIcon);
stepButton.setDisabledIcon(grayStepIcon);
stepButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
if ((e.getModifiers() & ActionEvent.SHIFT_MASK)==1) {
stop();
setStepNumber(getStepNumber()+5);
}
else step();
}
});
// create back button
backButton = new PlayerButton(backIcon);
backButton.setDisabledIcon(grayBackIcon);
backButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
if ((e.getModifiers() & ActionEvent.SHIFT_MASK)==1) {
stop();
setStepNumber(getStepNumber()-5);
}
else back();
}
});
// create mouse listener and add to step and back buttons
MouseListener stepListener = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (disabled) return;
if(e.getSource()==stepButton) {
firePropertyChange("stepbutton", null, new Boolean(true)); //$NON-NLS-1$
} else {
firePropertyChange("backbutton", null, new Boolean(true)); //$NON-NLS-1$
}
}
public void mouseExited(MouseEvent e) {
if (disabled) return;
if(e.getSource()==stepButton) {
firePropertyChange("stepbutton", null, new Boolean(false)); //$NON-NLS-1$
} else {
firePropertyChange("backbutton", null, new Boolean(false)); //$NON-NLS-1$
}
}
};
stepButton.addMouseListener(stepListener);
backButton.addMouseListener(stepListener);
// create listeners
// inner popup menu listener classes
readoutListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
setReadoutType(e.getActionCommand());
}
};
goToListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
showGoToDialog();
}
};
timeSetListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
VideoClip clip = getVideoClip();
Object response = JOptionPane.showInputDialog(vidPanel,
MediaRes.getString("VideoPlayer.Dialog.SetTime.Message"), //$NON-NLS-1$
MediaRes.getString("VideoPlayer.Dialog.SetTime.Title")+" "+getFrameNumber(), //$NON-NLS-1$ //$NON-NLS-2$
JOptionPane.PLAIN_MESSAGE,
null, null, getTime()/1000);
if (response!=null) {
if (response.equals("")) //$NON-NLS-1$
clip.setStartTime(Double.NaN);
else try {
double t = Double.parseDouble(response.toString());
double t0 = t*1000-clipControl.getTime();
clip.setStartTime(t0);
} catch (NumberFormatException ex) {
}
}
ClipInspector inspector = clip.inspector;
if (inspector!=null && inspector.isVisible()) {
inspector.t0Field.setValue(clip.getStartTime()/1000);
}
}
};
// create slider
slider = new JSlider(0, 0, 0);
slider.setOpaque(false);
slider.setMinorTickSpacing(1);
slider.setSnapToTicks(true);
slider.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
slider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
VideoClip clip = getVideoClip();
int i = slider.getValue(); // frame number
if (i<clip.getStartFrameNumber()) {
slider.setValue(clip.getStartFrameNumber());
}
else if (i>clip.getEndFrameNumber()) {
slider.setValue(clip.getEndFrameNumber());
}
else {
int n = clip.frameToStep(i);
if(n!=getStepNumber() && !disabled) {
setStepNumber(n);
}
else if(!clip.includesFrame(i)) {
slider.setValue(clip.stepToFrame(n));
}
}
}
});
inLabel = new JLabel(inOutIcon);
outLabel = new JLabel(inOutIcon);
sliderLabels = new Hashtable<Integer, JLabel>();
sliderLabels.put(new Integer(0), inLabel);
sliderLabels.put(new Integer(9), outLabel);
slider.setLabelTable(sliderLabels);
slider.setPaintLabels(true);
final MouseListener slideMouseListener = slider.getMouseListeners()[0];
slider.removeMouseListener(slideMouseListener);
final MouseMotionListener slideMouseMotionListener = slider.getMouseMotionListeners()[0];
slider.removeMouseMotionListener(slideMouseMotionListener);
final MouseInputAdapter inOutSetter = new MouseInputAdapter() {
float inset = 0;
int x;
int maxEndFrame;
public void mousePressed(MouseEvent e) {
if (disabled) return;
stop();
maxEndFrame = getVideoClip().getEndFrameNumber();
if (OSPRuntime.isPopupTrigger(e)) {
// inner popup menu listener classes
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
VideoClip clip = getVideoClip();
int val = clipControl.getFrameNumber();
if ("in".equals(e.getActionCommand())) { //$NON-NLS-1$
clip.setStartFrameNumber(val, maxEndFrame);
if (clip.inspector != null && clip.inspector.isVisible()) {
clip.inspector.startField.setValue(clip.getStartFrameNumber());
}
}
else {
clip.setEndFrameNumber(val);
if (clip.inspector != null && clip.inspector.isVisible()) {
clip.inspector.endField.setValue(clip.getEndFrameNumber());
}
}
refresh();
}
};
// create popup menu and add menu items
JPopupMenu popup = new JPopupMenu();
JMenuItem item = new JMenuItem(MediaRes.getString("ClipInspector.Title")+"..."); //$NON-NLS-1$ //$NON-NLS-2$
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
Frame frame = null;
Container c = vidPanel.getTopLevelAncestor();
if(c instanceof Frame) {
frame = (Frame) c;
}
ClipInspector inspector = getVideoClip().getClipInspector(clipControl, frame);
if(inspector.isVisible()) {
return;
}
Point p0 = new Frame().getLocation();
Point loc = inspector.getLocation();
if((loc.x==p0.x)&&(loc.y==p0.y)) {
// center on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width - inspector.getBounds().width) / 2;
int y = (dim.height - inspector.getBounds().height) / 2;
inspector.setLocation(x, y);
}
inspector.initialize();
inspector.setVisible(true);
}
});
popup.add(item);
popup.addSeparator();
final int frameNum = clipControl.getFrameNumber();
if (active==null || active.equals("in")) { //$NON-NLS-1$
String s = MediaRes.getString("VideoPlayer.Slider.Popup.Menu.SetIn"); //$NON-NLS-1$
s += " ("+frameNum+")"; //$NON-NLS-1$ //$NON-NLS-2$
item = new JMenuItem(s);
item.setActionCommand("in"); //$NON-NLS-1$
item.addActionListener(listener);
popup.add(item);
}
if (active==null || active.equals("out")) { //$NON-NLS-1$
String s = MediaRes.getString("VideoPlayer.Slider.Popup.Menu.SetOut"); //$NON-NLS-1$
s += " ("+frameNum+")"; //$NON-NLS-1$ //$NON-NLS-2$
item = new JMenuItem(s);
item.setActionCommand("out"); //$NON-NLS-1$
item.addActionListener(listener);
popup.add(item);
}
active = null;
boolean includeTimeItems = false;
for(String type: readoutTypes) {
if (type.equals("time")) //$NON-NLS-1$
includeTimeItems = true;
}
if (includeTimeItems) {
// set frame time to zero
popup.addSeparator();
if (getTime()!=0) {
String s = MediaRes.getString("VideoPlayer.Popup.Menu.SetTimeToZero"); //$NON-NLS-1$
item = new JMenuItem(s);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
double t0 = -clipControl.getTime();
getVideoClip().setStartTime(t0);
}
});
item.addActionListener(readoutListener);
popup.add(item);
}
// set frame time
item = new JMenuItem(MediaRes.getString("VideoPlayer.Readout.Menu.SetTime")); //$NON-NLS-1$
item.setActionCommand("time"); //$NON-NLS-1$
item.addActionListener(timeSetListener);
item.addActionListener(readoutListener);
popup.add(item);
}
// show popup menu
popup.show(slider, e.getX(), e.getY());
}
else if (active==null) {
slideMouseListener.mousePressed(e);
}
else {
stop();
x = e.getX();
if (active=="in") { //$NON-NLS-1$
int start = getVideoClip().getStartFrameNumber();
vidPanel.setMessage(MediaRes.getString("VideoPlayer.InMarker.ToolTip")+": "+start); //$NON-NLS-1$ //$NON-NLS-2$
}
else if (active=="out") { //$NON-NLS-1$
int end = getVideoClip().getEndFrameNumber();
vidPanel.setMessage(MediaRes.getString("VideoPlayer.OutMarker.ToolTip")+": "+end); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
public void mouseReleased(MouseEvent e) {
if (disabled) return;
VideoClip clip = getVideoClip();
if (active==null)
slideMouseListener.mouseReleased(e);
else {
clip.trimFrameCount();
vidPanel.setMessage(null);
}
clip.setAdjusting(false);
}
public void mouseExited(MouseEvent e) {
vidPanel.setMouseCursor(Cursor.getDefaultCursor());
if (disabled) return;
slideMouseListener.mouseExited(e);
firePropertyChange("slider", null, new Boolean(false)); //$NON-NLS-1$
}
public void mouseMoved(MouseEvent e) {
active = null;
if (disabled) return;
int yMin = slider.getHeight()-inLabel.getHeight()-2;
if (inset==0)
inset = slider.getInsets().left + 7;
int offset = Math.min(0, getVideoClip().getFrameShift());
if (e.getY()>yMin) {
VideoClip clip = getVideoClip();
double pixPerFrame = (slider.getWidth()-2*inset)/(clip.getFrameCount()-1);
int start = getVideoClip().getStartFrameNumber();
int x = (int)(inset+(start+offset)*pixPerFrame);
String hint = " "+MediaRes.getString("VideoPlayer.InOutMarker.ToolTip"); //$NON-NLS-1$//$NON-NLS-2$
if (e.getX()<x+8 && e.getX()>x-8) {
active = "in"; //$NON-NLS-1$
slider.setToolTipText(
MediaRes.getString("VideoPlayer.InMarker.ToolTip")+": "+start+hint); //$NON-NLS-1$ //$NON-NLS-2$
}
else {
int end = getVideoClip().getEndFrameNumber();
x = (int)(inset+(end+offset)*pixPerFrame);
if (e.getX()<x+8 && e.getX()>x-8) {
active = "out"; //$NON-NLS-1$
slider.setToolTipText(
MediaRes.getString("VideoPlayer.OutMarker.ToolTip")+": "+end+hint); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
if (active==null) {
slideMouseMotionListener.mouseMoved(e);
vidPanel.setMouseCursor(Cursor.getDefaultCursor());
slider.setToolTipText(MediaRes.getString("VideoPlayer.Slider.ToolTip")); //$NON-NLS-1$
}
else {
vidPanel.setMouseCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
public void mouseDragged(MouseEvent e) {
if (disabled) return;
if (active==null) {
slideMouseMotionListener.mouseDragged(e);
return;
}
VideoClip clip = getVideoClip();
clip.setAdjusting(true);
boolean increasing = e.getX()>x;
x = e.getX();
int offset = Math.min(0, getVideoClip().getFrameShift());
int val = Math.round((clip.getFrameCount()-1)*(e.getX()-inset)/(slider.getWidth()-2*inset));
if (increasing)
val = Math.min(val, clip.getFrameCount()-1+getVideoClip().getStepSize());
else
val = Math.min(val, clip.getFrameCount()-1);
val = Math.max(val-offset, 0);
if (active.equals("in")) { //$NON-NLS-1$
int prevStart = clip.getStartFrameNumber();
if (clip.setStartFrameNumber(val, maxEndFrame)) {
int newStart = clip.getStartFrameNumber();
vidPanel.setMessage(MediaRes.getString("VideoPlayer.InMarker.ToolTip")+": "+newStart); //$NON-NLS-1$ //$NON-NLS-2$
// reset start time if needed
if (!clip.isDefaultStartTime) {
double startTime = clip.getStartTime();
startTime += (newStart-prevStart)*clipControl.getMeanFrameDuration();
clip.setStartTime(startTime);
}
clipControl.setStepNumber(0);
if (clip.inspector != null && clip.inspector.isVisible()) {
clip.inspector.startField.setValue(newStart);
clip.inspector.t0Field.setValue(clip.getStartTime()/1000);
}
updateReadout();
}
}
else if (active.equals("out")) { //$NON-NLS-1$
if (clip.setEndFrameNumber(val)) {
int end = clip.getEndFrameNumber();
vidPanel.setMessage(MediaRes.getString("VideoPlayer.OutMarker.ToolTip")+": "+end); //$NON-NLS-1$ //$NON-NLS-2$
clipControl.setStepNumber(clip.getStepCount()-1);
if (clip.inspector != null && clip.inspector.isVisible()) {
clip.inspector.endField.setValue(clip.getEndFrameNumber());
}
}
}
}
};
slider.addMouseListener(inOutSetter);
slider.addMouseMotionListener(inOutSetter);
InputMap im = slider.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = SwingUtilities.getUIActionMap(slider);
am.put(im.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)), null);
am.put(im.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)), null);
slider.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (disabled) return;
switch(e.getKeyCode()) {
case KeyEvent.VK_PAGE_UP :
back();
break;
case KeyEvent.VK_PAGE_DOWN :
step();
break;
}
}
});
// create readout
readout = new PlayerButton() {
// override size methods so has same height as other buttons
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = rateSpinner.getPreferredSize().height;
return dim;
}
public Dimension getMinimumSize() {
Dimension dim = super.getMinimumSize();
dim.height = rateSpinner.getPreferredSize().height;
return dim;
}
public Dimension getMaximumSize() {
Dimension dim = super.getMaximumSize();
dim.height = rateSpinner.getPreferredSize().height;
return dim;
}
};
readout.setForeground(new Color(204, 51, 51));
readout.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
if(readoutTypes.length<2) {
return;
}
// create popup menu and add menu items
JPopupMenu popup = new JPopupMenu();
JMenu displayMenu = new JMenu(MediaRes.getString("VideoPlayer.Readout.Menu.Display")); //$NON-NLS-1$
popup.add(displayMenu);
JMenuItem item;
for(int i = 0; i<readoutTypes.length; i++) {
String type = readoutTypes[i];
if(type.equals("step")) { //$NON-NLS-1$
item = new JCheckBoxMenuItem(MediaRes.getString("VideoPlayer.Readout.MenuItem.Step")); //$NON-NLS-1$
item.setSelected(type.equals(readoutType));
item.setActionCommand(type);
item.addActionListener(readoutListener);
displayMenu.add(item);
} else if(type.equals("time")) { //$NON-NLS-1$
item = new JCheckBoxMenuItem(MediaRes.getString("VideoPlayer.Readout.MenuItem.Time")); //$NON-NLS-1$
item.setSelected(type.equals(readoutType));
item.setActionCommand(type);
item.addActionListener(readoutListener);
displayMenu.add(item);
popup.addSeparator();
if (getTime()!=0) {
String s = MediaRes.getString("VideoPlayer.Popup.Menu.SetTimeToZero"); //$NON-NLS-1$
item = new JMenuItem(s);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
double t0 = -clipControl.getTime();
getVideoClip().setStartTime(t0);
}
});
item.addActionListener(readoutListener);
popup.add(item);
}
item = new JMenuItem(MediaRes.getString("VideoPlayer.Readout.Menu.SetTime")); //$NON-NLS-1$
item.setActionCommand(type);
item.addActionListener(timeSetListener);
item.addActionListener(readoutListener);
popup.add(item);
item = new JMenuItem(MediaRes.getString("VideoPlayer.Readout.Menu.GoTo")+"..."); //$NON-NLS-1$ //$NON-NLS-2$
item.setActionCommand(type);
item.addActionListener(goToListener);
popup.add(item);
} else {
item = new JCheckBoxMenuItem(MediaRes.getString("VideoPlayer.Readout.MenuItem.Frame")); //$NON-NLS-1$
item.setSelected(type.equals(readoutType));
item.setActionCommand(type);
item.addActionListener(readoutListener);
displayMenu.add(item);
}
}
// final VideoClip clip = getVideoClip();
// final Video video = clip.getVideo();
// if (video!=null && video.getFrameCount()>1) {
// item = new JMenuItem(MediaRes.getString("VideoPlayer.Readout.Menu.Renumber")); //$NON-NLS-1$
// item.setActionCommand("frame"); //$NON-NLS-1$
// item.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// final int vidFrame = video.getFrameNumber();
// Object response = JOptionPane.showInputDialog(vidPanel,
// MediaRes.getString("VideoPlayer.Dialog.SetFrameNumber.Message"), //$NON-NLS-1$
// MediaRes.getString("VideoPlayer.Dialog.SetFrameNumber.Title")+" "+vidFrame, //$NON-NLS-1$ //$NON-NLS-2$
// JOptionPane.PLAIN_MESSAGE,
// null, null, getFrameNumber());
// if (response!=null) {
// if (!response.equals("")) try { //$NON-NLS-1$
// int n = Integer.parseInt(response.toString());
// int shift = vidFrame-n;
// int start = clip.getStartFrameNumber();
// int count = clip.getStepCount();
// clip.setFrameShift(shift, start, count);
// updateSlider();
// } catch (NumberFormatException ex) {
// }
// }
// }
//
// });
// item.addActionListener(readoutListener);
// popup.add(item);
// }
// show popup menu
popup.show(readout, 0, readout.getHeight());
}
});
readout.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (OSPRuntime.isPopupTrigger(e))
readout.doClick(0);
}
});
// create stepSize button
stepSizeButton = new PlayerButton() {
// override size methods so has same height as other buttons
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = rateSpinner.getPreferredSize().height;
return dim;
}
public Dimension getMinimumSize() {
Dimension dim = super.getMinimumSize();
dim.height = rateSpinner.getPreferredSize().height;
return dim;
}
public Dimension getMaximumSize() {
Dimension dim = super.getMaximumSize();
dim.height = rateSpinner.getPreferredSize().height;
return dim;
}
};
stepSizeButton.setForeground(new Color(204, 51, 51));
stepSizeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
// inner popup menu listener class
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
int frameNumber = getFrameNumber();
VideoClip clip = getVideoClip();
try {
int n = Integer.parseInt(e.getActionCommand());
clip.setStepSize(n);
} catch (NumberFormatException ex) {
String cur = String.valueOf(getVideoClip().getStepSize());
Object input = JOptionPane.showInputDialog(vidPanel,
MediaRes.getString("VideoPlayer.Dialog.StepSize.Message"), //$NON-NLS-1$
MediaRes.getString("VideoPlayer.Dialog.StepSize.Title"), //$NON-NLS-1$
JOptionPane.PLAIN_MESSAGE,
null, null, cur);
if (input!=null) {
int n = Integer.parseInt(input.toString());
clip.setStepSize(n);
}
}
setStepNumber(clip.frameToStep(frameNumber));
if (clip.inspector != null && clip.inspector.isVisible()) {
clip.inspector.stepSizeField.setValue(clip.getStepSize());
}
}
};
// create popup menu and add menu items
JPopupMenu popup = new JPopupMenu();
for(int i = 1; i<6; i++) {
JMenuItem item = new JMenuItem(String.valueOf(i));
item.addActionListener(listener);
popup.add(item);
}
popup.addSeparator();
JMenuItem item = new JMenuItem(MediaRes.getString("VideoPlayer.Button.StepSize.Other")); //$NON-NLS-1$
item.addActionListener(listener);
popup.add(item);
// show popup menu
popup.show(stepSizeButton, 0, stepSizeButton.getHeight());
}
});
stepSizeButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (OSPRuntime.isPopupTrigger(e))
stepSizeButton.doClick(0);
}
});
// create inspector button
inspectorButton = new PlayerButton(videoClipIcon);
inspectorButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (disabled) return;
Frame frame = null;
Container c = vidPanel.getTopLevelAncestor();
if(c instanceof Frame) {
frame = (Frame) c;
}
ClipInspector inspector = getVideoClip().getClipInspector(clipControl, frame);
if(inspector.isVisible()) {
return;
}
Point p0 = new Frame().getLocation();
Point loc = inspector.getLocation();
if((loc.x==p0.x)&&(loc.y==p0.y)) {
// center on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width - inspector.getBounds().width) / 2;
int y = (dim.height - inspector.getBounds().height) / 2;
inspector.setLocation(x, y);
}
inspector.initialize();
inspector.setVisible(true);
}
});
// create loop button
loopButton = new PlayerButton(noloopIcon, loopIcon);
loopButton.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
setLooping(!loopButton.isSelected());
}
});
loopButton.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_SPACE) {
setLooping(!loopButton.isSelected());
}
}
});
// add components to toolbar
toolbar.add(readout);
toolbar.add(rateSpinner);
toolbar.add(resetButton);
toolbar.add(playButton);
toolbar.add(slider);
toolbar.add(backButton);
toolbar.add(stepSizeButton);
toolbar.add(stepButton);
toolbar.add(loopButton);
if(inspectorButtonVisible) {
toolbar.add(inspectorButton);
}
}
/**
* Updates the play buttons based on the specified play state.
*
* @param playing <code>true</code> if the video is playing
*/
private void updatePlayButtons(final boolean playing) {
Runnable runner = new Runnable() {
public void run() {
int stepCount = getVideoClip().getStepCount();
boolean canPlay = stepCount>1;
playButton.setEnabled(canPlay && (playing || getStepNumber()<stepCount-1));
stepButton.setEnabled(canPlay && (playing || getStepNumber()<stepCount-1));
backButton.setEnabled(canPlay && (playing || getStepNumber()>0));
playButton.setSelected(playing);
if(playing) {
playButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.Pause.ToolTip")); //$NON-NLS-1$
playButton.setPressedIcon(pauseIcon);
playButton.setIcon(pauseIcon);
}
else {
playButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.Play.ToolTip")); //$NON-NLS-1$
playButton.setPressedIcon(playIcon);
playButton.setIcon(playIcon);
}
}
};
if(SwingUtilities.isEventDispatchThread()) runner.run();
else SwingUtilities.invokeLater(runner);
}
/**
* Updates the loop button based on the specified looping state.
*
* @param looping <code>true</code> if the video is looping
*/
private void updateLoopButton(boolean looping) {
if(looping==loopButton.isSelected()) {
return;
}
loopButton.setSelected(looping);
if(looping) {
loopButton.setPressedIcon(loopIcon);
loopButton.setIcon(loopIcon);
} else {
loopButton.setPressedIcon(noloopIcon);
loopButton.setIcon(noloopIcon);
}
}
/**
* Updates the slider and readout based on the current step number.
*/
private void updateReadout() {
// update slider position
int frameNumber = clipControl.getFrameNumber();
int startFrame = getVideoClip().getStartFrameNumber();
int endFrame = getVideoClip().getEndFrameNumber();
if (frameNumber < startFrame)
clipControl.setStepNumber(0);
else if (frameNumber > endFrame)
clipControl.setStepNumber(getVideoClip().getStepCount());
slider.setValue(clipControl.getFrameNumber());
// update readout
int stepNumber = clipControl.getStepNumber();
String display;
if(readoutType.equals("step")) { //$NON-NLS-1$
if(stepNumber<10) {
display = "00"+stepNumber; //$NON-NLS-1$
} else if(stepNumber<100) {
display = "0"+stepNumber; //$NON-NLS-1$
} else {
display = ""+stepNumber; //$NON-NLS-1$
}
} else if(readoutType.equals("frame")) { //$NON-NLS-1$
int n = clipControl.getFrameNumber();
if(n<10) {
display = "00"+n; //$NON-NLS-1$
} else if(n<100) {
display = "0"+n; //$NON-NLS-1$
} else {
display = ""+n; //$NON-NLS-1$
}
} else {
// default readout is time
// set formatting based on mean step duration
if (timeFormat instanceof DecimalFormat) {
DecimalFormat format = (DecimalFormat)timeFormat;
double dur = getMeanStepDuration(); // millisec
if (dur<10) {
format.applyPattern("0.00E0"); //$NON-NLS-1$
}
else if (dur<100) {
format.applyPattern(NumberField.DECIMAL_3_PATTERN);
}
else if (dur<1000) {
format.applyPattern(NumberField.DECIMAL_2_PATTERN);
}
else if (dur<10000) {
format.applyPattern(NumberField.DECIMAL_1_PATTERN);
}
else {
format.applyPattern("0.00E0"); //$NON-NLS-1$
}
}
display = timeFormat.format(getTime()/1000.0);
}
readout.setText(display);
// update rate spinner
rateSpinner.setValue(getRate());
// update stepSizeButton
stepSizeButton.setText(""+getVideoClip().getStepSize()); //$NON-NLS-1$
// set font sizes
FontSizer.setFonts(readout, FontSizer.getLevel());
FontSizer.setFonts(rateSpinner, FontSizer.getLevel());
FontSizer.setFonts(stepSizeButton, FontSizer.getLevel());
// update tooltips
stepSizeButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.StepSize.ToolTip")); //$NON-NLS-1$
rateSpinner.setToolTipText(MediaRes.getString("VideoPlayer.Spinner.Rate.ToolTip")); //$NON-NLS-1$
// if at last step, update play button
if (stepNumber==getVideoClip().getStepCount()-1)
updatePlayButtons(clipControl.isPlaying());
}
/**
* Updates the slider based on the current in and out points.
*/
private void updateSlider() {
// update slider
VideoClip clip = getVideoClip();
slider.setMinimum(Math.max(0, -clip.getFrameShift()));
slider.setMaximum(slider.getMinimum()+clip.getFrameCount()-1);
sliderLabels.clear();
sliderLabels.put(new Integer(clip.getStartFrameNumber()), inLabel);
sliderLabels.put(new Integer(clip.getEndFrameNumber()), outLabel);
slider.repaint();
}
public void showGoToDialog() {
if (goToDialog==null) {
goToDialog = new GoToDialog(this);
// center dialog on videoPanel view
Container c = VideoPlayer.this.getParent();
while (c!=null) {
if (c instanceof JSplitPane) {
Dimension dim = c.getSize();
Point p = c.getLocationOnScreen();
int x = (dim.width - goToDialog.getBounds().width) / 2;
int y = (dim.height - goToDialog.getBounds().height) / 2;
goToDialog.setLocation(p.x+x, p.y+y);
break;
}
c = c.getParent();
}
}
else {
goToDialog.setPlayer(this);
}
goToDialog.setVisible(true);
}
/**
* PlayerButton inner class
*/
protected class PlayerButton extends JButton {
/**
* Constructs a PlayerButton.
*/
public PlayerButton() {
setOpaque(false);
setBorderPainted(false);
addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
setBorderPainted(true);
}
public void mouseExited(MouseEvent e) {
setBorderPainted(false);
}
});
}
/**
* Constructs a PlayerButton with an icon.
*
* @param icon the icon
*/
public PlayerButton(Icon icon) {
this();
setIcon(icon);
}
/**
* Constructs a PlayerButton with icons for selected and unselected states.
*
* @param off the unselected state icon
* @param on the selected state icon
*/
public PlayerButton(Icon off, Icon on) {
this();
setIcon(off);
setSelectedIcon(on);
}
}
/**
* GoToDialog inner class
*/
protected static class GoToDialog extends JDialog {
static HashMap<VideoPlayer, String[]> prev = new HashMap<VideoPlayer, String[]>();
VideoPlayer player;
JButton okButton, cancelButton;
JLabel frameLabel, timeLabel, stepLabel;
JTextField frameField, timeField, stepField;
KeyAdapter keyListener;
FocusAdapter focusListener;
String prevFrame, prevTime, prevStep;
Color error_red = new Color(255, 140, 160);
public GoToDialog(VideoPlayer vidPlayer) {
super(JOptionPane.getFrameForComponent(vidPlayer.vidPanel), true);
setPlayer(vidPlayer);
JPanel contentPane = new JPanel(new BorderLayout());
setContentPane(contentPane);
// create buttons
okButton = new JButton(DisplayRes.getString("GUIUtils.Ok")); //$NON-NLS-1$
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String input = stepField.getText();
if (input!=null && !input.equals("")) try { //$NON-NLS-1$
int n = Integer.parseInt(input);
player.clipControl.setStepNumber(n);
player.refresh();
} catch (NumberFormatException ex) {
}
setVisible(false);
}
});
cancelButton = new JButton(DisplayRes.getString("GUIUtils.Cancel")); //$NON-NLS-1$
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
// create key and focus listeners
keyListener = new KeyAdapter() {
public void keyPressed(KeyEvent e) {
JTextField field = (JTextField)e.getSource();
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
okButton.doClick(0);
}
else {
field.setBackground(Color.white);
}
}
public void keyReleased(KeyEvent e) {
JTextField field = (JTextField)e.getSource();
if(e.getKeyCode()!=KeyEvent.VK_ENTER) {
setValues(field);
}
}
};
focusListener = new FocusAdapter() {
public void focusLost(FocusEvent e) {
JTextField field = (JTextField)e.getSource();
field.setBackground(Color.white);
}
};
// create input fields and labels
frameField = new JTextField(6);
frameField.addKeyListener(keyListener);
frameField.addFocusListener(focusListener);
timeField = new JTextField(6);
timeField.addKeyListener(keyListener);
timeField.addFocusListener(focusListener);
stepField = new JTextField(6);
stepField.addKeyListener(keyListener);
stepField.addFocusListener(focusListener);
frameLabel = new JLabel();
timeLabel = new JLabel();
stepLabel = new JLabel();
// assemble
Box box = Box.createVerticalBox();
JPanel framePanel = new JPanel();
framePanel.add(frameLabel);
framePanel.add(frameField);
box.add(framePanel);
JPanel timePanel = new JPanel();
timePanel.add(timeLabel);
timePanel.add(timeField);
box.add(timePanel);
JPanel stepPanel = new JPanel();
stepPanel.add(stepLabel);
stepPanel.add(stepField);
box.add(stepPanel);
contentPane.add(box, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.add(okButton);
buttonPanel.add(cancelButton);
contentPane.add(buttonPanel, BorderLayout.SOUTH);
refreshGUI();
pack();
}
public void refreshGUI() {
setTitle(MediaRes.getString("VideoPlayer.GoToDialog.Title")); //$NON-NLS-1$
okButton.setText(DisplayRes.getString("GUIUtils.Ok")); //$NON-NLS-1$
cancelButton.setText(DisplayRes.getString("GUIUtils.Cancel")); //$NON-NLS-1$
frameLabel.setText(MediaRes.getString("VideoPlayer.Readout.MenuItem.Frame")+":"); //$NON-NLS-1$ //$NON-NLS-2$
timeLabel.setText(MediaRes.getString("VideoPlayer.Readout.MenuItem.Time")+" (s):"); //$NON-NLS-1$ //$NON-NLS-2$
stepLabel.setText(MediaRes.getString("VideoPlayer.Readout.MenuItem.Step")+":"); //$NON-NLS-1$ //$NON-NLS-2$
// set label sizes
ArrayList<JLabel> labels = new ArrayList<JLabel>();
labels.add(frameLabel);
labels.add(timeLabel);
labels.add(stepLabel);
FontRenderContext frc = new FontRenderContext(null, false, false);
Font font = frameLabel.getFont();
//display panel labels
int w = 0;
for(Iterator<JLabel> it = labels.iterator(); it.hasNext(); ) {
JLabel next = it.next();
Rectangle2D rect = font.getStringBounds(next.getText()+" ", frc); //$NON-NLS-1$
w = Math.max(w, (int) rect.getWidth()+1);
}
Dimension labelSize = new Dimension(w, 20);
for(Iterator<JLabel> it = labels.iterator(); it.hasNext(); ) {
JLabel next = it.next();
next.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
next.setPreferredSize(labelSize);
next.setHorizontalAlignment(SwingConstants.TRAILING);
}
}
public void setPlayer(VideoPlayer vidPlayer) {
if (player!=null && player!=vidPlayer) {
prev.put(player, new String[] {prevFrame, prevTime, prevStep});
String[] former = prev.get(vidPlayer);
if (former!=null) {
prevFrame = former[0];
prevTime = former[1];
prevStep = former[2];
frameField.setText(prevFrame);
timeField.setText(prevTime);
stepField.setText(prevStep);
}
}
player = vidPlayer;
}
private void setValues(JTextField inputField) {
String input = inputField.getText();
if ("".equals(input)) { //$NON-NLS-1$
prevFrame = ""; //$NON-NLS-1$
prevTime = ""; //$NON-NLS-1$
prevStep = ""; //$NON-NLS-1$
}
else {
VideoClip clip = player.getVideoClip();
if (inputField==frameField) {
prevFrame = input;
try {
int frameNum = Integer.parseInt(input);
int entered = frameNum;
frameNum = Math.max(clip.getFirstFrameNumber(), frameNum);
frameNum = Math.min(clip.getEndFrameNumber(), frameNum);
int stepNum = clip.frameToStep(frameNum);
frameNum = clip.stepToFrame(stepNum);
double t = player.getStepTime(stepNum)/1000;
prevTime = timeFormat.format(t);
prevStep = String.valueOf(stepNum);
if (frameNum!=entered) {
frameField.setBackground(error_red);
}
} catch (NumberFormatException ex) {
prevTime = ""; //$NON-NLS-1$
prevStep = ""; //$NON-NLS-1$
frameField.setBackground(error_red);
}
}
else if (inputField==timeField) {
prevTime = input;
try {
input = input.replaceAll(",", "."); //$NON-NLS-1$ //$NON-NLS-2$
double t = Double.valueOf(input)*1000; // millisec
// find step number
double dt = player.getMeanStepDuration();
int n = (int)((t-clip.getStartTime())/dt);
int stepNum = Math.max(0, n);
stepNum = Math.min(stepNum, clip.getStepCount()-1);
int frameNum = clip.stepToFrame(stepNum);
double tmin = player.getFrameTime(clip.getFirstFrameNumber());
double tmax = player.getFrameTime(clip.getLastFrameNumber());
if (t<tmin || t>tmax) {
timeField.setBackground(error_red);
}
prevFrame = String.valueOf(frameNum);
prevStep = String.valueOf(stepNum);
} catch (NumberFormatException ex) {
prevFrame = ""; //$NON-NLS-1$
prevStep = ""; //$NON-NLS-1$
timeField.setBackground(error_red);
}
}
else {
try {
int stepNum = Integer.parseInt(input);
stepNum = Math.max(0, stepNum);
stepNum = Math.min(clip.getStepCount()-1, stepNum);
int frameNum = clip.stepToFrame(stepNum);
double t = player.getStepTime(stepNum)/1000;
prevFrame = String.valueOf(frameNum);
prevTime = timeFormat.format(t);
prevStep = String.valueOf(stepNum);
} catch (NumberFormatException ex) {
}
}
}
frameField.setText(prevFrame);
timeField.setText(prevTime);
stepField.setText(prevStep);
}
public void setVisible(boolean vis) {
if (vis) {
prevFrame = ""; //$NON-NLS-1$
prevTime = ""; //$NON-NLS-1$
prevStep = ""; //$NON-NLS-1$
frameField.setText(prevFrame);
timeField.setText(prevTime);
stepField.setText(prevStep);
frameField.setBackground(Color.white);
timeField.setBackground(Color.white);
stepField.setBackground(Color.white);
FontSizer.setFonts(this, FontSizer.getLevel());
refreshGUI();
pack();
}
super.setVisible(vis);
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software 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; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/