//
// DisplayImpl.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2002 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;

import java.io.*;
import java.rmi.*;
import java.util.*;

import java.awt.*;
import java.awt.image.*;
import java.awt.print.*;
import java.net.*;

import javax.swing.*;

import visad.browser.Convert;
import visad.browser.Divider;
import visad.util.*;

import visad.collab.ControlMonitorEvent;
import visad.collab.DisplayMonitor;
import visad.collab.DisplayMonitorImpl;
import visad.collab.DisplaySync;
import visad.collab.DisplaySyncImpl;
import visad.collab.MonitorEvent;
import visad.java2d.DisplayImplJ2D;

/**
   DisplayImpl is the abstract VisAD superclass for display
   implementations.  It is runnable.<P>

   DisplayImpl is not Serializable and should not be copied
   between JVMs.<P>
*/
public abstract class DisplayImpl extends ActionImpl implements LocalDisplay {

  /** instance variables */
  /** a Vector of ScalarMap objects;
      does not include ConstantMap objects */
  private Vector MapVector = new Vector();

  /** a Vector of ConstantMap objects */
  private Vector ConstantMapVector = new Vector();

  /** a Vector of RealType (and TextType) objects occuring
      in MapVector */
  private Vector RealTypeVector = new Vector();

  /** a Vector of DisplayRealType objects occuring in MapVector */
  private Vector DisplayRealTypeVector = new Vector();

  /** list of Control objects linked to ScalarMap objects in MapVector;
      the Control objects may be linked to UI widgets, or just computed */
  private Vector ControlVector = new Vector();

  /** ordered list of DataRenderer objects that render Data objects */
  private Vector RendererVector = new Vector();

  /** list of objects interested in learning when DataRenderers
      are deleted from this Display */
  private Vector RendererSourceListeners = new Vector();

  /** list of objects interested in learning when Data objects
      are deleted from this Display */
  private Vector RmtSrcListeners = new Vector();

  /** list of objects interested in receiving messages
      from this Display */
  private Vector MessageListeners = new Vector();

  /** DisplayRenderer object for background and metadata rendering */
  private DisplayRenderer displayRenderer;

  /** Component where data depictions are rendered;
      must be set by concrete subclass constructor;
      may be null for off-screen displays */
  Component component;


  /** set to indicate need to compute ranges of RealType-s
      and sampling for Animation */
  private boolean initialize = true;

  /** set to indicate that ranges should be auto-scaled
      every time data are displayed */
  private boolean always_initialize = false;

  /** set to re-display all linked Data */
  private boolean redisplay_all = false;


  /** length of ValueArray of distinct DisplayRealType values;
      one per Single DisplayRealType that occurs in a ScalarMap,
      plus one per ScalarMap per non-Single DisplayRealType;
      ScalarMap.valueIndex is an index into ValueArray */
  private int valueArrayLength;

  /** mapping from ValueArray to DisplayScalar */
  private int[] valueToScalar;

  /** mapping from ValueArray to MapVector */
  private int[] valueToMap;

  /** Vector of DisplayListeners */
  private final transient Vector ListenerVector = new Vector();

  private Object mapslock = new Object();

  // WLH 16 March 99
  private MouseBehavior mouse = null;

  // objects which monitor and synchronize with remote displays
  private transient DisplayMonitor displayMonitor = null;
  private transient DisplaySync displaySync = null;

  // activity monitor
  private transient DisplayActivity displayActivity = null;

  // Support for printing
  private Printable printer;

