//
// 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);
  }
}

 

Ads help maintain this website.