Package com.bbn.openmap

Source Code of com.bbn.openmap.MapBean

// **********************************************************************
//
// <copyright>
//
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
//
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/MapBean.java,v $
// $RCSfile: MapBean.java,v $
// $Revision: 1.11.2.18 $
// $Date: 2009/03/03 21:07:22 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.OverlayLayout;

import com.bbn.openmap.event.CenterEvent;
import com.bbn.openmap.event.CenterListener;
import com.bbn.openmap.event.LayerEvent;
import com.bbn.openmap.event.LayerListener;
import com.bbn.openmap.event.PaintListener;
import com.bbn.openmap.event.PaintListenerSupport;
import com.bbn.openmap.event.PanEvent;
import com.bbn.openmap.event.PanListener;
import com.bbn.openmap.event.ProjectionChangeVetoException;
import com.bbn.openmap.event.ProjectionEvent;
import com.bbn.openmap.event.ProjectionListener;
import com.bbn.openmap.event.ProjectionSupport;
import com.bbn.openmap.event.ZoomEvent;
import com.bbn.openmap.event.ZoomListener;
import com.bbn.openmap.proj.Mercator;
import com.bbn.openmap.proj.Proj;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.ProjectionFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.LatLonPoint;

/**
* The MapBean is the main component of the OpenMap Development Kit. It is a
* Java Bean that manages and displays a map. A map is comprised of a projection
* and a list of layers, and this class has methods that allow you to control
* the projection parameters and to add and remove layers. Layers that are part
* of the map receive dynamic notifications of changes to the underlying view
* and projection.
* <p>
* Most of the methods in the MapBean are called from the Java AWT and Swing
* code. These methods make the MapBean a good "Swing citizen" to its parent
* components, and you should not need to invoke them. In general there are only
* two reasons to call MapBean methods: controlling the projection, and adding
* or removing layers.
* <p>
* When controlling the MapBean projection, simply call the method that applies
* - setCenter, pan, zoom, etc. NOTE: If you are setting more than one parameter
* of the projection, it's more efficient to getProjection(), directly set the
* parameters of the projection object, and then call setProjection() with the
* modified projection. That way, each ProjectionListener of the MapBean (each
* layer) will only receive one projectionChanged() method call, as opposed to
* receiving one for each projection adjustment.
* <p>
* To add or remove layers, use the add() and remove() methods that the MapBean
* inherits from java.awt.Container. The add() method can be called with an
* integer that indicates its desired position in the layer list.
* <P>
* Changing the default clipping area may cause some Layers to not be drawn
* completely, depending on what the clipping area is set to and when the layer
* is trying to get itself painted. When manually adjusting clipping area, make
* sure that when restricted clipping is over that a full repaint occurs if
* there is a chance that another layer may be trying to paint itself.
* <P>
* PropertyChangeListeners and ProjectionListeners both receive notifications of
* the projection changes, but the PropertyChangeListeners receive them first.
* If you want to have a component that limits the MapBean's projection
* parameters, it should be a PropertyChangeListener on the MapBean, and throw a
* ProjectionChangeVetoException whenever a Projection setting falls outside of
* the limits. The ProjectionChangeVetoException should hold the alternate
* settings allowed by the listener. When a ProjectionChangeVetoException is
* thrown, all of the PropertyChangeListeners will receive another
* PropertyChangeEvent notification, under the MapBean.projectionVetoed property
* name. The old value for that property will be the rejected Projection object,
* and the new value will be the ProjectionChangeVetoException containing the
* new suggestions. The MapBean will then apply the suggestions and launch
* another round of projection change notifications. The ProjectionListeners
* only receive notification of Projections that have passed through the
* PropertyChangeListeners.
*
* @see Layer
*/
public class MapBean extends JComponent implements ComponentListener,
        ContainerListener, ProjectionListener, PanListener, ZoomListener,
        LayerListener, CenterListener, SoloMapComponent {

    public static final String LayersProperty = "MapBean.layers";

    public static final String CursorProperty = "MapBean.cursor";

    public static final String BackgroundProperty = "MapBean.background";

    public static final String ProjectionProperty = "MapBean.projection";

    public static final String ProjectionVetoedProperty = "MapBean.projectionVetoed";

    /**
     * OpenMap title.
     */
    public static final String title = "OpenMap(tm)";

    /**
     * OpenMap version.
     */
    public static final String version = "4.6.5";

    /**
     * Suppress the copyright message on initialization. But remember, the
     * OpenMap License says you can't do this!
     */
    public static boolean suppressCopyright = false;

    private static final boolean DEBUG_TIMESTAMP = false;

    private static final boolean DEBUG_THREAD = true;

    private static final String copyrightNotice = "OpenMap(tm) Version "
            + version + "\r\n"
            + "  Copyright (C) BBNT Solutions LLC.  All rights reserved.\r\n"
            + "  See http://openmap.bbn.com/ for details.\r\n";

    public final static float DEFAULT_CENTER_LAT = 0.0f;

    public final static float DEFAULT_CENTER_LON = 0.0f;

    // zoomed all the way out
    public final static float DEFAULT_SCALE = Float.MAX_VALUE;

    public final static int DEFAULT_WIDTH = 640;

    public final static int DEFAULT_HEIGHT = 480;

    protected int minHeight = 100;

    protected int minWidth = 100;

    protected Proj projection = new Mercator(new LatLonPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON), DEFAULT_SCALE, DEFAULT_WIDTH, DEFAULT_HEIGHT);

    protected ProjectionSupport projectionSupport;

    /**
     * Layers that are removed from the MapBean are held until the next
     * projection change. When the projection changes, they are notified that
     * they have been removed from the map. This list is kept so that toggling a
     * layer on and off won't cause them to get rid of their resources, in case
     * the user is just creating different views of the map.
     */
    protected Vector removedLayers = new Vector(0);

    /**
     * Some users may want the layers deleted immediately when they are removed
     * from the map. This flag controls that. The default behavior is to hold a
     * reference to a layer and actually release it when the projection changes
     * (default = true). Set to false if you want the MapBean to tell a Layer it
     * has been removed immediately when it happens.
     */
    protected boolean layerRemovalDelayed = true;

    /**
     * This vector is to let the layers know when they have been added to the
     * map.
     */
    protected Vector addedLayers = new Vector(0);

    /**
     * The PaintListeners want to know when the map has been repainted.
     */
    protected PaintListenerSupport painters = null;

    /**
     * The background color for this particular MapBean. If null, the setting
     * for the projection, which in turn is set in the Environment class, will
     * be used.
     */
    protected Paint background = null;

    /**
     * The MapBeanRepaintPolicy to use to handler/filter/pace layer repaint()
     * requests. If not set, a StandardMapBeanRepaintPolicy will be used, which
     * forwards repaint requests to Swing normally.
     */
    protected MapBeanRepaintPolicy repaintPolicy = null;

    public final static Color DEFAULT_BACKGROUND_COLOR = new Color(191, 239, 255);

    /**
     * Return the OpenMap Copyright message.
     *
     * @return String Copyright
     */
    public static String getCopyrightMessage() {
        return copyrightNotice;
    }

    /**
     * Construct a MapBean.
     */
    public MapBean() {
        if (Debug.debugging("mapbean")) {
            debugmsg("MapBean()");
        }
        if (!suppressCopyright) {
            Debug.output(copyrightNotice);
        }

        background = DEFAULT_BACKGROUND_COLOR;

        // Don't need one for every MapBean, just the first one.
        suppressCopyright = true;

        super.setLayout(new OverlayLayout(this));
        projectionSupport = new ProjectionSupport(this);
        addComponentListener(this);
        addContainerListener(this);

        // ----------------------------------------
        // In a builder tool it seems that the OverlayLayout
        // makes the MapBean fail to resize. And since it has
        // no children by default, it has no size. So I add
        // a null Layer here to give it a default size.
        // ----------------------------------------
        if (java.beans.Beans.isDesignTime()) {
            add(new Layer() {
                public void projectionChanged(ProjectionEvent e) {}

                public Dimension getPreferredSize() {
                    return new Dimension(100, 100);
                }
            });
        }

        setPreferredSize(new Dimension(projection.getWidth(), projection.getHeight()));
    }

    /**
     * Return a string-ified representation of the MapBean.
     *
     * @return String
     */
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    /**
     * Call when getting rid of the MapBean, it releases pointers to all
     * listeners and kills the ProjectionSupport thread.
     */
    public void dispose() {
        if (projectionSupport != null) {
            projectionSupport.dispose();
        }

        painters.removeAll();
        addedLayers.removeAllElements();

        removeAll();
        purgeAndNotifyRemovedLayers();
    }

    /*----------------------------------------------------------------------
     * Window System overrides
     *----------------------------------------------------------------------*/

    /**
     * Adds additional constraints on possible children components. The new
     * component must be a Layer. This method included as a good container
     * citizen, and should not be called directly. Use the add() methods
     * inherited from java.awt.Container instead.
     *
     * @param comp Component
     * @param constraints Object
     * @param index int location
     */
    protected final void addImpl(Component comp, Object constraints, int index) {
        if (comp instanceof Layer) {
            super.addImpl(comp, constraints, index);
        } else {
            throw new IllegalArgumentException("only Layers can be added to a MapBean");
        }
    }

    /**
     * Prevents changing the LayoutManager. Don't let anyone change the
     * LayoutManager! This is called by the parent component and should not be
     * called directly.
     */
    public final void setLayout(LayoutManager mgr) {
        throw new IllegalArgumentException("cannot change layout of Map");
    }

    /**
     * Return the minimum size of the MapBean window. Included here to be a good
     * citizen.
     */
    public Dimension getMinimumSize() {
        return new Dimension(minWidth, minHeight);
    }

    /**
     * Set the minimum size of the MapBean window. Included here to be a good
     * citizen.
     */
    public void setMinimumSize(Dimension dim) {
        minWidth = (int) dim.getWidth();
        minHeight = (int) dim.getHeight();
    }

    /**
     * Get the Insets of the MapBean. This returns 0-length Insets.
     * <p>
     * This makes sure that there will be no +x,+y offset when drawing graphics.
     * This is ok since any borders around the MapBean will get drawn afterwards
     * on top.
     *
     * @return Insets 0-length Insets
     */
    public final Insets getInsets() {
        return insets;
    }

    private final transient static Insets insets = new Insets(0, 0, 0, 0);

    /*----------------------------------------------------------------------
     * ComponentListener implementation
     *----------------------------------------------------------------------*/

    /**
     * ComponentListener interface method. Should not be called directly.
     * Invoked when component has been resized, and kicks off a projection
     * change.
     *
     * @param e ComponentEvent
     */
    public void componentResized(ComponentEvent e) {
        if (Debug.debugging("mapbean")) {
            debugmsg("Size changed: " + getWidth() + " x " + getHeight());
        }
        projection.setWidth(getWidth());
        projection.setHeight(getHeight());
        fireProjectionChanged();
    }

    /**
     * ComponentListener interface method. Should not be called directly.
     * Invoked when component has been moved.
     *
     * @param e ComponentEvent
     */
    public void componentMoved(ComponentEvent e) {}

    /**
     * ComponentListener interface method. Should not be called directly.
     * Invoked when component has been shown.
     *
     * @param e ComponentEvent
     */
    public void componentShown(ComponentEvent e) {}

    /**
     * ComponentListener interface method. Should not be called directly.
     * Invoked when component has been hidden.
     *
     * @param e ComponentEvent
     */
    public void componentHidden(ComponentEvent e) {}

    /*----------------------------------------------------------------------
     *
     *----------------------------------------------------------------------*/

    /**
     * Add a ProjectionListener to the MapBean. You do not need to call this
     * method to add layers as ProjectionListeners. This method is called for
     * the layer when it is added to the MapBean. Use this method for other
     * objects that you want to know about the MapBean's projection.
     *
     * @param l ProjectionListener
     */
    public synchronized void addProjectionListener(ProjectionListener l) {
        projectionSupport.addProjectionListener(l);
        // Assume that it wants the current projection
        l.projectionChanged(new ProjectionEvent(this, getProjection()));
    }

    /**
     * Remove a ProjectionListener from the MapBean. You do not need to call
     * this method to remove layers that are ProjectionListeners. This method is
     * called for the layer when it is removed from the MapBean. Use this method
     * for other objects that you want to remove from receiving projection
     * events.
     *
     * @param l ProjectionListener
     */
    public synchronized void removeProjectionListener(ProjectionListener l) {
        projectionSupport.removeProjectionListener(l);
    }

    /**
     * Called from within the MapBean when its projection listeners need to know
     * about a projection change.
     */
    protected void fireProjectionChanged() {
        // Fire the property change, so the messages get cleared out.
        // Then, if any of the layers have a problem with their new
        // projection, their messages will be displayed.
        if (Debug.debugging("proj")) {
            Debug.output("MapBean firing projection: " + getProjection());
        }
        try {
            firePropertyChange(ProjectionProperty, null, getProjection());
        } catch (ProjectionChangeVetoException pcve) {
            firePropertyChange(ProjectionVetoedProperty, getProjection(), pcve);
            pcve.updateWithParameters(this);
            return;
        }
        projectionSupport.fireProjectionChanged(getProjection());
        purgeAndNotifyRemovedLayers();
    }

    /**
     * Clear the vector containing all of the removed layers, and let those
     * layers know they have been removed from the map.
     */
    public void purgeAndNotifyRemovedLayers() {
        // Tell any layers that have been removed that they have
        // been removed
        if (removedLayers.size() == 0) {
            return;
        }
        for (int i = 0; i < removedLayers.size(); i++) {
            Layer l = ((Layer) removedLayers.elementAt(i));
            l.removed(this);
        }
        removedLayers.removeAllElements();

        // Shouldn't call this, but it's the only thing
        // that seems to make it work...
        // Seems to help gc'ing layers in a timely manner.
        if (Debug.debugging("helpgc")) {
            System.gc();
        }
    }

    /*----------------------------------------------------------------------
     * Properties
     *----------------------------------------------------------------------*/

    /**
     * Gets the scale of the map.
     *
     * @return float the current scale of the map
     * @see Projection#getScale
     */
    public float getScale() {
        return projection.getScale();
    }

    /**
     * Sets the scale of the map. The Projection may silently disregard this
     * setting, setting it to a <strong>maxscale </strong> or <strong>minscale
     * </strong> value.
     *
     * @param newScale the new scale
     * @see Proj#setScale
     */
    public void setScale(float newScale) {
        projection.setScale(newScale);
        fireProjectionChanged();
    }

    /**
     * Gets the center of the map in the form of a LatLonPoint.
     *
     * @return the center point of the map
     * @see Projection#getCenter
     */
    public LatLonPoint getCenter() {
        return projection.getCenter();
    }

    /**
     * Sets the center of the map.
     *
     * @param newCenter the center point of the map
     * @see Proj#setCenter(LatLonPoint)
     */
    public void setCenter(LatLonPoint newCenter) {
        projection.setCenter(newCenter);
        fireProjectionChanged();
    }

    /**
     * Sets the center of the map.
     *
     * @param lat the latitude of center point of the map in decimal degrees
     * @param lon the longitude of center point of the map in decimal degrees
     * @see Proj#setCenter(float, float)
     */
    public void setCenter(float lat, float lon) {
        projection.setCenter(lat, lon);
        fireProjectionChanged();
    }

    /**
     * Get the type of the projection.
     *
     * @return int type
     * @see Projection#getProjectionType
     * @deprecated Projection Type integers are no longer really used. The
     *             ProjectionFactory should be consulted for which type are
     *             available, and the projection should be created there.
     */
    public int getProjectionType() {
        return projection.getProjectionType();
    }

    /**
     * Set the type of the projection. If different from the current type, this
     * installs a new projection with the same center, scale, width, and height
     * of the previous one.
     *
     * @param newType the new projection type
     * @deprecated Projection Type integers are no longer really used. The
     *             ProjectionFactory should be consulted for which type are
     *             available, and the projection should be created there.
     */
    public void setProjectionType(int newType) {
        int oldType = projection.getProjectionType();
        if (oldType != newType) {
            LatLonPoint ctr = projection.getCenter();
            setProjection((Proj) (ProjectionFactory.makeProjection(newType,
                    ctr.getLatitude(),
                    ctr.getLongitude(),
                    projection.getScale(),
                    projection.getWidth(),
                    projection.getHeight())));
        }
    }

    /**
     * Set the background color of the map. If the background for this MapBean
     * is not null, the background of the projection will be used.
     *
     * @param color java.awt.Color.
     */
    public void setBackgroundColor(Color color) {
        setBackground(color);
    }

    public void setBackground(Color color) {
        super.setBackground(color);
        setBckgrnd((Paint) color);
    }

    /**
     * We override this to set the paint mode on the Graphics before the border
     * is painted, otherwiser we get an XOR effect in the border.
     */
    public void paintBorder(Graphics g) {
        g.setPaintMode();
        super.paintBorder(g);
    }

    /**
     * Set the background of the map. If the background for this MapBean is not
     * null, the background of the projection will be used.
     *
     * @param paint java.awt.Paint.
     */
    public void setBckgrnd(Paint paint) {
        setBufferDirty(true);

        // Instead, do this.
        Paint oldBackground = background;
        background = paint;
        firePropertyChange(BackgroundProperty, oldBackground, background);

        repaint();
    }

    /**
     * Get the background color of the map. If the background color for this
     * MapBean has been explicitly set, that value will be returned. Otherwise,
     * the background color of the projection will be returned. If the
     * background is not a color (as opposed to Paint) this method will return
     * null.
     *
     * @return color java.awt.Color.
     */
    public Color getBackground() {
        Paint ret = getBckgrnd();
        if (ret instanceof Color) {
            return (Color) ret;
        }

        return super.getBackground();
    }

    /**
     * Get the background of the map. If the background for this MapBean has
     * been explicitly set, that value will be returned. Otherwise, the
     * background of the projection will be returned.
     *
     * @return color java.awt.Color.
     */
    public Paint getBckgrnd() {
        Paint ret = background;
        if (ret == null) {
            // ret = projection.getBackgroundColor();
            ret = super.getBackground();
        }
        return ret;
    }

    /**
     * Get the projection property.
     *
     * @return Projection
     */
    public Projection getProjection() {
        return projection;
    }

    /**
     * Set the projection. Shouldn't be null, and won't do anything if it is.
     *
     * @param aProjection Projection
     */
    public void setProjection(Projection aProjection) {
        if (aProjection != null) {
            setBufferDirty(true);
            projection = (Proj) aProjection;
            setPreferredSize(new Dimension(projection.getWidth(), projection.getHeight()));
            fireProjectionChanged();
        }
    }

    // ------------------------------------------------------------
    // CenterListener interface
    // ------------------------------------------------------------

    /**
     * Handles incoming <code>CenterEvents</code>.
     *
     * @param evt the incoming center event
     */
    public void center(CenterEvent evt) {
        setCenter(evt.getLatitude(), evt.getLongitude());
    }

    // ------------------------------------------------------------
    // PanListener interface
    // ------------------------------------------------------------

    /**
     * Handles incoming <code>PanEvents</code>.
     *
     * @param evt the incoming pan event
     */
    public void pan(PanEvent evt) {
        if (Debug.debugging("mapbean")) {
            debugmsg("PanEvent: " + evt);
        }
        float az = evt.getAzimuth();
        float c = evt.getArcDistance();
        if (Float.isNaN(c)) {
            projection.pan(az);
        } else {
            projection.pan(az, c);
        }

        fireProjectionChanged();
    }

    // ------------------------------------------------------------
    // ZoomListener interface
    // ------------------------------------------------------------

    /**
     * Zoom the Map. Part of the ZoomListener interface. Sets the scale of the
     * MapBean projection, based on a relative or absolute amount.
     *
     * @param evt the ZoomEvent describing the new scale.
     */
    public void zoom(ZoomEvent evt) {
        float newScale;
        if (evt.isAbsolute()) {
            newScale = evt.getAmount();
        } else if (evt.isRelative()) {
            newScale = getScale() * evt.getAmount();
        } else {
            return;
        }
        setScale(newScale);
    }

    // ------------------------------------------------------------
    // ContainerListener interface
    // ------------------------------------------------------------

    protected transient Layer[] currentLayers = new Layer[0];

    protected transient boolean doContainerChange = true;

    /**
     * ContainerListener Interface method. Should not be called directly. Part
     * of the ContainerListener interface, and it's here to make the MapBean a
     * good Container citizen.
     *
     * @param value boolean
     */
    public void setDoContainerChange(boolean value) {
        // if changing from false to true, call changeLayers()
        if (!doContainerChange && value) {
            doContainerChange = value;
            changeLayers(null);
        } else {
            doContainerChange = value;
        }
    }

    /**
     * ContainerListener Interface method. Should not be called directly. Part
     * of the ContainerListener interface, and it's here to make the MapBean a
     * good Container citizen.
     *
     * @return boolean
     */
    public boolean getDoContainerChange() {
        return doContainerChange;
    }

    /**
     * ContainerListener Interface method. Should not be called directly. Part
     * of the ContainerListener interface, and it's here to make the MapBean a
     * good Container citizen.
     *
     * @param e ContainerEvent
     */
    public void componentAdded(ContainerEvent e) {
        // Blindly cast. addImpl has already checked to be
        // sure the child is a Layer.
        addProjectionListener((Layer) e.getChild());

        // If the new layer is in the queue to have removed() called
        // on it take it off the queue, and don't add it to the
        // added() queue (it doesn't know that it was removed, yet).
        // Otherwise, add it to the queue to have added() called on
        // it.
        if (!removedLayers.removeElement(e.getChild())) {
            addedLayers.addElement(e.getChild());
        }
        changeLayers(e);
    }

    /**
     * ContainerListener Interface method. Should not be called directly. Part
     * of the ContainerListener interface, and it's here to make the MapBean a
     * good Container citizen. Layers that are removed are added to a list,
     * which is cleared when the projection changes. If they are added to the
     * MapBean again before the projection changes, they are taken off the list,
     * added back to the MapBean, and are simply repainted. This prevents layers
     * from doing unnecessary work if they are toggled on and off without
     * projection changes.
     *
     * @param e ContainerEvent
     * @see com.bbn.openmap.MapBean#purgeAndNotifyRemovedLayers
     */
    public void componentRemoved(ContainerEvent e) {
        // Blindly cast. addImpl has already checked to be
        // sure the child is a Layer.
        removeProjectionListener((Layer) e.getChild());
        removedLayers.addElement(e.getChild());
        changeLayers(e);
    }

    /**
     * ContainerListener Interface method. Should not be called directly. Part
     * of the ContainerListener interface, and it's here to make the MapBean a
     * good Container citizen.
     *
     * @param e ContainerEvent
     */
    protected void changeLayers(ContainerEvent e) {
        // Container Changes can be disabled to speed adding/removing
        // multiple layers
        if (!doContainerChange) {
            return;
        }
        Component[] comps = this.getComponents();
        int ncomponents = comps.length;
        Layer[] newLayers = new Layer[ncomponents];
        System.arraycopy(comps, 0, newLayers, 0, ncomponents);
        if (Debug.debugging("mapbean"))
            debugmsg("changeLayers() - firing change");
        firePropertyChange(LayersProperty, currentLayers, newLayers);

        // Tell the new layers that they have been added
        for (int i = 0; i < addedLayers.size(); i++) {
            ((Layer) addedLayers.elementAt(i)).added(this);
        }
        addedLayers.removeAllElements();

        currentLayers = newLayers;

    }

    // ------------------------------------------------------------
    // ProjectionListener interface
    // ------------------------------------------------------------

    /**
     * ProjectionListener interface method. Should not be called directly.
     *
     * @param e ProjectionEvent
     */
    public void projectionChanged(ProjectionEvent e) {
        Projection newProj = e.getProjection();
        if (!projection.equals(newProj)) {
            setProjection(newProj);
        }
    }

    /**
     * Set the Mouse cursor over the MapBean component.
     *
     * @param newCursor Cursor
     */
    public void setCursor(Cursor newCursor) {
        firePropertyChange(CursorProperty, this.getCursor(), newCursor);
        super.setCursor(newCursor);
    }

    /**
     * In addition to adding the PropertyChangeListener as the JComponent method
     * does, this method also provides the listener with the initial version of
     * the Layer and Cursor properties.
     */
    public void addPropertyChangeListener(PropertyChangeListener pcl) {
        super.addPropertyChangeListener(pcl);
        pcl.propertyChange(new PropertyChangeEvent(this, LayersProperty, currentLayers, currentLayers));
        pcl.propertyChange(new PropertyChangeEvent(this, CursorProperty, this.getCursor(), this.getCursor()));
        pcl.propertyChange(new PropertyChangeEvent(this, BackgroundProperty, this.getBckgrnd(), this.getBckgrnd()));
    }

    protected final void debugmsg(String msg) {
        Debug.output(this.toString()
                + (DEBUG_TIMESTAMP ? (" [" + System.currentTimeMillis() + "]")
                        : "")
                + (DEBUG_THREAD ? (" [" + Thread.currentThread() + "]") : "")
                + ": " + msg);
    }

    /**
     * Same as JComponent.paint(), except if there are no children (Layers), the
     * projection still paints the background and the border is painted.
     */
    public void paint(Graphics g) {
        if (getComponentCount() == 0 && projection != null) {
            drawProjectionBackground(g);
            paintBorder(g);
        } else {
            super.paint(g);
        }
    }

    /**
     * Convenience method to test if Graphics is Graphics2D object, and to try
     * to do the right thing.
     */
    protected void drawProjectionBackground(Graphics g) {
        if (g instanceof Graphics2D) {
            projection.drawBackground((Graphics2D) g, getBckgrnd());
        } else {
            g.setColor(getBackground());
            projection.drawBackground(g);
        }
    }

    /**
     * Same as JComponent.paintChildren() except any PaintListeners are notified
     * and the border is painted over the children.
     */
    public void paintChildren(Graphics g) {
        paintChildren(g, null);
    }

    /**
     * Same as paintChildren, but allows you to set a clipping area to paint. Be
     * careful with this, because if the clipping area is set while some layer
     * decides to paint itself, that layer may not have all it's objects
     * painted.
     */
    public void paintChildren(Graphics g, Rectangle clip) {

        g = getMapBeanRepaintPolicy().modifyGraphicsForPainting(g);

        if (clip != null) {
            g.setClip(clip);
        } else {
            // Had to do this to make the DrawingTool happy, or
            // anything else swing-like that wanted to be around or on
            // top of the MapBean.
            g.setClip(0, 0, getWidth(), getHeight());
        }

        drawProjectionBackground(g);
        super.paintChildren(g);

        // Take care of the PaintListeners...
        if (painters != null) {
            painters.paint(g);
        }

        // border gets overwritten accidentally, so redraw it now
        paintBorder(g);
    }

    /**
     * Method that provides an option of whether or not to draw the border when
     * painting. Usually called from another object trying to control the Map
     * appearance when events are flying around.
     */
    public void paintChildrenWithBorder(Graphics g, boolean drawBorder) {
        drawProjectionBackground(g);
        if (drawBorder) {
            paintChildren(g);
        } else {
            super.paintChildren(g);
        }
    }

    /**
     * Add a PaintListener.
     *
     * @param l PaintListener
     */
    public synchronized void addPaintListener(PaintListener l) {
        if (painters == null) {
            painters = new PaintListenerSupport(this);
        }
        painters.addPaintListener(l);
    }

    /**
     * Remove a PaintListener.
     *
     * @param l PaintListener
     */
    public synchronized void removePaintListener(PaintListener l) {
        if (painters == null) {
            return;
        }
        painters.removePaintListener(l);

        // Should we get rid of the support if there are no painters?
        // The support will get created when a listener is added.
        if (painters.size() == 0) {
            painters = null;
        }
    }

    // ------------------------------------------------------------
    // LayerListener interface
    // ------------------------------------------------------------

    /**
     * LayerListener interface method. A list of layers will be added, removed,
     * or replaced based on on the type of LayerEvent.
     *
     * @param evt a LayerEvent
     */
    public void setLayers(LayerEvent evt) {
        Layer[] layers = evt.getLayers();
        int type = evt.getType();

        if (type == LayerEvent.ALL) {
            // Don't care about these at all...
            return;
        }

        // @HACK is this cool?:
        if (layers == null) {
            System.err.println("MapBean.setLayers(): layers is null!");
            return;
        }

        boolean oldChange = getDoContainerChange();
        setDoContainerChange(false);

        // use LayerEvent.REPLACE when you want to remove all current
        // layers
        // add a new set
        if (type == LayerEvent.REPLACE) {
            if (Debug.debugging("mapbean")) {
                debugmsg("Replacing all layers");
            }
            removeAll();

            for (int i = 0; i < layers.length; i++) {
                // @HACK is this cool?:
                if (layers[i] == null) {
                    System.err.println("MapBean.setLayers(): layer " + i
                            + " is null");
                    continue;
                }

                if (Debug.debugging("mapbean")) {
                    debugmsg("Adding layer[" + i + "]= " + layers[i].getName());
                }
                add(layers[i]);
                layers[i].setVisible(true);
            }

        }

        // use LayerEvent.ADD when adding and/or reshuffling layers
        else if (type == LayerEvent.ADD) {
            if (Debug.debugging("mapbean")) {
                debugmsg("Adding new layers");
            }
            for (int i = 0; i < layers.length; i++) {
                if (Debug.debugging("mapbean")) {
                    debugmsg("Adding layer[" + i + "]= " + layers[i].getName());
                }
                add(layers[i]);
                layers[i].setVisible(true);
            }
        }

        // use LayerEvent.REMOVE when you want to delete layers from
        // the map
        else if (type == LayerEvent.REMOVE) {
            if (Debug.debugging("mapbean")) {
                debugmsg("Removing layers");
            }
            for (int i = 0; i < layers.length; i++) {
                if (Debug.debugging("mapbean")) {
                    debugmsg("Removing layer[" + i + "]= "
                            + layers[i].getName());
                }
                remove(layers[i]);
            }
        }

        if (!layerRemovalDelayed) {
            purgeAndNotifyRemovedLayers();
        }

        setDoContainerChange(oldChange);
        repaint();
        revalidate();
    }

    /**
     * A call to try and get the MapBean to reduce flashing by controlling when
     * repaints happen, waiting for lower layers to call for a repaint(), too.
     * Calls shouldForwardRepaint(Layer), which acts as a policy for whether to
     * forward the repaint up the Swing tree.
     */
    public void repaint(Layer layer) {
        // Debug.output(layer.getName() + " - wants a repaint()");
        getMapBeanRepaintPolicy().repaint(layer);
    }

    /**
     * Set the MapBeanRepaintPolicy used by the MapBean. This policy can be used
     * to pace/filter layer repaint() requests.
     */
    public void setMapBeanRepaintPolicy(MapBeanRepaintPolicy mbrp) {
        repaintPolicy = mbrp;
    }

    /**
     * Get the MapBeanRepaintPolicy used by the MapBean. This policy can be used
     * to pace/filter layer repaint() requests. If no policy has been set, a
     * StandardMapBeanRepaintPolicy will be created, which simply forwards all
     * requests.
     */
    public MapBeanRepaintPolicy getMapBeanRepaintPolicy() {
        if (repaintPolicy == null) {
            repaintPolicy = new StandardMapBeanRepaintPolicy(this);
        }
        return repaintPolicy;
    }

    /**
     * Convenience function to get the LatLonPoint representing a screen
     * location from a MouseEvent. Returns null if the event is null, or if the
     * projection is not set in the MapBean. Allocates new LatLonPoint with
     * coordinates.
     */
    public LatLonPoint getCoordinates(MouseEvent event) {
        return getCoordinates(event, null);
    }

    /**
     * Convenience function to get the LatLonPoint representing a screen
     * location from a MouseEvent. Returns null if the event is null, or if the
     * projection is not set in the MapBean. Save on memory allocation by
     * sending in the LatLonPoint to fill.
     */
    public LatLonPoint getCoordinates(MouseEvent event, LatLonPoint llp) {
        Projection proj = getProjection();
        if (proj == null || event == null) {
            return null;
        }

        if (llp == null) {
            return proj.inverse(event.getX(), event.getY());
        } else {
            return proj.inverse(event.getX(), event.getY(), llp);
        }
    }

    /**
     * Interface-like method to query if the MapBean is buffered, so you can
     * control behavior better. Allows the removal of specific instance-like
     * quieries for, say, BufferedMapBean, when all you really want to know is
     * if you have the data is buffered, and if so, should be buffer be cleared.
     * For the MapBean, always false.
     */
    public boolean isBuffered() {
        return false;
    }

    /**
     * Interface-like method to set a buffer dirty, if there is one. In MapBean,
     * there isn't.
     *
     * @param value boolean
     */
    public void setBufferDirty(boolean value) {}

    /**
     * Checks whether the image buffer should be repainted.
     *
     * @return boolean whether the layer buffer is dirty. Always true for
     *         MapBean, because a paint is always gonna need to happen.
     */
    public boolean isBufferDirty() {
        return true;
    }

    /**
     * If true (default) layers are held when they are removed, and then
     * released and notified of removal when the projection changes. This saves
     * the layers from releasing resources if the layer is simply being toggled
     * on/off for different map views.
     */
    public void setLayerRemovalDelayed(boolean set) {
        layerRemovalDelayed = set;
    }

    /**
     * Return the flag for delayed layer removal.
     */
    public boolean isLayerRemovalDelayed() {
        return layerRemovalDelayed;
    }

    /**
     * Go through the layers, and for all of them that have the autoPalette
     * variable turned on, show their palettes.
     */
    public void showLayerPalettes() {
        Component[] comps = this.getComponents();
        for (int i = 0; i < comps.length; i++) {
            // they have to be layers
            if (((Layer) comps[i]).autoPalette) {
                ((Layer) comps[i]).showPalette();
            }
        }
    }

    /**
     * Turn off all layer palettes.
     */
    public void hideLayerPalettes() {
        Component[] comps = this.getComponents();
        for (int i = 0; i < comps.length; i++) {
            // they have to be layers
            ((Layer) comps[i]).hidePalette();
        }
    }

}
TOP

Related Classes of com.bbn.openmap.MapBean

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.