  /**
   * construct a DisplayImpl with given name and DisplayRenderer
   * @param name String name for DisplayImpl (used for debugging)
   * @param renderer DisplayRenderer that controls aspects of the
   *                 display not specific to any particular Data
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public DisplayImpl(String name, DisplayRenderer renderer)
         throws VisADException, RemoteException {
    super(name);
    // put system intrinsic DisplayRealType-s in DisplayRealTypeVector
    for (int i=0; i<DisplayRealArray.length; i++) {
      DisplayRealTypeVector.addElement(DisplayRealArray[i]);
    }

    displayMonitor = new DisplayMonitorImpl(this);
    displaySync = new DisplaySyncImpl(this);

    if (renderer != null) {
      displayRenderer = renderer;
    } else {
      displayRenderer = getDefaultDisplayRenderer();
    }
    displayRenderer.setDisplay(this);

    // initialize ScalarMap's, ShadowDisplayReal's and Control's
    clearMaps();
  }

  /** 
   * construct a DisplayImpl collaborating with the given RemoteDisplay,
   * and with the given DisplayRenderer 
   * @param rmtDpy RemoteDisplay to collaborate with
   * @param renderer DisplayRenderer that controls aspects of the 
   *                 display not specific to any particular Data
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public DisplayImpl(RemoteDisplay rmtDpy, DisplayRenderer renderer)
         throws VisADException, RemoteException {
    // this(rmtDpy, renderer, false);


    super(rmtDpy.getName() + ".remote"); // WLH 11 April 2001

    // get class used for remote display
    String className = rmtDpy.getDisplayClassName();
    Class rmtClass;
    try {
      rmtClass = Class.forName(className);
    } catch (ClassNotFoundException cnfe) {
      throw new DisplayException("Cannot find remote display class " +
                                 className);
    }

    // make sure this display class
    // is compatible with the remote display class
    if (!rmtClass.isInstance(this)) {
      throw new DisplayException("Cannot construct " + getClass().getName() +
                                 " from remote " + className);
    }

    // put system intrinsic DisplayRealType-s in DisplayRealTypeVector
    for (int i=0; i<DisplayRealArray.length; i++) {
      DisplayRealTypeVector.addElement(DisplayRealArray[i]);
    }

    displayMonitor = new DisplayMonitorImpl(this);
    displaySync = new DisplaySyncImpl(this);

    if (renderer != null) {
      displayRenderer = renderer;
    } else {
      try {
        String name = rmtDpy.getDisplayRendererClassName();
        Object obj = Class.forName(name).newInstance();
        displayRenderer = (DisplayRenderer )obj;
      } catch (Exception e) {
        displayRenderer = getDefaultDisplayRenderer();
      }
    }
    displayRenderer.setDisplay(this);

    // initialize ScalarMap's, ShadowDisplayReal's and Control's
    clearMaps();
  }

  // suck in any remote ScalarMaps
  void copyScalarMaps(RemoteDisplay rmtDpy)
  {
    Vector m;
    try {
      m = rmtDpy.getMapVector();
    } catch (Exception e) {
      System.err.println("Couldn't copy ScalarMaps");
      return;
    }

    Enumeration me = m.elements();
    while (me.hasMoreElements()) {
      ScalarMap sm = (ScalarMap )me.nextElement();
      try {
        addMap((ScalarMap )sm.clone());
      } catch (DisplayException de) {
        try {
          addMap(new ScalarMap(sm.getScalar(), sm.getDisplayScalar()));
        } catch (Exception e) {
          System.err.println("Couldn't copy remote ScalarMap " + sm);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * copy ConstantMaps from RemoteDisplay to this
   * @param rmtDpy RemoteDisplay to get ConstantMaps from
   */
  void copyConstantMaps(RemoteDisplay rmtDpy)
  {
    Vector c;
    try {
      c = rmtDpy.getConstantMapVector();
    } catch (Exception e) {
      System.err.println("Couldn't copy ConstantMaps");
      return;
    }

    Enumeration ce = c.elements();
    while (ce.hasMoreElements()) {
      ConstantMap cm = (ConstantMap )ce.nextElement();
      try {
        addMap((ConstantMap )cm.clone());
      } catch (DisplayException de) {
        try {
          addMap(new ConstantMap(cm.getConstant(), cm.getDisplayScalar()));
        } catch (Exception e) {
          System.err.println("Couldn't copy remote ConstantMap " + cm);
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * copy GraphicsModeControl settings from RemoteDisplay to this
   * @param rmtDpy RemoteDisplay to get GraphicsModeControl settings from
   */
  void copyGraphicsModeControl(RemoteDisplay rmtDpy)
  {
    GraphicsModeControl rc;
    try {
      getGraphicsModeControl().syncControl(rmtDpy.getGraphicsModeControl());
    } catch (UnmarshalException ue) {
      System.err.println("Couldn't copy remote GraphicsModeControl");
      return;
    } catch (java.rmi.ConnectException ce) {
      System.err.println("Couldn't copy remote GraphicsModeControl");
      return;
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }

  }

  /**
   * copy DataReferences from RemoteDisplay to this
   * @param rmtDpy RemoteDisplay to get DataReferences from
   * @param localRefs array of DataReferences: don't get any
   *                  DataReference from rmtDpy that has the same
   *                  name as a DataReference in localRefs
   */
  private void copyRefLinks(RemoteDisplay rmtDpy, DataReference[] localRefs)
  {
    Vector ml;
    if (rmtDpy == null) return;
    try {
      ml = rmtDpy.getReferenceLinks();
    } catch (UnmarshalException ue) {
      System.err.println("Couldn't copy remote DataReferences");
      return;
    } catch (java.rmi.ConnectException ce) {
      System.err.println("Couldn't copy remote DataReferences");
      return;
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }

    String[] refNames;
    if (localRefs == null) {
      refNames = null;
    } else {
      refNames = new String[localRefs.length];
      for (int i = 0; i < refNames.length; i++) {
        try {
          refNames[i] = localRefs[i].getName();
        } catch (VisADException ve) {
          refNames[i] = null;
        } catch (RemoteException re) {
          refNames[i] = null;
        }
      }
    }

    Enumeration mle = ml.elements();
    if (mle.hasMoreElements()) {

      DataRenderer dr = displayRenderer.makeDefaultRenderer();
      String defaultClass = dr.getClass().getName();

      while (mle.hasMoreElements()) {
        RemoteReferenceLink link = (RemoteReferenceLink )mle.nextElement();

        // get reference to Data object
        RemoteDataReference ref;
        try {
          ref = link.getReference();
        } catch (Exception e) {
          System.err.println("Couldn't copy remote DataReference");
          ref = null;
        }

        if (ref != null && refNames != null) {
          String rName;
          try {
            rName = ref.getName();
          } catch (VisADException ve) {
            System.err.println("Couldn't get DataReference name");
            rName = null;
          } catch (RemoteException re) {
            System.err.println("Couldn't get remote DataReference name");
            rName = null;
          }

          if (rName != null) {
            for (int i = 0; i < refNames.length; i++) {
              if (rName.equals(refNames[i])) {
                ref = null;
                break;
              }
            }
          }
        }

        if (ref != null) {

          // build array of ConstantMap values
          ConstantMap[] cm = null;
          try {
            Vector v = link.getConstantMapVector();
            int len = v.size();
            if (len > 0) {
              cm = new ConstantMap[len];
              for (int i = 0; i < len; i++) {
                cm[i] = (ConstantMap )v.elementAt(i);
              }
            }
          } catch (Exception e) {
            System.err.println("Couldn't copy ConstantMaps" +
                               " for remote DataReference");
          }

          // get proper DataRenderer
          DataRenderer renderer;
          try {
            String newClass = link.getRendererClassName();
            if (newClass.equals(defaultClass)) {
              renderer = null;
            } else {
              Object obj = Class.forName(newClass).newInstance();
              renderer = (DataRenderer )obj;
            }
          } catch (Exception e) {
            System.err.println("Couldn't copy remote DataRenderer name" +
                               "; using " + defaultClass);
            renderer = null;
          }

          // build RemoteDisplayImpl to which reference is attached
          try {
            RemoteDisplayImpl rd = new RemoteDisplayImpl(this);

            // if this reference uses the default renderer...
            if (renderer == null) {
              rd.addReference(ref, cm);
            } else {
              rd.addReferences(renderer, ref, cm);
            }
          } catch (Exception e) {
            System.err.println("Couldn't add remote DataReference " + ref);
          }
        }
      }
    }
  }

  /**
   * copy Data from RemoteDisplay to this
   * @param rmtDpy RemoteDisplay to get Data from
   */
  protected void syncRemoteData(RemoteDisplay rmtDpy)
    throws VisADException, RemoteException
  {
    copyScalarMaps(rmtDpy);
    copyConstantMaps(rmtDpy);
    copyGraphicsModeControl(rmtDpy);
    copyRefLinks(rmtDpy, null);

    notifyAction();

    waitForTasks();

    // only add remote display as listener *after* we've synced
    displayMonitor.addRemoteListener(rmtDpy);
    initializeControls();
  }

  // get current state of all controls from remote display(s)
  private void initializeControls()
  {
    ListIterator iter = ControlVector.listIterator();
    while (iter.hasNext()) {
      try {
        Control ctl = (Control )iter.next();
        ControlMonitorEvent evt;
        evt = new ControlMonitorEvent(MonitorEvent.CONTROL_INIT_REQUESTED,
                                      (Control )ctl.clone());
        displayMonitor.notifyListeners(evt);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /** RemoteDisplayImpl to this for use with remote DisplayListeners */
  private RemoteDisplayImpl rd = null;

  /**
   * Notify this instance's {@link DisplayListener}s.
   *
   * @param  id  type of DisplayEvent that is to be sent
   * @param  x  the horizontal x coordinate for the mouse location in
   *            the display component
   * @param  y  the vertical y coordinate for the mouse location in
   *            the display component
   * @throws VisADException     if a VisAD failure occurs.
   * @throws RemoteException    if a Java RMI failure occurs.
   */
  public void notifyListeners(final int id, final int x, final int y)
         throws VisADException, RemoteException {
//    notifyListeners(new DisplayEvent(this, id, x, y));
    
    synchronized (eventStatus) {
      if (!eventStatus[id]) return;  // ignore disabled events
    }

    Runnable runnable = new Runnable() {
      public void run() {
        try {
          DisplayEvent evt = new DisplayEvent(DisplayImpl.this, id, x, y);  
          for (Enumeration listeners = ((Vector)ListenerVector.clone()).elements(); listeners.hasMoreElements(); ) {
            DisplayListener listener = (DisplayListener) listeners.nextElement();
            if (listener instanceof Remote) {
              if (rd == null) {
                rd = new RemoteDisplayImpl(DisplayImpl.this);
              }
              listener.displayChanged(evt.cloneButDisplay(rd));
            } else {
              listener.displayChanged(evt.cloneButDisplay(DisplayImpl.this));
            }
          }
        } catch (Exception e) {
        }
      }
    };
    if(SwingUtilities.isEventDispatchThread()) {
        runnable.run();
    } else {
        SwingUtilities.invokeLater(runnable);
    }
  }

  /**
   * Notify this instance's {@link DisplayListener}s.
   *
   * @param evt                 The {@link DisplayEvent} to be passed to the
   *                            {@link DisplayListener}s.
   * @throws VisADException     if a VisAD failure occurs.
   * @throws RemoteException    if a Java RMI failure occurs.
   */
  public void notifyListeners(final DisplayEvent evt)
         throws VisADException, RemoteException {

    synchronized (eventStatus) {
      if (!eventStatus[evt.getId()]) return;  // ignore disabled events
    }

    Runnable runnable = new Runnable() {
      public void run() {
        try {
          for (Enumeration listeners = ((Vector)ListenerVector.clone()).elements(); listeners.hasMoreElements(); ) {
            DisplayListener listener = (DisplayListener) listeners.nextElement();
            if (listener instanceof Remote) {
              if (rd == null) {
                rd = new RemoteDisplayImpl(DisplayImpl.this);
              }
              listener.displayChanged(evt.cloneButDisplay(rd));
            } else {
              listener.displayChanged(evt.cloneButDisplay(DisplayImpl.this));
            }
          }
        } catch (Exception e) {
        }
      }
    };
    if(SwingUtilities.isEventDispatchThread()) {
        runnable.run();
    } else {
        SwingUtilities.invokeLater(runnable);
    }
  }

  /**
   * add a DisplayListener
   * @param listener DisplayListener to add
   */
  public void addDisplayListener(DisplayListener listener) {
    ListenerVector.addElement(listener);
  }

  /**
   * remove a DisplayListener
   * @param listener DisplayListener to remove
   */
  public void removeDisplayListener(DisplayListener listener) {
    ListenerVector.removeElement(listener);
  }

  /**
   * @return the java.awt.Component (e.g., JPanel or AppletPanel)
   *         this DisplayImpl uses; returns null for an offscreen
   *         DisplayImpl
   */
  public Component getComponent() {
    return component;
  }

  /**
   * set the java.awt.Component this DisplayImpl uses
   * @param c Component to set
   */
  public void setComponent(Component c) {
    component = c;
  }

  /**
   * request auto-scaling of ScalarMap ranges the next time
   * Data are transformed into scene graph elements
   */
  public void reAutoScale() {
    initialize = true;
// printStack("reAutoScale");
  }

  /**
   * if auto is true, re-apply auto-scaling of ScalarMap ranges
   * every time Display is triggered
   * @param a flag indicating whether to always re-apply auto-scaling
   */
  public void setAlwaysAutoScale(boolean a) {
    always_initialize = a;
  }

  /**
   * request all linked Data to be re-transformed into scene graph
   * elements
   */
  public void reDisplayAll() {
    redisplay_all = true;
// printStack("reDisplayAll");
    notifyAction();
  }


  // CTR - begin code for slaved displays

  /** Internal list of slaves linked to this display. */
  private Vector Slaves = new Vector();

  /**
   * link a slave display to this
   * @param display RemoteSlaveDisplay to link
   */
  public void addSlave(RemoteSlaveDisplay display) {
    if (!Slaves.contains(display)) Slaves.add(display);
  }

  /**
   * remove a link between a slave display and this
   * @param display RemoteSlaveDisplay to remove
   */
  public void removeSlave(RemoteSlaveDisplay display) {
    if (Slaves.contains(display)) Slaves.remove(display);
  }

  /**
   * remove all links to slave displays
   */
  public void removeAllSlaves() {
    Slaves.removeAllElements();
  }

  /**
   * @return flag indicating whether there are any slave displays
   *         linked to this display
   */
  public boolean hasSlaves() {
    return !Slaves.isEmpty();
  }

  /**
   * update all linked slave displays with the given image
   * @param img BufferedImage to send to all linked slave displays
   */
  public void updateSlaves(BufferedImage img) {
    // extract pixels from image
    int width = img.getWidth();
    int height = img.getHeight();
    int type = img.getType();
    int[] pixels = new int[width*height];
    img.getRGB(0, 0, width, height, pixels, 0, width);

    // encode pixels with RLE
    int[] encoded = Convert.encodeRLE(pixels);

    synchronized (Slaves) {
      // send encoded pixels to each slave
      for (int i=0; i<Slaves.size(); i++) {
        RemoteSlaveDisplay d = (RemoteSlaveDisplay) Slaves.elementAt(i);
        try {
          d.sendImage(encoded, width, height, type);
        }
        catch (java.rmi.ConnectException exc) {
          // remote slave client has died; remove it from list
          Slaves.remove(i--);
        }
        catch (RemoteException exc) { }
      }
    }
  }

  /**
   * update all linked slave display with the given message
   * @param message String to send to all linked slave displays
   */
  public void updateSlaves(String message) {
    synchronized (Slaves) {
      // send message to each slave
      for (int i=0; i<Slaves.size(); i++) {
        RemoteSlaveDisplay d = (RemoteSlaveDisplay) Slaves.elementAt(i);
        try {
          d.sendMessage(message);
        }
        catch (java.rmi.ConnectException exc) {
          // remote slave client has died; remove it from list
          Slaves.remove(i--);
        }
        catch (RemoteException exc) { }
      }
    }
  }

  // CTR - end code for slaved displays


  /** Enabled status flag for each DisplayEvent type. */
  private final boolean[] eventStatus = {
    true,  // (not used)
    true,  // MOUSE_PRESSED
    true,  // FRAME_DONE
    true,  // TRANSFORM_DONE
    true,  // MOUSE_PRESSED_LEFT
    true,  // MOUSE_PRESSED_CENTER
    true,  // MOUSE_PRESSED_RIGHT
    true,  // MOUSE_RELEASED
    true,  // MOUSE_RELEASED_LEFT
    true,  // MOUSE_RELEASED_CENTER
    true,  // MOUSE_RELEASED_RIGHT
    true,  // MAP_ADDED
    true,  // MAPS_CLEARED
    true,  // REFERENCE_ADDED
    true,  // REFERENCE_REMOVED
    true,  // DESTROYED
    true,  // KEY_PRESSED
    true,  // KEY_RELEASED
    false, // MOUSE_DRAGGED
    false, // MOUSE_ENTERED
    false, // MOUSE_EXITED
    false, // MOUSE_MOVED
    false, // WAIT_ON
    false, // WAIT_OFF
    true,  // MAP_REMOVED
  };

  /**
   * Enables reporting of a DisplayEvent of a given type
   * when it occurs in this display.
   *
   * @param id DisplayEvent type to enable.  Valid types are:
   *          <UL>
   *          <LI>DisplayEvent.FRAME_DONE
   *          <LI>DisplayEvent.TRANSFORM_DONE
   *          <LI>DisplayEvent.MOUSE_PRESSED
   *          <LI>DisplayEvent.MOUSE_PRESSED_LEFT
   *          <LI>DisplayEvent.MOUSE_PRESSED_CENTER
   *          <LI>DisplayEvent.MOUSE_PRESSED_RIGHT
   *          <LI>DisplayEvent.MOUSE_RELEASED_LEFT
   *          <LI>DisplayEvent.MOUSE_RELEASED_CENTER
   *          <LI>DisplayEvent.MOUSE_RELEASED_RIGHT
   *          <LI>DisplayEvent.MAP_ADDED
   *          <LI>DisplayEvent.MAPS_CLEARED
   *          <LI>DisplayEvent.REFERENCE_ADDED
   *          <LI>DisplayEvent.REFERENCE_REMOVED
   *          <LI>DisplayEvent.DESTROYED
   *          <LI>DisplayEvent.KEY_PRESSED
   *          <LI>DisplayEvent.KEY_RELEASED
   *          <LI>DisplayEvent.MOUSE_DRAGGED
   *          <LI>DisplayEvent.MOUSE_ENTERED
   *          <LI>DisplayEvent.MOUSE_EXITED
   *          <LI>DisplayEvent.MOUSE_MOVED
   *          <LI>DisplayEvent.WAIT_ON
   *          <LI>DisplayEvent.WAIT_OFF
   *          <LI>DisplayEvent.MAP_REMOVED
   *          </UL>
   */
  public void enableEvent(int id) {

    if (id < 1 || id >= eventStatus.length) return;

    synchronized(eventStatus) {
      eventStatus[id] = true;
    }
  }

  /**
   * Disables reporting of a DisplayEvent of a given type
   * when it occurs in this display.
   *
   * @param id DisplayEvent type to disable.  Valid types are:
   *          <UL>
   *          <LI>DisplayEvent.FRAME_DONE
   *          <LI>DisplayEvent.TRANSFORM_DONE
   *          <LI>DisplayEvent.MOUSE_PRESSED
   *          <LI>DisplayEvent.MOUSE_PRESSED_LEFT
   *          <LI>DisplayEvent.MOUSE_PRESSED_CENTER
   *          <LI>DisplayEvent.MOUSE_PRESSED_RIGHT
   *          <LI>DisplayEvent.MOUSE_RELEASED_LEFT
   *          <LI>DisplayEvent.MOUSE_RELEASED_CENTER
   *          <LI>DisplayEvent.MOUSE_RELEASED_RIGHT
   *          <LI>DisplayEvent.MAP_ADDED
   *          <LI>DisplayEvent.MAPS_CLEARED
   *          <LI>DisplayEvent.REFERENCE_ADDED
   *          <LI>DisplayEvent.REFERENCE_REMOVED
   *          <LI>DisplayEvent.DESTROYED
   *          <LI>DisplayEvent.KEY_PRESSED
   *          <LI>DisplayEvent.KEY_RELEASED
   *          <LI>DisplayEvent.MOUSE_DRAGGED
   *          <LI>DisplayEvent.MOUSE_ENTERED
   *          <LI>DisplayEvent.MOUSE_EXITED
   *          <LI>DisplayEvent.MOUSE_MOVED
   *          <LI>DisplayEvent.WAIT_ON
   *          <LI>DisplayEvent.WAIT_OFF
   *          <LI>DisplayEvent.MAP_REMOVED
   *          </UL>
   */
  public void disableEvent(int id) {

    if (id < 1 || id >= eventStatus.length) return;

    synchronized(eventStatus) {
      eventStatus[id] = false;
    }
  }

  /**
   * @param id DisplayEvent type
   * @return flag indicating whether a DisplayEvent of a given
   *         type is eported when it occurs in this display.
   */
  public boolean isEventEnabled(int id) {

    if (id < 1 || id >= eventStatus.length) {
      return false;
    }
    else {
      synchronized(eventStatus) {
	return eventStatus[id];
      }
    }
  }

  /**
   * Link a reference to this Display.
   * This method may only be invoked after all links to
   * {@link visad.ScalarMap ScalarMaps}
   * have been made.
   *
   * @param ref data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   */
  public void addReference(ThingReference ref)
         throws VisADException, RemoteException {
    if (!(ref instanceof DataReference)) {
      throw new ReferenceException("DisplayImpl.addReference: ref " +
                                   "must be DataReference");
    }
    if (displayRenderer == null) return;
    addReference((DataReference) ref, null);
  }

  /**
   * Replace remote reference with local reference.
   *
   * @param rDpy Remote display.
   * @param ref Local reference which will replace the previous
   *            reference.
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see visad.DisplayImpl#addReference(visad.ThingReference)
   */
  public void replaceReference(RemoteDisplay rDpy, ThingReference ref)
    throws VisADException, RemoteException
  {
    if (!(ref instanceof DataReference)) {
      throw new ReferenceException("DisplayImpl.replaceReference: ref " +
                                   "must be DataReference");
    }
    if (displayRenderer == null) return;
    replaceReference(rDpy, (DataReference )ref, null);
  }

  /**
   * Add a link to a DataReference object
   *
   * @param link The link to the DataReference.
   *
   * @exception VisADException If referenced data is null
   *                           or if a link already exists.
   * @exception RemoteException If a link could not be made
   *                            within the remote display.
   */
  void addLink(DataDisplayLink link)
        throws VisADException, RemoteException
  {
    if (displayRenderer == null) return;
    addLink(link, true);
  }

  /**
   * Add a link to a DataReference object
   *
   * @param link The link to the DataReference.
   * @param syncRemote <tt>true</tt> if this is not just
   *                   a local link.
   *
   * @exception VisADException If referenced data is null
   *                           or if a link already exists.
   * @exception RemoteException If a link could not be made
   *                            within the remote display.
   */
  private void addLink(DataDisplayLink link, boolean syncRemote)
        throws VisADException, RemoteException
  {
    if (displayRenderer == null) return;
    super.addLink((ReferenceActionLink )link);
    if (syncRemote) {
      notifyListeners(new DisplayReferenceEvent(this,
                                                DisplayEvent.REFERENCE_ADDED,
                                                link));
    }
  }

  /**
   * Link a reference to this Display.
   * <tt>ref</tt> must be a local
   * {@link visad.DataReferenceImpl DataReferenceImpl}.
   * The {@link visad.ConstantMap ConstantMap} array applies only
   * to the rendering reference.
   *
   * @param ref data reference
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with the data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  public void addReference(DataReference ref,
         ConstantMap[] constant_maps) throws VisADException, RemoteException {
    if (!(ref instanceof DataReferenceImpl)) {
      throw new RemoteVisADException("DisplayImpl.addReference: requires " +
                                     "DataReferenceImpl");
    }
    if (displayRenderer == null) return;
    if (findReference(ref) != null) {
      throw new TypeException("DisplayImpl.addReference: link already exists");
    }
    DataRenderer renderer = displayRenderer.makeDefaultRenderer();
    DataDisplayLink[] links = {new DataDisplayLink(ref, this, this, constant_maps,
                                                   renderer, getLinkId())};
    addLink(links[0]);
    renderer.setLinks(links, this);
    synchronized (mapslock) {
      RendererVector.addElement(renderer);
    }

    initialize |= computeInitialize();

// printStack("addReference");
    notifyAction();
  }

  /**
   * Replace remote reference with local reference.
   *
   * @param rDpy Remote display.
   * @param ref Local reference which will replace the previous
   *            reference.
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with the data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see visad.DisplayImpl#addReference(visad.DataReference, visad.ConstantMap[])
   */
  public void replaceReference(RemoteDisplay rDpy, DataReference ref,
                               ConstantMap[] constant_maps)
    throws VisADException, RemoteException
  {
    if (displayRenderer == null) return;
    replaceReferences(rDpy, null, new DataReference[] {ref},
                      new ConstantMap[][] {constant_maps});
  }

  /** decide whether an autoscale is needed */
  private boolean computeInitialize() {
    boolean init = false;
    for (Iterator iter = ((java.util.List)MapVector.clone()).iterator();
        !init && iter.hasNext();
        init |= ((ScalarMap)iter.next()).doInitialize()) {
    }
    if (!init) {
      AnimationControl control =
        (AnimationControl) getControl(AnimationControl.class);
      if (control != null) {
        init |= (control.getSet() == null);
      }
    }
    return init;
  }

  /**
   * Link a RemoteDataReference to this Display.
   * The {@link visad.ConstantMap ConstantMap} array applies only
   * to the rendering reference.
   * For use by addReference() method of RemoteDisplay that adapts this.
   *
   * @param ref remote data reference
   * @param display RemoteDisplay adapting this
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with the data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  void adaptedAddReference(RemoteDataReference ref, RemoteDisplay display,
       ConstantMap[] constant_maps) throws VisADException, RemoteException {
    if (findReference(ref) != null) {
      throw new TypeException("DisplayImpl.adaptedAddReference: " +
                              "link already exists");
    }
    if (displayRenderer == null) return;
    DataRenderer renderer = displayRenderer.makeDefaultRenderer();
    DataDisplayLink[] links = {new DataDisplayLink(ref, this, display, constant_maps,
                                                   renderer, getLinkId())};
    addLink(links[0]);
    renderer.setLinks(links, this);
    synchronized (mapslock) {
      RendererVector.addElement(renderer);
    }

    initialize |= computeInitialize();

// printStack("adaptedAddReference");
    notifyAction();
  }

  /**
   * Link a reference to this Display using a non-default renderer.
   * <tt>ref</tt> must be a local
   * {@link visad.DataReferenceImpl DataReferenceImpl}.
   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
   * than {@link visad.Display Display}
   *
   * @param renderer logic to render this data
   * @param ref data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  public void addReferences(DataRenderer renderer, DataReference ref)
         throws VisADException, RemoteException {
    addReferences(renderer, new DataReference[] {ref}, null);
  }

  /**
   * Replace remote reference with local reference using
   * non-default renderer.
   *
   * @param rDpy Remote display.
   * @param renderer logic to render this data
   * @param ref Local reference which will replace the previous
   *            reference.
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference)
   */
  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
                                DataReference ref)
    throws VisADException, RemoteException
  {
    replaceReferences(rDpy, renderer, new DataReference[] {ref}, null);
  }

  /**
   * Link a reference to this Display using a non-default renderer.
   * <tt>ref</tt> must be a local
   * {@link visad.DataReferenceImpl DataReferenceImpl}.
   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
   * than {@link visad.Display Display}
   *
   * @param renderer logic to render this data
   * @param ref data reference
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with the data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  public void addReferences(DataRenderer renderer, DataReference ref,
                            ConstantMap[] constant_maps)
         throws VisADException, RemoteException {
    addReferences(renderer, new DataReference[] {ref},
                  new ConstantMap[][] {constant_maps});
  }

  /**
   * Replace remote reference with local reference using
   * non-default renderer.
   *
   * @param rDpy Remote display.
   * @param renderer logic to render this data
   * @param ref Local reference which will replace the previous
   *            reference.
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with the data reference
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data reference to the remote display.
   *
   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference, visad.ConstantMap[])
   */
  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
                                DataReference ref, ConstantMap[] constant_maps)
    throws VisADException, RemoteException
  {
    replaceReferences(rDpy, renderer, new DataReference[] {ref},
                      new ConstantMap[][] {constant_maps});
  }

  /**
   * Link references to this display using a non-default renderer.
   * <tt>refs</tt> must be local
   * {@link visad.DataReferenceImpl DataReferenceImpls}.
   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
   * than {@link visad.Display Display}
   *
   * @param renderer logic to render this data
   * @param refs array of data references
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data references to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  public void addReferences(DataRenderer renderer, DataReference[] refs)
         throws VisADException, RemoteException {
    addReferences(renderer, refs, null);
  }

  /**
   * Replace remote references with local references.
   *
   * @param rDpy Remote display.
   * @param renderer logic to render this data
   * @param refs array of local data references
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data references to the remote display.
   *
   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference[])
   */
  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
                                DataReference[] refs)
    throws VisADException, RemoteException
  {
    replaceReferences(rDpy, renderer, refs, null);
  }

  /**
   * Link references to this display using the non-default renderer.
   * <tt>refs</tt> must be local
   * {@link visad.DataReferenceImpl DataReferenceImpls}.
   * The <tt>maps[i]</tt> array applies only to rendering <tt>refs[i]</tt>.
   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
   * than {@link visad.Display Display}
   *
   * @param renderer logic to render this data
   * @param refs array of data references
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with data references
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data references to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  public void addReferences(DataRenderer renderer, DataReference[] refs,
                            ConstantMap[][] constant_maps)
         throws VisADException, RemoteException {
    addReferences(renderer, refs, constant_maps, true);
  }

  /**
   * Link references to this display using the non-default renderer.
   * <tt>refs</tt> must be local
   * {@link visad.DataReferenceImpl DataReferenceImpls}.
   * The <tt>maps[i]</tt> array applies only to rendering <tt>refs[i]</tt>.
   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
   * than {@link visad.Display Display}
   *
   * @param renderer logic to render this data
   * @param refs array of data references
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with data references.
   * @param syncRemote <tt>true</tt> if this data should be forwarded
   *                   to the remote display.
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters
   * @exception RemoteException if there was a problem adding the
   *                            data references to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  private void addReferences(DataRenderer renderer, DataReference[] refs,
                             ConstantMap[][] constant_maps,
                             boolean syncRemote)
         throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    // N.B. This method is called by all replaceReference() methods
    if (refs.length < 1) {
      throw new DisplayException("DisplayImpl.addReferences: must have at " +
                                 "least one DataReference");
    }
    if (constant_maps != null && refs.length != constant_maps.length) {
      throw new DisplayException("DisplayImpl.addReferences: constant_maps " +
                                 "length must match refs length");
    }
    if (!displayRenderer.legalDataRenderer(renderer)) {
      throw new DisplayException("DisplayImpl.addReferences: illegal " +
                                 "DataRenderer class");
    }
    DataDisplayLink[] links = new DataDisplayLink[refs.length];
    for (int i=0; i< refs.length; i++) {
      if (!(refs[i] instanceof DataReferenceImpl)) {
        throw new RemoteVisADException("DisplayImpl.addReferences: requires " +
                                       "DataReferenceImpl");
      }
      if (findReference(refs[i]) != null) {
        throw new TypeException("DisplayImpl.addReferences: link already exists");
      }
      if (constant_maps == null) {
        links[i] = new DataDisplayLink(refs[i], this, this, null,
                                       renderer, getLinkId());
      }
      else {
        links[i] = new DataDisplayLink(refs[i], this, this, constant_maps[i],
                                       renderer, getLinkId());
      }
      addLink(links[i], syncRemote);
    }
    renderer.setLinks(links, this);
    synchronized (mapslock) {
      RendererVector.addElement(renderer);
    }

    initialize |= computeInitialize();

// printStack("addReferences");
    notifyAction();
  }

  /**
   * Replace remote references with local references.
   *
   * @param rDpy Remote display.
   * @param renderer logic to render this data
   * @param refs array of data references
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with data references.
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters.
   * @exception RemoteException if there was a problem adding the
   *                            data references to the remote display.
   *
   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference[], visad.ConstantMap[][])
   */
  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
                                DataReference[] refs,
                                ConstantMap[][] constant_maps)
    throws VisADException, RemoteException
  {
    if (displayRenderer == null) return;
    if (renderer == null) {
      renderer = displayRenderer.makeDefaultRenderer();
    }

    removeAllReferences();
    addReferences(renderer, refs, constant_maps, false);
    copyRefLinks(rDpy, refs);
  }

  /**
   * Link references to this display using the non-default renderer.
   * <tt>refs</tt> may be a mix of local
   * {@link visad.DataReferenceImpl DataReferenceImpls} and
   * {@link visad.RemoteDataReference RemoteDataReferences}.
   * The <tt>maps[i]</tt> array applies only to rendering <tt>refs[i]</tt>.
   * For use by addReferences() method of RemoteDisplay that adapts this.
   *
   * @param renderer logic to render this data
   * @param refs array of data references
   * @param display RemoteDisplay adapting this
   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
   *                      associated with data references.
   *
   * @exception VisADException if there was a problem with one or more
   *                           parameters
   * @exception RemoteException if there was a problem adding the
   *                            data references to the remote display.
   *
   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
   */
  void adaptedAddReferences(DataRenderer renderer, DataReference[] refs,
       RemoteDisplay display, ConstantMap[][] constant_maps)
       throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    if (refs.length < 1) {
      throw new DisplayException("DisplayImpl.addReferences: must have at " +
                                 "least one DataReference");
    }
    if (constant_maps != null && refs.length != constant_maps.length) {
      throw new DisplayException("DisplayImpl.addReferences: constant_maps " +
                                 "length must match refs length");
    }
    if (!displayRenderer.legalDataRenderer(renderer)) {
      throw new DisplayException("DisplayImpl.addReferences: illegal " +
                                 "DataRenderer class");
    }
    DataDisplayLink[] links = new DataDisplayLink[refs.length];
    for (int i=0; i< refs.length; i++) {
      if (findReference(refs[i]) != null) {
        throw new TypeException("DisplayImpl.addReferences: link already exists");
      }
      if (refs[i] instanceof DataReferenceImpl) {
        // refs[i] is local
        if (constant_maps == null) {
          links[i] = new DataDisplayLink(refs[i], this, this, null,
                                         renderer, getLinkId());
        }
        else {
          links[i] = new DataDisplayLink(refs[i], this, this, constant_maps[i],
                                         renderer, getLinkId());
        }
      }
      else {
        // refs[i] is remote
        if (constant_maps == null) {
          links[i] = new DataDisplayLink(refs[i], this, display, null,
                                         renderer, getLinkId());
        }
        else {
          links[i] = new DataDisplayLink(refs[i], this, display, constant_maps[i],
                                         renderer, getLinkId());
        }
      }
      addLink(links[i]);
    }
    renderer.setLinks(links, this);
    synchronized (mapslock) {
      RendererVector.addElement(renderer);
    }

    initialize |= computeInitialize();

// printStack("adaptedAddReferences");
    notifyAction();
  }

  /**
   * remove link to ref, which must be a local DataReferenceImpl;
   * if ref was added as part of a DataReference  array passed to
   * addReferences(), remove links to all of them
   * @param ref ThingReference to remove
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void removeReference(ThingReference ref)
         throws VisADException, RemoteException {
    if (!(ref instanceof DataReferenceImpl)) {
      throw new RemoteVisADException("ActionImpl.removeReference: requires " +
                                     "DataReferenceImpl");
    }
    adaptedDisplayRemoveReference((DataReference) ref);
  }

  /**
   * remove DataDisplayLinks from this DisplayImpl
   * @param links array of DataDisplayLinks to remove
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  void removeLinks(DataDisplayLink[] links)
    throws RemoteException, VisADException
  {
    if (displayRenderer == null) return;
    for (int i = links.length - 1; i >= 0; i--) {
      if (links[i] != null) {
        links[i].clearMaps();
      }
    }

    super.removeLinks(links);
  }

  /**
   * remove link to a DataReference;
   * uses by removeReference() method of RemoteActionImpl that
   * adapts this ActionImpl;
   * because DataReference array input to adaptedAddReferences
   * may be a mix of local and remote, we tolerate either here
   * @param ref DataReference to remove
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  void adaptedDisplayRemoveReference(DataReference ref)
       throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    DataDisplayLink link = (DataDisplayLink) findReference(ref);
    // don't throw an Exception if link is null: users may try to
    // remove all DataReferences added by a call to addReferences
    if (link == null) return;
    DataRenderer renderer = link.getRenderer();
    DataDisplayLink[] links = renderer.getLinks();
    synchronized (mapslock) {
      renderer.clearAVControls();
      renderer.clearScene();
      RendererVector.removeElement(renderer);
    }
    removeLinks(links);
  }

  /**
   * remove all links to DataReferences
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void removeAllReferences()
         throws VisADException, RemoteException {

    if (displayRenderer == null) return;
    Vector temp = (Vector) RendererVector.clone();

    synchronized (mapslock) {
      Iterator renderers = temp.iterator();
      while (renderers.hasNext()) {
        DataRenderer renderer = (DataRenderer) renderers.next();
        renderer.clearAVControls();
        DataDisplayLink[] links = renderer.getLinks();
        renderers.remove();
        removeLinks(links);
        renderer.clearScene();
      }
      RendererVector.removeAllElements();

      initialize = true;
// printStack("removeAllReferences");
    }
  }

  /**
   * trigger possible re-transform of linked Data
   * used by Controls to notify this DisplayImpl that they
   * have changed
   */
  public void controlChanged() {
    notifyAction();
  }

  /**
   * over-ride ActionImpl.checkTicks() to always return true,
   * since DisplayImpl always runs doAction to find out if any
   * linked Data needs to be re-transformed
   * @return true
   */
  public boolean checkTicks() {
    return true;
  }

  /**
   * destroy this display: clear all references to objects
   * (so they can be garbage collected), stop all Threads
   * and remove all links
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void destroy() throws VisADException, RemoteException {
    VisADException thrownVE = null;
    RemoteException thrownRE = null;

    if (mapslock == null) mapslock = new Object();
    synchronized (mapslock) {
      stop();
  
      if (displayActivity != null) {
        displayActivity.destroy();
      }
  
      // tell everybody we're going away
//      notifyListeners(new DisplayEvent(this, DisplayEvent.DESTROYED));
      notifyListeners(DisplayEvent.DESTROYED, 0, 0);
  
      // remove all listeners
      synchronized (ListenerVector) {
        ListenerVector.removeAllElements();
      }
  
      try {
        removeAllReferences();
      } catch (RemoteException re) {
        thrownRE = re;
      } catch (VisADException ve) {
        thrownVE = ve;
      }
  
      try {
        clearMaps();
      } catch (RemoteException re) {
        thrownRE = re;
      } catch (VisADException ve) {
        thrownVE = ve;
      }
  
      AnimationControl control =
        (AnimationControl) getControl(AnimationControl.class);
      if (control != null) {
        control.stop();
      }
  
      if (thrownVE != null) {
        throw thrownVE;
      }
      if (thrownRE != null) {
        throw thrownRE;
      }
  
  // get rid of dangling references
  /* done in clearMaps()
      verify (RendererVector == null)
      MapVector.removeAllElements();
      ConstantMapVector.removeAllElements();
      RealTypeVector.removeAllElements();
  */
      DisplayRealTypeVector.removeAllElements();
      ControlVector.removeAllElements();
      RendererSourceListeners.removeAllElements();
      RmtSrcListeners.removeAllElements();
      MessageListeners.removeAllElements();
      ListenerVector.removeAllElements();
      Slaves.removeAllElements();
      displayRenderer = null; // this disables most DisplayImpl methods
      component = null;
      mouse = null;
      displayMonitor = null;
      displaySync = null;
      displayActivity = null;
      printer = null;
      rd = null;
      widgetPanel = null;
    } // end synchronized (mapslock)
  }

