// // AnimationWidget.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad.util; /* AWT packages */ import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /* JFC packages */ import javax.swing.*; import javax.swing.event.*; /* RMI classes */ import java.rmi.RemoteException; /* VisAD packages */ import visad.*; /** * A widget that allows users to control aspects of animation (stop/start, * step, animation speed and direction). It is initialized with the state * of the AnimationControl for the ScalarMap used in the constructor. Once this * widget is constructed, it should be used to control animation instead * of using methods in AnimationControl. Once constructed, changes made * using AnimationControl methods will not be reflected in this widget. */ public class AnimationWidget extends JPanel implements ActionListener, ChangeListener, ControlListener, ScalarMapListener { private static final boolean DEBUG = false; private boolean aDir; private boolean aAnim; private int aMs; private JRadioButton forward; private JRadioButton reverse; private JButton onOff; private JButton step; private JTextField ms; private JSlider TimeSlider; private AnimationControl control; /** * construct an AnimationWidget linked to the Control in smap * (which must be to Display.Animation) with auto-detecting ms/frame * * @param smap Display.Animation ScalarMap */ public AnimationWidget(ScalarMap smap) throws VisADException, RemoteException { this(smap, -1); } /** * construct an AnimationWidget linked to the Control in smap * (which must be to Display.Animation) with specified ms/frame * * @param smap Display.Animation ScalarMap * @param st animation speed (ms/frame). If value is negative, * the default speed set in the Control is used. */ public AnimationWidget(ScalarMap smap, int st) throws VisADException, RemoteException { // verify scalar map if (!Display.Animation.equals(smap.getDisplayScalar())) { throw new DisplayException("AnimationWidget: ScalarMap must " + "be to Display.Animation"); } // initialize control settings (these will change later) aAnim = false; aDir = true; aMs = st; // create JPanels JPanel top = new JPanel(); JPanel bottom = new JPanel(); JPanel left = new JPanel(); JPanel right = new JPanel(); // set up layouts setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS)); bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS)); left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS)); right.setLayout(new BoxLayout(right, BoxLayout.Y_AXIS)); // create JComponents forward = new JRadioButton("Forward", aDir); reverse = new JRadioButton("Reverse", !aDir); onOff = new JButton("Stop"); step = new JButton("Step"); ms = new JTextField("????????"); // WLH 2 Dec 98 Dimension msize = ms.getMaximumSize(); Dimension psize = ms.getPreferredSize(); msize.height = psize.height; ms.setMaximumSize(msize); JLabel msLabel = new JLabel("ms/frame"); TimeSlider = new JSlider(1, 1, 1); /* DRM 1999-05-19 */ // set up JComponents Color fore = msLabel.getForeground(); forward.setForeground(fore); reverse.setForeground(fore); onOff.setForeground(fore); step.setForeground(fore); ms.setForeground(fore); TimeSlider.setPaintTicks(true); // make sure onOff button stays the same size to avoid ...'s // use step maximum size since "Step" has same size as "Stop" onOff.setMaximumSize(step.getMaximumSize()); // group JRadioButtons ButtonGroup group = new ButtonGroup(); group.add(forward); group.add(reverse); // align JComponents left.setAlignmentX(JPanel.CENTER_ALIGNMENT); left.setAlignmentY(JPanel.TOP_ALIGNMENT); right.setAlignmentX(JPanel.CENTER_ALIGNMENT); right.setAlignmentY(JPanel.TOP_ALIGNMENT); onOff.setAlignmentX(JButton.CENTER_ALIGNMENT); step.setAlignmentX(JButton.CENTER_ALIGNMENT); ms.setAlignmentY(JTextField.CENTER_ALIGNMENT); msLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); // lay out JComponents left.add(forward); left.add(reverse); right.add(onOff); right.add(step); top.add(left); top.add(right); add(top); bottom.add(ms); bottom.add(msLabel); add(bottom); add(TimeSlider); // get control startup values. getControlSettings((AnimationControl )smap.getControl()); if (st > 0) { aMs = st; if (control != null) control.setStep(aMs); } fixControlUI(); // add listeners if (control != null) control.addControlListener(this); smap.addScalarMapListener(this); forward.addActionListener(this); forward.setActionCommand("forward"); reverse.addActionListener(this); reverse.setActionCommand("reverse"); onOff.addActionListener(this); onOff.setActionCommand("go"); step.addActionListener(this); step.setActionCommand("step"); ms.addActionListener(this); ms.setActionCommand("ms"); TimeSlider.addChangeListener(this); } private void getControlSettings(AnimationControl ctl) { control = ctl; if (control != null) { aDir = control.getDirection(); aAnim = control.getOn(); aMs = (int )control.getStep(); } } private void fixAnimUI() { if (aAnim) { onOff.setText("Stop"); step.setEnabled(false); } else { onOff.setText("Go"); step.setEnabled(true); } } private void fixDirUI() { if (aDir) { forward.setSelected(true); } else { reverse.setSelected(true); } } private void fixSpeedUI() { ms.setText(Integer.toString(aMs)); } private void fixSliderUI() { int max = 1; int cur = 1; Set set = null; if(control != null) { try { set = control.getSet(); if (set != null) { max = set.getLength(); } } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } cur = control.getCurrent() + 1; if (cur < 1) { cur = 1; } else if (cur > max) { cur = max; } } TimeSlider.setMaximum(max); TimeSlider.setMinimum(1); TimeSlider.setValue(cur); int maj; if (max < 20) { maj = max/4; } else if (max < 30) { maj = max/6; } else { maj = max/8; } TimeSlider.setMajorTickSpacing(maj); TimeSlider.setMinorTickSpacing(maj/4); TimeSlider.setPaintLabels(set != null); TimeSlider.repaint(); } private void fixControlUI() { // update Stop/Go buttons fixAnimUI(); // update direction radiobuttons fixDirUI(); // update speed text fixSpeedUI(); // update slider ticks fixSliderUI(); } /** * ActionListener method used with JTextField and JButtons */ public void actionPerformed(ActionEvent e) { // WLH 28 March 2000 if (control == null) { System.out.println("control == null in AnimationWidget.actionPerformed"); return; } String cmd = e.getActionCommand(); if (cmd.equals("forward")) { try { control.setDirection(true); aDir = true; } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } if (cmd.equals("reverse")) { try { control.setDirection(false); aDir = false; } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } if (cmd.equals("ms") || (cmd.equals("go") && !aAnim)) { int fr = -1; try { fr = Integer.parseInt(ms.getText()); } catch (NumberFormatException exc) { } if (fr > 0) { try { control.setStep(fr); aMs = fr; if (aDir) forward.requestFocus(); else reverse.requestFocus(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } fixSpeedUI(); } if (cmd.equals("go")) { try { control.setOn(!aAnim); aAnim = !aAnim; fixAnimUI(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } if (cmd.equals("step")) { try { // slider will adjust automatically with ControlListener control.takeStep(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } } /** * ChangeListener method used with JSlider. */ public void stateChanged(ChangeEvent e) { // CTR 2 June 2000 - There is a bug in JDK 1.2.2+ where // JSlider.getValueIsAdjusting() always seems to return true // on Solaris platforms (but not on Windows platforms). // if (!TimeSlider.getValueIsAdjusting()) try { /* DRM 1999-05-19 */ if (control != null) control.setCurrent(TimeSlider.getValue()-1); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } /** * ControlListener method used for programmatically moving JSlider */ public void controlChanged(ControlEvent e) { if (control != null) { boolean newDir = control.getDirection(); if (newDir != aDir) { aDir = newDir; fixDirUI(); } boolean newAnim = control.getOn(); if (newAnim != aAnim) { aAnim = newAnim; fixAnimUI(); } int newMs = (int )control.getStep(); if (newMs != aMs) { aMs = newMs; fixSpeedUI(); } fixSliderUI(); } } /** * ScalarMapListener method used to recompute JSlider bounds */ public void mapChanged(ScalarMapEvent e) { fixSliderUI(); } /** * ScalarMapListener method used to detect new AnimationControl */ public void controlChanged(ScalarMapControlEvent evt) { int id = evt.getId(); if (id == ScalarMapEvent.CONTROL_REMOVED || id == ScalarMapEvent.CONTROL_REPLACED) { evt.getControl().removeControlListener(this); if (id == ScalarMapEvent.CONTROL_REMOVED) { control = null; } } if (id == ScalarMapEvent.CONTROL_REPLACED || id == ScalarMapEvent.CONTROL_ADDED) { control = (AnimationControl )(evt.getScalarMap().getControl()); getControlSettings(control); fixControlUI(); if (control != null) control.addControlListener(this); } } /** * Work-around for Swing bug where pack() doesn't display slider labels; * actually, it still won't, but window will be the right size */ public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); return new Dimension(d.width, d.height+18); } }