  /**
   * Check if any Data need re-transform, and if so, do it.
   * Check if auto-scaling is needed for any ScalarMaps, and
   * if so, do it. This method does the real work of DisplayImpl.
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void doAction() throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    if (mapslock == null) return;
    synchronized (mapslock) {
      if (RendererVector == null || displayRenderer == null) {
        return;
      }
      // put a try/finally block around the setWaitFlag(true), so that we unset
      // the flag before exiting even if an Exception or Error is thrown
      try {
// System.out.println("DisplayImpl call setWaitFlag(true)");
        displayRenderer.setWaitFlag(true);
        // set tickFlag-s in changed Control-s
        // clone MapVector to avoid need for synchronized access
        Vector tmap = (Vector) MapVector.clone();
        Enumeration maps = tmap.elements();
        while (maps.hasMoreElements()) {
          ScalarMap map = (ScalarMap) maps.nextElement();
          map.setTicks();
        }

        // set ScalarMap.valueIndex-s and valueArrayLength
        int n = getDisplayScalarCount();
        int[] scalarToValue = new int[n];
        for (int i=0; i<n; i++) scalarToValue[i] = -1;
        valueArrayLength = 0;
        maps = tmap.elements();
        while (maps.hasMoreElements()) {
          ScalarMap map = ((ScalarMap) maps.nextElement());
          DisplayRealType dreal = map.getDisplayScalar();
            map.setValueIndex(valueArrayLength);
            valueArrayLength++;
        }

        // set valueToScalar and valueToMap arrays
        valueToScalar = new int[valueArrayLength];
        valueToMap = new int[valueArrayLength];
        for (int i=0; i<tmap.size(); i++) {
          ScalarMap map = (ScalarMap) tmap.elementAt(i);
          DisplayRealType dreal = map.getDisplayScalar();
          valueToScalar[map.getValueIndex()] = getDisplayScalarIndex(dreal);
          valueToMap[map.getValueIndex()] = i;
        }

        // invoke each DataRenderer (to prepare associated Data objects
        // for transformation)
        // clone RendererVector to avoid need for synchronized access
        Vector temp = ((Vector) RendererVector.clone());
        Enumeration renderers = temp.elements();
        boolean go = false;
        if (initialize) {
          renderers = temp.elements();
          while (!go && renderers.hasMoreElements()) {
            DataRenderer renderer = (DataRenderer) renderers.nextElement();
            go |= renderer.checkAction();
          }
        }
/*
System.out.println("initialize = " + initialize + " go = " + go +
                     " redisplay_all = " + redisplay_all);
*/
        if (redisplay_all) {
          go = true;
// System.out.println("redisplay_all = " + redisplay_all + " go = " + go);
          redisplay_all = false;
        }

        if (!initialize || go) {
          boolean lastinitialize = initialize;
          displayRenderer.prepareAction(temp, tmap, go, initialize);

          // WLH 10 May 2001
          boolean anyBadMap = false;
          maps = tmap.elements();
          while (maps.hasMoreElements()) {
            ScalarMap map = ((ScalarMap) maps.nextElement());
            if (map.badRange()) {
              anyBadMap = true;
              // System.out.println("badRange " + map);
            }
          }

          renderers = temp.elements();
          boolean badScale = false;
          while (renderers.hasMoreElements()) {
            DataRenderer renderer = (DataRenderer) renderers.nextElement();
            boolean badthis = renderer.getBadScale(anyBadMap);
            badScale |= badthis;
/*
            if (badthis) {
              DataDisplayLink[] links = renderer.getLinks();
              System.out.println("badthis " +
                                 links[0].getThingReference().getName());
            }
*/
          }
          initialize = badScale;
          if (always_initialize) initialize = true;

          if (initialize && !lastinitialize) {
            displayRenderer.prepareAction(temp, tmap, go, initialize);
          }

          boolean transform_done = false;

// System.out.println("DisplayImpl.doAction transform");
// int i = 0;
          boolean any_exceptions = false;
          renderers = temp.elements();
          while (renderers.hasMoreElements()) {
// System.out.println("DisplayImpl invoke renderer.doAction " + i);
// i++;
            DataRenderer renderer = (DataRenderer) renderers.nextElement();

            boolean this_transform = renderer.doAction();
            transform_done |= this_transform;
            any_exceptions |= !renderer.getExceptionVector().isEmpty();
/*
            if (this_transform) {
              DataDisplayLink[] links = renderer.getLinks();
              System.out.println("transform " + getName() + " " +
                                 links[0].getThingReference().getName());
            }
*/
          }
          if (transform_done) {
// System.out.println(getName() + " invoked " + i + " renderers");
            AnimationControl control =
              (AnimationControl) getControl(AnimationControl.class);
            if (control != null) {
              control.init();
            }
            synchronized (ControlVector) {
              Enumeration controls = ControlVector.elements();
              while(controls.hasMoreElements()) {
                Control cont = (Control) controls.nextElement();
                if (ValueControl.class.isInstance(cont)) {
                  ((ValueControl) cont).init();
                }
              }
            }
          }

          if (transform_done || any_exceptions) {
            notifyListeners(DisplayEvent.TRANSFORM_DONE, 0, 0);
          }

        }

        // clear tickFlag-s in Control-s
        maps = tmap.elements();
        while(maps.hasMoreElements()) {
          ScalarMap map = (ScalarMap) maps.nextElement();
          map.resetTicks();
        }
      } finally {
// System.out.println("DisplayImpl call setWaitFlag(false)");
        displayRenderer.setWaitFlag(false);
      }
    } // end synchronized (mapslock)
  }

  /**
   * @return the default DisplayRenderer for this DisplayImpl
   */
  protected abstract DisplayRenderer getDefaultDisplayRenderer();

  /**
   * @return the DisplayRenderer associated with this DisplayImpl
   */
  public DisplayRenderer getDisplayRenderer() {
    return displayRenderer;
  }

  /**
   * Returns a clone of the list of DataRenderer-s.  A clone is returned
   * to avoid concurrent access problems by the Display thread.
   * @return			A clone of the list of DataRenderer-s.
   * @see #getRenderers()
   */
  public Vector getRendererVector() {
    return (Vector) RendererVector.clone();
  }

  /**
   * @return the number of DisplayRealTypes in ScalarMaps
   *         linked to this DisplayImpl
   */
  public int getDisplayScalarCount() {
    return DisplayRealTypeVector.size();
  }

  /**
   * get the DisplayRealType with the given index
   * @param index index into Vector of DisplayRealTypes
   * @return the indexed DisplayRealType
   */
  public DisplayRealType getDisplayScalar(int index) {
    return (DisplayRealType) DisplayRealTypeVector.elementAt(index);
  }

  /**
   * get the index for the given DisplayRealType
   * @param dreal DisplayRealType to search for
   * @return the index of dreal in Vector of DisplayRealTypes
   */
  public int getDisplayScalarIndex(DisplayRealType dreal) {
    int dindex;
    synchronized (DisplayRealTypeVector) {
      DisplayTupleType tuple = dreal.getTuple();
      if (tuple != null) {
        int n = tuple.getDimension();
        for (int i=0; i<n; i++) {
          try {
            DisplayRealType ereal =
              (DisplayRealType) tuple.getComponent(i);
            int eindex = DisplayRealTypeVector.indexOf(ereal);
            if (eindex < 0) {
              DisplayRealTypeVector.addElement(ereal);
            }
          }
          catch (VisADException e) {
          }
        }
      }
      dindex = DisplayRealTypeVector.indexOf(dreal);
      if (dindex < 0) {
        DisplayRealTypeVector.addElement(dreal);
        dindex = DisplayRealTypeVector.indexOf(dreal);
      }
    }
    return dindex;
  }

  /**
   * @return the number of ScalarTypes in ScalarMaps
   *         linked to this DisplayImpl
   */
  public int getScalarCount() {
    return RealTypeVector.size();
  }

  /**
   * get the ScalarType with the given index
   * @param index index into Vector of ScalarTypes
   * @return the indexed ScalarType
   */
  public ScalarType getScalar(int index) {
    return (ScalarType) RealTypeVector.elementAt(index);
  }

  /**
   * get the index for the given ScalarType
   * @param real ScalarType to search for
   * @return the index of real in Vector of ScalarTypes
   * @throws RemoteException an RMI error occurred
   */
  public int getScalarIndex(ScalarType real) throws RemoteException {
    return RealTypeVector.indexOf(real);
  }

  /**
   * add a ScalarMap to this Display, assuming a local source
   * @param map ScalarMap to add
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void addMap(ScalarMap map)
         throws VisADException, RemoteException {
    addMap(map, VisADEvent.LOCAL_SOURCE);
  }

  /**
   * add a ScalarMap to this Display
   * @param map ScalarMap to add
   * @param remoteId remote source for collab, or VisADEvent.LOCAL_SOURCE
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void addMap(ScalarMap map, int remoteId)
         throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    synchronized (mapslock) {
      int index;
      if (!RendererVector.isEmpty()) {
        ScalarType st = map.getScalar();
        if (st != null) {
          Vector temp = (Vector) RendererVector.clone();
          Iterator renderers = temp.iterator();
          while (renderers.hasNext()) {
            DataRenderer renderer = (DataRenderer) renderers.next();
            DataDisplayLink[] links = renderer.getLinks();
            for (int i=0; i<links.length; i++) {
              if (MathType.findScalarType(links[i].getType(), st)) {
/* WLH relax addMap() & clearMap() 17 Dec 2002
                throw new DisplayException("DisplayImpl.addMap(): " + 
                            "ScalarType may not occur in any DataReference");
*/
                DataReference ref = links[i].getDataReference();
                if (ref != null) ref.incTick();
              }
            }
          }
        }
      }
      DisplayRealType type = map.getDisplayScalar();
      if (!displayRenderer.legalDisplayScalar(type)) {
        throw new BadMappingException("DisplayImpl.addMap: " +
              map.getDisplayScalar() + " illegal for this DisplayRenderer");
      }
      if ((Display.LineWidth.equals(type) ||
           Display.PointSize.equals(type) ||
           Display.LineStyle.equals(type) ||
           Display.TextureEnable.equals(type) ||
           Display.MissingTransparent.equals(type) ||
           Display.PolygonMode.equals(type) ||
           Display.CurvedSize.equals(type) ||
           Display.ColorMode.equals(type)) &&
          !(map instanceof ConstantMap))
      {
        throw new BadMappingException("DisplayImpl.addMap: " +
              map.getDisplayScalar() + " for ConstantMap only");
      }
      map.setDisplay(this);

      if (map instanceof ConstantMap) {
        synchronized (ConstantMapVector) {
          Enumeration maps = ConstantMapVector.elements();
          while(maps.hasMoreElements()) {
            ConstantMap map2 = (ConstantMap) maps.nextElement();
            if (map2.getDisplayScalar().equals(map.getDisplayScalar())) {
              throw new BadMappingException("Display.addMap: two ConstantMaps " +
                                "have the same DisplayScalar");
            }
          }
          ConstantMapVector.addElement(map);
        }
        if (!RendererVector.isEmpty()) {
          reDisplayAll(); // WLH 2 April 2002
        }
      }
      else { // !(map instanceof ConstantMap)
        // add to RealTypeVector and set ScalarIndex
        ScalarType real = map.getScalar();
        DisplayRealType dreal = map.getDisplayScalar();
        synchronized (MapVector) {
          Enumeration maps = MapVector.elements();
          while(maps.hasMoreElements()) {
            ScalarMap map2 = (ScalarMap) maps.nextElement();
            if (real.equals(map2.getScalar()) &&
                dreal.equals(map2.getDisplayScalar()) &&
                !dreal.equals(Display.Shape)) {
              throw new BadMappingException("Display.addMap: two ScalarMaps " +
                                     "with the same RealType & DisplayRealType");
            }
            if (dreal.equals(Display.Animation) &&
                map2.getDisplayScalar().equals(Display.Animation)) {
              throw new BadMappingException("Display.addMap: two RealTypes " +
                                            "are mapped to Animation");
            }
          }
          MapVector.addElement(map);
          needWidgetRefresh = true;
        }
        synchronized (RealTypeVector) {
          index = RealTypeVector.indexOf(real);
          if (index < 0) {
            RealTypeVector.addElement(real);
            index = RealTypeVector.indexOf(real);
          }
        }
        map.setScalarIndex(index);
        map.setControl();
        // WLH 18 June 2002
        if (!RendererVector.isEmpty() && map.doInitialize()) {
          reAutoScale();
        }
      } // end !(map instanceof ConstantMap)
      addDisplayScalar(map);
      notifyListeners(new DisplayMapEvent(this, DisplayEvent.MAP_ADDED, map,
                                          remoteId));

      // make sure we monitor all changes to this ScalarMap
      map.addScalarMapListener(displayMonitor);
    }
  }

  /**
   * remove a ScalarMap from this Display, assuming a local source
   * @param map ScalarMap to remove
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */ 
  public void removeMap(ScalarMap map)
         throws VisADException, RemoteException {
    removeMap(map, VisADEvent.LOCAL_SOURCE);
  }

  /**
   * remove a ScalarMap from this Display
   * @param map ScalarMap to add
   * @param remoteId remote source for collab, or VisADEvent.LOCAL_SOURCE
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */ 
  public void removeMap(ScalarMap map, int remoteId)
         throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    synchronized (mapslock) {
      // can have multiple equals() maps to Shape, so test for ==
      int index = MapVector.indexOf(map);
      while (index >=0 && map != MapVector.elementAt(index)) {
        index = MapVector.indexOf(map, index+1);
      }
      if (index < 0) {
        throw new BadMappingException("Display.removeMap: " + map + " not " +
                                      "in Display " + getName());
      }
      MapVector.removeElementAt(index);
      ScalarType real = map.getScalar();
      if (real != null) {
        Enumeration maps = MapVector.elements();
        boolean any = false;
        while(maps.hasMoreElements()) {
          ScalarMap map2 = (ScalarMap) maps.nextElement();
          if (real.equals(map2.getScalar())) any = true;
        }
        if (!any) {
          // if real is not used by any other ScalarMap, remove it
          // and adjust ScalarIndex of all other ScalarMaps
          RealTypeVector.removeElement(real);
  
          maps = MapVector.elements();
          while(maps.hasMoreElements()) {
            ScalarMap map2 = (ScalarMap) maps.nextElement();
            ScalarType real2 = map2.getScalar();
            int index2 = RealTypeVector.indexOf(real2);
            if (index2 < 0) {
              throw new BadMappingException("Display.removeMap: impossible 1");
            }
            map2.setScalarIndex(index2);
          }
        } // end if (!any)
      } // end if (real != null)

      // trigger events
      if (map instanceof ConstantMap) {
        if (!RendererVector.isEmpty()) {
          reDisplayAll();
        }
      }
      else { // !(map instanceof ConstantMap)
        if (!RendererVector.isEmpty()) {
          ScalarType st = map.getScalar();
          if (st != null) { // not necessary for !(map instanceof ConstantMap)
            Vector temp = (Vector) RendererVector.clone();
            Iterator renderers = temp.iterator();
            while (renderers.hasNext()) {
              DataRenderer renderer = (DataRenderer) renderers.next();
              DataDisplayLink[] links = renderer.getLinks();
              for (int i=0; i<links.length; i++) {
                if (MathType.findScalarType(links[i].getType(), st)) {
                  DataReference ref = links[i].getDataReference();
                  if (ref != null) ref.incTick();
                }
              }
            }
          }
        }
        // add DRM 2003-02-21
        if (map.getAxisScale() != null) {
          DisplayRenderer displayRenderer = getDisplayRenderer();
          displayRenderer.clearScale(map.getAxisScale());

          Enumeration maps = MapVector.elements();
          while(maps.hasMoreElements()) {
            ScalarMap map2 = (ScalarMap) maps.nextElement();
            AxisScale axisScale = map2.getAxisScale();
            if (axisScale != null) {
              displayRenderer.clearScale(axisScale);
              axisScale.setAxisOrdinal(-1);
            }
          }
          maps = MapVector.elements();
          while(maps.hasMoreElements()) {
            ScalarMap map2 = (ScalarMap) maps.nextElement();
            AxisScale axisScale = map2.getAxisScale();
            if (axisScale != null) {
              map2.makeScale();
            }
          }


        }
        needWidgetRefresh = true;
      } // end !(map instanceof ConstantMap)
      notifyListeners(new DisplayMapEvent(this, DisplayEvent.MAP_REMOVED, map,
                                          remoteId));
      map.nullDisplay(); // ??
    } // end synchronized (mapslock)
  }

  /**
   * add a ScalarType from a ScalarMap from this Display
   * @param map ScalarMap whose ScalarType to add
   */
  void addDisplayScalar(ScalarMap map) {
    int index;

    if (displayRenderer == null) return;
    DisplayRealType dreal = map.getDisplayScalar();
    synchronized (DisplayRealTypeVector) {
      DisplayTupleType tuple = dreal.getTuple();
      if (tuple != null) {
        int n = tuple.getDimension();
        for (int i=0; i<n; i++) {
          try {
            DisplayRealType ereal =
              (DisplayRealType) tuple.getComponent(i);
            int eindex = DisplayRealTypeVector.indexOf(ereal);
            if (eindex < 0) {
              DisplayRealTypeVector.addElement(ereal);
            }
          }
          catch (VisADException e) {
          }
        }
      }
      index = DisplayRealTypeVector.indexOf(dreal);
      if (index < 0) {
        DisplayRealTypeVector.addElement(dreal);
        index = DisplayRealTypeVector.indexOf(dreal);
      }
    }
    map.setDisplayScalarIndex(index);
  }

  /**
   * remove all ScalarMaps linked this display;
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  public void clearMaps() throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    synchronized (mapslock) {
      if (!RendererVector.isEmpty()) {
/* WLH relax addMap() & clearMap() 17 Dec 2002
        throw new DisplayException("DisplayImpl.clearMaps: RendererVector " +
                                   "must be empty");
*/
        reDisplayAll();
      }

      Enumeration maps;

      synchronized (MapVector) {
        maps = MapVector.elements();
        while(maps.hasMoreElements()) {
          ScalarMap map = (ScalarMap) maps.nextElement();
          map.nullDisplay();
          map.removeScalarMapListener(displayMonitor);
        }
        MapVector.removeAllElements();
        needWidgetRefresh = true;
      }
      synchronized (ConstantMapVector) {
        maps = ConstantMapVector.elements();
        while(maps.hasMoreElements()) {
          ConstantMap map = (ConstantMap) maps.nextElement();
          map.nullDisplay();
          map.removeScalarMapListener(displayMonitor);
        }
        ConstantMapVector.removeAllElements();
      }

      synchronized (ControlVector) {
        // clear Control-s associated with this Display
        maps = ControlVector.elements();
        while(maps.hasMoreElements()) {
          Control ctl = (Control )maps.nextElement();
          ctl.removeControlListener((ControlListener )displayMonitor);
          ctl.setInstanceNumber(-1);
        }
        ControlVector.removeAllElements();
        // one each GraphicsModeControl and ProjectionControl always exists
        Control control = (Control) getGraphicsModeControl();
        if (control != null) addControl(control);
        control = (Control) getProjectionControl();
        if (control != null) addControl(control);
        // don't forget RendererControl
        control = (Control) displayRenderer.getRendererControl();
        if (control != null) addControl(control);
      }
      // clear RealType-s from RealTypeVector
      // removeAllElements is synchronized
      RealTypeVector.removeAllElements();
      synchronized (DisplayRealTypeVector) {
        // clear DisplayRealType-s from DisplayRealTypeVector
        DisplayRealTypeVector.removeAllElements();
        // put system intrinsic DisplayRealType-s in DisplayRealTypeVector
        for (int i=0; i<DisplayRealArray.length; i++) {
          DisplayRealTypeVector.addElement(DisplayRealArray[i]);
        }
      }
      displayRenderer.clearAxisOrdinals();
      displayRenderer.setAnimationString(new String[] {null, null});
    }

    notifyListeners(new DisplayEvent(this, DisplayEvent.MAPS_CLEARED));
  }

  /**
   * @return clone of Vector of ScalarMaps linked to this DisplayImpl
   *         (doesn't include ConstantMaps)
   */
  public Vector getMapVector() {
    return (Vector) MapVector.clone();
  }

  /**
   * @return clone of Vector of ConstantMaps linked to this DisplayImpl
   */
  public Vector getConstantMapVector() {
    return (Vector) ConstantMapVector.clone();
  }

  /**
   * Get the instance number of this <CODE>Control</CODE>
   * in the internal <CODE>ControlVector</CODE>.
   *
   * @param ctl <CODE>Control</CODE> to look for.
   *
   * @return Instance number (<CODE>-1</CODE> if not found.)
   */
  private int getInstanceNumber(Control ctl)
  {
    Class ctlClass = ctl.getClass();
    int num = 0;
    Enumeration enumeration = ControlVector.elements();
    while (enumeration.hasMoreElements()) {
      Control c = (Control )enumeration.nextElement();
      if (ctlClass.isInstance(c)) {
        if (ctl == c) {
          return num;
        }
        num++;
      }
    }

    return -1;
  }

  /**
   * Return the ID used to identify the collaborative connection to
   * the specified remote display.<br>
   * <br>
   * <b>WARNING!</b>  Due to limitations in the Java RMI implementation,
   * this only works with an exact copy of the RemoteDisplay used to
   * create the collaboration link.
   *
   * @param rmtDpy the specified remote display.
   * @return <tt>DisplayMonitor.UNKNOWN_LISTENER_ID</tt> if not found;
   *         otherwise, returns the ID.
   * @throws RemoteException an RMI error occurred
   */
  public int getConnectionID(RemoteDisplay rmtDpy)
    throws RemoteException
  {
    if (displayMonitor == null) return DisplayMonitor.UNKNOWN_LISTENER_ID;
    return displayMonitor.getConnectionID(rmtDpy);
  }

  /**
   * add a Control to this DisplayImpl
   * @param control Control to add
   */
  public void addControl(Control control) {
    if (displayRenderer == null) return;
    if (control != null && !ControlVector.contains(control)) {
      ControlVector.addElement(control);
      control.setIndex(ControlVector.indexOf(control));
      control.setInstanceNumber(getInstanceNumber(control));
      control.addControlListener((ControlListener )displayMonitor);
    }
  }

  /**
   * get a linked Control with the given Class;
   * only called for Control objects associated with 'single'
   * DisplayRealTypes
   * @param c sub-Class of Control to search for
   * @return linked Control with Class c, or null
   */
  public Control getControl(Class c) { return getControl(c, 0); }

  /**
   * get ordinal instance of linked Control object of the
   * specified class
   * @param c sub-Class of Control to search for
   * @param inst ordinal instance number
   * @return linked Control with Class c, or null
   */
  public Control getControl(Class c, int inst) {
    return getControls(c, null, inst);
  }

  /**
   * get all linked Control objects of the specified Class
   * @param c sub-Class of Control to search for
   * @return Vector of linked Controls with Class c
   */
  public Vector getControls(Class c) {
    Vector v = new Vector();
    getControls(c, v, -1);
    return v;
  }

  /**
   * Internal method which does the bulk of the work for both
   * <CODE>getControl()</CODE> and <CODE>getControls()</CODE>.
   * If <CODE>v</CODE> is non-null, adds all <CODE>Control</CODE>s
   *  of the specified <CODE>Class</CODE> to that <CODE>Vector</CODE>.
   * Otherwise, returns <CODE>inst</CODE> instance of the
   *  <CODE>Control</CODE> matching the specified <CODE>Class</CODE>,
   *  or <CODE>null</CODE> if no <CODE>Control</CODE> matching the
   *  criteria is found.
   */
  private Control getControls(Class ctlClass, Vector v, int inst)
  {
    if (displayRenderer == null) return null;
    if (ctlClass == null) {
      return null;
    }

    GraphicsModeControl gmc = getGraphicsModeControl();
    if (ctlClass.isInstance(gmc)) {
      if (v == null) {
        return gmc;
      }
      v.addElement(gmc);
    }
    else {
      synchronized (ControlVector) {
        Enumeration enumeration = ControlVector.elements();
        while(enumeration.hasMoreElements()) {
          Control c = (Control )enumeration.nextElement();
          if (ctlClass.isInstance(c)) {
            if (v != null) {
              v.addElement(c);
            } else if (c.getInstanceNumber() == inst) {
              return c;
            }
          }
        }
      }
    }

    return null;
  }

  /**
   * @return the total number of controls used by this display
   */
  public int getNumberOfControls() { return ControlVector.size(); }

  /**
   * @return clone of Vector of Controls linked to this DisplayImpl
   * @deprecated - DisplayImpl shouldn't expose itself at this level
   */
  public Vector getControlVector() {
    return (Vector) ControlVector.clone();
  }

  /** whether the Control widget panel needs to be reconstructed */
  private boolean needWidgetRefresh = true;

  /** this Display's associated panel of Control widgets */
  private JPanel widgetPanel = null;

  /**
   * get a GUI component containing this Display's Control widgets;
   * create the widgets as necessary
   * @return Container of widget panel
   */
  public Container getWidgetPanel() {
    if (displayRenderer == null) return null;
    if (needWidgetRefresh) {
      synchronized (MapVector) {
        // construct widget panel if needed
        if (widgetPanel == null) {
          widgetPanel = new JPanel();
          widgetPanel.setLayout(new BoxLayout(widgetPanel, BoxLayout.Y_AXIS));
        }
        else widgetPanel.removeAll();

        if (getLinks().size() > 0) {
          // GraphicsModeControl widget
          GMCWidget gmcw = new GMCWidget(getGraphicsModeControl());
          addToWidgetPanel(gmcw, false);
        }

        for (int i=0; i<MapVector.size(); i++) {
          ScalarMap sm = (ScalarMap) MapVector.elementAt(i);

          DisplayRealType drt = sm.getDisplayScalar();
          try {
            double[] a = new double[2];
            double[] b = new double[2];
            double[] c = new double[2];
            boolean scale = sm.getScale(a, b, c);
            if (scale) {
              // ScalarMap range widget
              RangeWidget rw = new RangeWidget(sm);
              addToWidgetPanel(rw, true);
            }
          }
          catch (VisADException exc) { }
          try {
            if (drt.equals(Display.RGB) || drt.equals(Display.RGBA)) {
              // ColorControl widget
              try {
                LabeledColorWidget lw = new LabeledColorWidget(sm);
                addToWidgetPanel(lw, true);
              }
              catch (VisADException exc) { }
              catch (RemoteException exc) { }
            }
            else if (drt.equals(Display.SelectValue)) {
              // ValueControl widget
              VisADSlider vs = new VisADSlider(sm);
              vs.setAlignmentX(JPanel.CENTER_ALIGNMENT);
              addToWidgetPanel(vs, true);
            }
            else if (drt.equals(Display.SelectRange)) {
              // RangeControl widget
              SelectRangeWidget srw = new SelectRangeWidget(sm);
              addToWidgetPanel(srw, true);
            }
            else if (drt.equals(Display.IsoContour)) {
              // ContourControl widget
              ContourWidget cw = new ContourWidget(sm);
              addToWidgetPanel(cw, true);
            }
            else if (drt.equals(Display.Animation)) {
              // AnimationControl widget
              AnimationWidget aw = new AnimationWidget(sm);
              addToWidgetPanel(aw, true);
            }
          }
          catch (VisADException exc) { }
          catch (RemoteException exc) { }
        }
      }
      needWidgetRefresh = false;
    }
    return widgetPanel;
  }

  /** add a component to the widget panel */
  private void addToWidgetPanel(Component c, boolean divide) {
    if (displayRenderer == null) return;
    if (divide) widgetPanel.add(new Divider());
    widgetPanel.add(c);
  }

  /**
   * @return length of valueArray passed to ShadowType.doTransform()
   */
  public int getValueArrayLength() {
    return valueArrayLength;
  }

  /**
   * @return int[] array mapping from valueArray indices to
   *         ScalarType Vector indices
   */
  public int[] getValueToScalar() {
    return valueToScalar;
  }

  /**
   * @return int[] array mapping from valueArray indices to
   *         ScalarMap Vector indices
   */
  public int[] getValueToMap() {
    return valueToMap;
  }

  /**
   * @return the ProjectionControl associated with this DisplayImpl
   */
  public abstract ProjectionControl getProjectionControl();

  /**
   * @return the GraphicsModeControl associated with this DisplayImpl
   */
  public abstract GraphicsModeControl getGraphicsModeControl();

  /**
   * wait for millis milliseconds
   * @param millis number of milliseconds to wait
   * @deprecated Use <CODE>new visad.util.Delay(millis)</CODE> instead.
   */
  public static void delay(int millis) {
    new visad.util.Delay(millis);
  }

  /**
   * print a stack dump with the given message
   * @param message String to print with stack dump
   */
  public static void printStack(String message) {
    try {
      throw new DisplayException("printStack: " + message);
    }
    catch (DisplayException e) {
      e.printStackTrace();
    }
  }

  /**
   * test for equality between this and the given Object
   * given their complexity, its reasonable that DisplayImpl
   * objects are only equal to themselves
   * @param obj Object to test for equality with this
   * @return flag indicating whether this is equal to obj
   */
  public boolean equals(Object obj) {
    return (obj == this);
  }

  /**
   * Returns the list of DataRenderer-s.  NOTE: The actual list is returned
   * rather than a copy.  If a copy is desired, then use
   * <code>getRendererVector()</code>.
   * @return			The list of DataRenderer-s.
   * @see #getRendererVector()
   */
  public Vector getRenderers()
  {
    return (Vector )RendererVector.clone();
  }

  /**
   * Return the API used for this display
   *
   * @return  the mode being used (UNKNOWN, JPANEL, APPLETFRAME,
   *                               OFFSCREEN, TRANSFORM_ONLY)
   * @throws  VisADException
   */
  public int getAPI()
	throws VisADException
  {
    throw new VisADException("No API specified");
  }

  /**
   * @return the <CODE>DisplayMonitor</CODE> associated with this
   * <CODE>Display</CODE>.
   */
  public DisplayMonitor getDisplayMonitor()
  {
    return displayMonitor;
  }

  /**
   * @return the <CODE>DisplaySync</CODE> associated with this
   * <CODE>Display</CODE>.
   */
  public DisplaySync getDisplaySync()
  {
    return displaySync;
  }

  /**
   * set given MouseBehavior
   * @param m MouseBehavior to set
   */
  public void setMouseBehavior(MouseBehavior m) {
    mouse = m;
  }

  /**
   * @return the MouseBehavior used for this Display
   */
  public MouseBehavior getMouseBehavior() {
    return mouse;
  }

  /**
   * make projection matrix from given arguments
   * @param rotx rotation about x axis
   * @param roty rotation about y axis
   * @param rotz rotation about z axis
   * @param scale linear scale factor
   * @param transx translation along x axis
   * @param transy translation along y axis
   * @param transz translation along z axis
   */
  public double[] make_matrix(double rotx, double roty, double rotz,
         double scale, double transx, double transy, double transz) {
    if (mouse != null) {
      return mouse.make_matrix(rotx, roty, rotz, scale, transx, transy, transz);
    }
    else {
      return null;
    }
  }

  /**
   * multiply matrices
   * @param a first operand matrix
   * @param b second operand matrix
   * @return product matrix
   */
  public double[] multiply_matrix(double[] a, double[] b) {
    if (mouse != null && a != null && b != null) {
      return mouse.multiply_matrix(a, b);
    }
    else {
      return null;
    }
  }

  /**
   * get a BufferedImage of this Display, without synchronizing
   * (assume the application has made sure Data have been
   * transformed and rendered)
   * @return a captured image of this Display
   */
  public BufferedImage getImage() {
    return getImage(false);
  }

  /** 
   * get a BufferedImage of this Display
   * @param sync if true, ensure that all linked Data have been
   *        transformed and rendered
   * @return a captured image of this Display 
   */
  public BufferedImage getImage(boolean sync) {
    if (displayRenderer == null) return null;
    Thread thread = Thread.currentThread();
    String name = thread.getName();
    if (thread.equals(getCurrentActionThread()) ||
        name.startsWith("J3D-Renderer") ||
        name.startsWith("AWT-EventQueue")) {
      throw new VisADError("cannot call getImage() from Thread: " + name);
    }
    if (sync) new Syncher(this);
    return displayRenderer.getImage();
  }

  /**
   * @return a String representation of this Display
   */
  public String toString() {
    return toString("");
  }

  /**
   * @param pre String added to start of each line
   * @return a String representation of this Display
   *         indented by pre (a string of blanks)
   */
  public String toString(String pre) {
    String s = pre + "Display\n";
    Enumeration maps = MapVector.elements();
    while(maps.hasMoreElements()) {
      ScalarMap map = (ScalarMap) maps.nextElement();
      s = s + map.toString(pre + "    ");
    }
    maps = ConstantMapVector.elements();
    while(maps.hasMoreElements()) {
      ConstantMap map = (ConstantMap) maps.nextElement();
      s = s + map.toString(pre + "    ");
    }
    return s;
  }

  /** Class used to ensure that all linked Data have been
      transformed and rendered, used by getImage() */
  public class Syncher extends Object implements DisplayListener {

    private ProjectionControl control;
    int count;

    /**
     * construct a Syncher for the given DisplayImpl
     * @param display DisplayImpl for this Syncher
     */
    Syncher(DisplayImpl display) {
      try {
        synchronized (this) {
          control = display.getProjectionControl();
          count = -1;
          display.disableAction();
          display.addDisplayListener(this);
          display.reDisplayAll();
          display.enableAction();
          this.wait();
        }
      }
      catch(InterruptedException e) {
      }
      display.removeDisplayListener(this);
    }

    /**
     * process DisplayEvent
     * @param e DisplayEvent to process
     */
    public void displayChanged(DisplayEvent e)
           throws VisADException, RemoteException {
      if (e.getId() == DisplayEvent.TRANSFORM_DONE) {
        count = 2;
        control.setMatrix(control.getMatrix());
      }
      else if (e.getId() == DisplayEvent.FRAME_DONE) {
        if (count > 0) {
          control.setMatrix(control.getMatrix());
          count--;
        }
        else if (count == 0) {
          synchronized (this) {
            this.notify();
          }
          count--;
        }
      }
    }
  }

  /**
   * Return the Printable object to be used by a PrinterJob.  This can
   * be used as follows:
   * <pre>
   *    PrinterJob printJob = PrinterJob.getPrinterJob();
   *    PageFormat pf = printJob.defaultPage();
   *    printJob.setPrintable(display.getPrintable(), pf);
   *    if (printJob.printDialog()) {
   *        try {
   *            printJob.print();
   *        }
   *        catch (Exception pe) {
   *            pe.printStackTrace();
   *        }
   *    }
   * </pre>
   *
   * @return printable object
   */
  public Printable getPrintable()
  {
    if (printer == null)
      printer =
        new Printable() {
          public int print(Graphics g, PageFormat pf, int pi)
             throws PrinterException
          {
            if (pi >= 1)
            {
               return Printable.NO_SUCH_PAGE;
            }
            BufferedImage image = DisplayImpl.this.getImage();
            g.drawImage(
                image,
                (int) pf.getImageableX(),
                (int) pf.getImageableY(),
                DisplayImpl.this.component);
            return Printable.PAGE_EXISTS;
          }
        };
    return printer;
  }

  /**
   * handle DisconnectException for the given ReferenceActionLink
   * @param raLink ReferenceActionLink with DisconnectException
   */
  void handleRunDisconnectException(ReferenceActionLink raLink)
  {
    if (!(raLink instanceof DataDisplayLink)) {
      return;
    }

    DataDisplayLink link = (DataDisplayLink )raLink;
  }

  /**
   * Notify this Display that a connection to a remote server has failed
   * @param renderer DataRenderer with failure
   * @param link DataDisplayLink with failure
   */
  public void connectionFailed(DataRenderer renderer, DataDisplayLink link)
  {
    try {
      removeLinks(new DataDisplayLink[] { link });
    } catch (VisADException ve) {
      ve.printStackTrace();
    } catch (RemoteException re) {
      re.printStackTrace();
    }

    if (renderer != null) {
      DataDisplayLink[] links = renderer.getLinks();
      if (links.length <= 1) {
        deleteRenderer(renderer);
      }
    }

    Enumeration enumeration = RmtSrcListeners.elements();
    while (enumeration.hasMoreElements()) {
      RemoteSourceListener l = (RemoteSourceListener )enumeration.nextElement();
      l.dataSourceLost(link.getName());
    }
  }

  /**
   * Inform <tt>listener</tt> of deleted {@link DataRenderer}s.
   *
   * @param listener Object to add.
   */
  public void addRendererSourceListener(RendererSourceListener listener)
  {
    RendererSourceListeners.addElement(listener);
  }

  /**
   * Remove <tt>listener</tt> from the {@link DataRenderer} deletion list.
   *
   * @param listener Object to remove.
   */
  public void removeRendererSourceListener(RendererSourceListener listener)
  {
    RendererSourceListeners.removeElement(listener);
  }

  /**
   * Stop using a {@link DataRenderer}.
   *
   * @param renderer Renderer to delete
   */
  private void deleteRenderer(DataRenderer renderer)
  {
    RendererVector.removeElement(renderer);

    Enumeration enumeration = RendererSourceListeners.elements();
    while (enumeration.hasMoreElements()) {
      ((RendererSourceListener )enumeration.nextElement()).rendererDeleted(renderer);
    }
  }

  /**
   * @deprecated
   */
  public void addDataSourceListener(RemoteSourceListener listener)
  {
    addRemoteSourceListener(listener);
  }

  /**
   * @deprecated
   */
  public void removeDataSourceListener(RemoteSourceListener listener)
  {
    removeRemoteSourceListener(listener);
  }

  /**
   * Inform <tt>listener</tt> of changes in the availability
   * of remote data/collaboration sources.
   *
   * @param listener Object to send change notifications.
   */
  public void addRemoteSourceListener(RemoteSourceListener listener)
  {
    RmtSrcListeners.addElement(listener);
  }

  /**
   * Remove <tt>listener</tt> from the remote source notification list.
   *
   * @param listener Object to be removed.
   */
  public void removeRemoteSourceListener(RemoteSourceListener listener)
  {
    RmtSrcListeners.removeElement(listener);
  }

  /**
   * Inform {@link RemoteSourceListener}s that the specified collaborative
   * connection has been lost.<br>
   * <br>
   * <b>WARNING!</b>  This should only be called from within the
   * visad.collab package!
   *
   * @param id ID of lost connection.
   */
  public void lostCollabConnection(int id)
  {
    Enumeration enumeration = RmtSrcListeners.elements();
    while (enumeration.hasMoreElements()) {
      ((RemoteSourceListener )enumeration.nextElement()).collabSourceLost(id);
    }
  }

  /**
   * Forward messages to the specified <tt>listener</tt>
   *
   * @param listener New message receiver.
   */
  public void addMessageListener(MessageListener listener)
  {
    MessageListeners.addElement(listener);
  }

  /**
   * Remove <tt>listener</tt> from the message list.
   *
   * @param listener Object to remove.
   */
  public void removeMessageListener(MessageListener listener)
  {
    MessageListeners.removeElement(listener);
  }

  /**
   * Send a message to all </tt>MessageListener</tt>s.
   *
   * @param msg Message being sent.
   */
  public void sendMessage(MessageEvent msg)
    throws RemoteException
  {
    RemoteException exception = null;
    Enumeration enumeration = MessageListeners.elements();
    while (enumeration.hasMoreElements()) {
      MessageListener l = (MessageListener )enumeration.nextElement();
      try {
        l.receiveMessage(msg);
      } catch (RemoteException re) {
        if (visad.collab.CollabUtil.isDisconnectException(re)) {
          // remote side disconnected; forget about it
          MessageListeners.removeElement(l);
        } else {
          // save this exception for later
          exception = re;
        }
      }
    }

    if (exception != null) {
      throw exception;
    }
  }

  /**
   * set aspect ratio of XAxis, YAxis & ZAxis in ScalarMaps rather
   * than matrix (i.e., don't distort text fonts); called by
   * ProjectionControl.setAspectCartesian()
   * @param aspect ratios; 3 elements for Java3D, 2 for Java2D
   * @throws VisADException a VisAD error occurred
   * @throws RemoteException an RMI error occurred
   */
  void setAspectCartesian(double[] aspect)
       throws VisADException, RemoteException {
    if (displayRenderer == null) return;
    if (mapslock == null) return;
    synchronized (mapslock) {
      // clone MapVector to avoid need for synchronized access
      Vector tmap = (Vector) MapVector.clone();
      Enumeration maps = tmap.elements();
      while (maps.hasMoreElements()) {
        ScalarMap map = (ScalarMap) maps.nextElement();
        map.setAspectCartesian(aspect);
      }

      tmap = (Vector) ConstantMapVector.clone();
      maps = tmap.elements();
      while (maps.hasMoreElements()) {
        ConstantMap map = (ConstantMap) maps.nextElement();
        map.setAspectCartesian(aspect);
      }

      // resize box
      getDisplayRenderer().setBoxAspect(aspect);

      // reAutoScale(); ??
      reDisplayAll();
    } // end synchronized (mapslock)
  }

  /**
   * Add a busy/idle activity handler.
   *
   * @param ah Activity handler.
   *
   * @throws VisADException If the handler couldn't be added.
   */
  public void addActivityHandler(ActivityHandler ah)
    throws VisADException
  {
    if (displayRenderer == null) return;
    if (displayActivity == null) {
      displayActivity = new DisplayActivity(this);
    }

    displayActivity.addHandler(ah);
  }

  /**
   * Remove a busy/idle activity handler.
   *
   * @param ah Activity handler.
   *
   * @throws VisADException If the handler couldn't be removed.
   */
  public void removeActivityHandler(ActivityHandler ah)
    throws VisADException
  {
    if (displayRenderer == null) return;
    if (displayActivity == null) {
      displayActivity = new DisplayActivity(this);
    }

    displayActivity.removeHandler(ah);
  }

  /**
   * Indicate to activity monitor that the Display is busy.
   */
  public void updateBusyStatus()
  {
    if (displayActivity != null) {
      displayActivity.updateBusyStatus();
    }
  }
}
