// ********************************************************************** // // <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/Layer.java,v $ // $RCSfile: Layer.java,v $ // $Revision: 1.34 $ // $Date: 2008/09/28 19:06:07 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap; import java.awt.Component; import java.awt.Container; import java.awt.Frame; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseListener; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.beans.beancontext.BeanContext; import java.beans.beancontext.BeanContextChild; import java.beans.beancontext.BeanContextChildSupport; import java.beans.beancontext.BeanContextMembershipEvent; import java.beans.beancontext.BeanContextMembershipListener; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.reflect.Method; import java.util.Hashtable; import java.util.Iterator; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.Icon; import javax.swing.JComponent; import com.bbn.openmap.event.InfoDisplayEvent; import com.bbn.openmap.event.InfoDisplayListener; import com.bbn.openmap.event.LayerStatusEvent; import com.bbn.openmap.event.LayerStatusListener; import com.bbn.openmap.event.ListenerSupport; import com.bbn.openmap.event.MapMouseListener; import com.bbn.openmap.event.ProjectionEvent; import com.bbn.openmap.event.ProjectionListener; import com.bbn.openmap.gui.ScrollPaneWindowSupport; import com.bbn.openmap.gui.WindowSupport; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.coords.GeoCoordTransformation; import com.bbn.openmap.util.ComponentFactory; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.PropUtils; import com.bbn.openmap.util.propertyEditor.Inspector; /** * Layer objects are components which can be added to the MapBean to make a map. * <p> * * Layers implement the ProjectionListener interface to listen for * ProjectionEvents. When the projection changes, they may need to refetch, * regenerate their graphics, and then repaint themselves into the new view. * <p> * * When the Layer is added to the MapBean, it will start receiving * ProjectionEvents via the ProjectionListener.projectionChanged() method it has * to implement. There is a setProjection(ProjectionEvent) methods that should * be called from there if you want to save the projection for later use * (handling MouseEvents, etc). If you call getProjection() before calling * setProjection(), getProjection() will return null, and your OMGraphics will * complain and probably freak out at some point. * * <pre> * // // SAMPLE handling of the ProjectionListener interface. * * public void projectionChanged(com.bbn.openmap.event.ProjectionEvent pe) { * Projection proj = setProjection(pe); * if (proj != null) { * // Use the projection to gather OMGraphics in the layer, * // and prepare the layer so that in the paint() method, * // the OMGraphics get rendered. * * // Call any methods that kick off work to build graphics * // here... * * // You get the paint() methods called by calling * // repaint(): * repaint(); * } * * fireStatusUpdate(LayerStatusEvent.FINISH_WORKING); * } * </pre> * * @see com.bbn.openmap.event.ProjectionListener * @see com.bbn.openmap.event.ProjectionEvent * @see com.bbn.openmap.PropertyConsumer */ public abstract class Layer extends JComponent implements ProjectionListener, ProjectionPainter, BeanContextChild, BeanContextMembershipListener, PropertyConsumer, ActionListener { private static final long serialVersionUID = 1L; /** * Pre-caches the swing package. Computed based on the package of * <code>JComponent</code>. */ protected static final String SWING_PACKAGE = getPackage(JComponent.class); /** * The String to use for a key lookup in a Properties object to find the * name to use in a GUI relating to this layer. */ public static final String PrettyNameProperty = "prettyName"; /** * The property to set to add the layer to the BeanContext * "addToBeanContext". This probably needs be set by the layer itself, * because it knows whether it needs other components or not. However, this * property is defined in case an option can be given to the user. If a * Layer doesn't want this option given, it should reset the * addToBeanContext variable after setProperties() is called. The * Layer.setProperties() methods maintain the current state of the variable * if undefined, which is true by default. */ public static final String AddToBeanContextProperty = "addToBeanContext"; /** * Property 'background' to designate this layer as a background layer, * which will cause extra buffering to occur if the application can handle * it. False by default. */ public static final String AddAsBackgroundProperty = "background"; /** * Property 'removable' to designate this layer as removable from the * application, or able to be deleted. True by default. */ public static final String RemovableProperty = "removable"; /** * The property for designating the minimum projection scale value that the * layer will respond to. This Layer class doesn't limit how subclasses will * react to projections with scale values smaller than the specified value. */ public static final String MinScaleProperty = "minScale"; /** * The property for designating the maximum projection scale value that the * layer will respond to. This Layer class doesn't limit how subclasses will * react to projections with scale values greater than the specified value. */ public static final String MaxScaleProperty = "maxScale"; /** * The property to show the palette when the layer is created - or, more * accurately, when the properties are set. */ public static final String AutoPaletteProperty = "autoPalette"; /** Layer-defined action event command to display the palette. */ public static final String DisplayPaletteCmd = "displayPaletteCmd"; /** Layer-defined action event command to hide the palette. */ public static final String HidePaletteCmd = "hidePaletteCmd"; /** * Layer-defined action event command to display the properties using an * Inspector. */ public static final String DisplayPropertiesCmd = "displayPropertiesCmd"; /** * Layer-defined action event command to force a redraw on the layer. The * Layer class does not respond to this command, it's provided as a * convenience. */ public static final String RedrawCmd = "redrawCmd"; /** * Generic property that may be set for a layer to let it know at runtime * that a path prefix needs to be added to a relative data path. This * property should be set in the attributes of a layer after being read from * properties. */ public static final String DataPathPrefixProperty = "dataPathPrefix"; /** * The property used to name a class providing translation services * (optional, depends on input data). */ public final static String TransformProperty = "transform"; /** * The listeners to the Layer that respond to requests for information * displays, like messages, requests for URL displays, etc. */ private final ListenerSupport<InfoDisplayListener> IDListeners = new ListenerSupport<InfoDisplayListener>(this); /** * List of LayerStatusListeners. */ private final ListenerSupport<LayerStatusListener> lsListeners = new ListenerSupport<LayerStatusListener>(this); /** * Token uniquely identifying this layer in the application properties. */ protected String propertyPrefix = null; /** * Used by the LayerHandler to check if the layer should be added to the * MapHandler BeanContext. See the comments under the * AddToBeanContextProperty. True by default. */ protected boolean addToBeanContext = true; /** * Flag used by the layer to indicate that it should be treated as a * background layer, indicating that any cache mechanism available can * enable extra buffering. This may prevent mouse events from being received * by the layer. */ protected boolean addAsBackground = false; /** * Flag to designate the layer as removable or not. */ protected boolean removable = true; /** * A flag to have the layer display it's palette when the properties are * set. If you are creating a layer manually, just call showPalette() * instead. */ protected boolean autoPalette = false; /** * A minimum projection scale value that the layer will respond to. Using * this value for reacting to the projection depends on the Layer * implementation, the Layer class doesn't limit subclasses from doing their * own thing in response to the scale setting on a projection. */ protected float minScale = Float.MIN_VALUE; /** * A maximum projection scale value that the layer will respond to. Using * this value for reacting to the projection depends on the Layer * implementation, the Layer class doesn't limit subclasses from doing their * own thing in response to the scale setting on a projection. */ protected float maxScale = Float.MAX_VALUE; /** * This is a convenience copy of the latest projection received from the * MapBean, when the Layer is added to the map. If you need it, use the * accessor!. */ private Projection projection = null; /** * Support class that now handles palette windows. */ protected transient WindowSupport windowSupport; /** * A helper component listener that is paying attention to the visibility of * the palette. */ protected transient ComponentListener paletteListener; /** * A pointer to the JDialog or JInternalFrame. May be used by the layer's * ComponentListeners to figure out if a component event is for the layer or * for the palette. */ protected transient Container palette; /** * The BeanContext allows Layers to find other components, and other * components to find the layer, if the layer is added to it. */ protected transient BeanContextChildSupport beanContextChildSupport = new BeanContextChildSupport(); /** * All layers have access to an I18n object, which is provided by the * Environment. */ protected transient I18n i18n = Environment.getI18n(); /** * Icon associated with layer. */ private Icon icon = null; /** * Hashtable containing attribute information for the layer. For serialized * layers, we're not really going out of our way to make sure that keys and * values are serializable. */ protected Hashtable attributes; /** * A translator that can be set to convert pre-projected coordinates from * the file into decimal degree lat/lon, and for saving data to a file in * pre-projected coordinates. */ protected GeoCoordTransformation coordTransform; /** * New variable to describe when a layer has responded to a projection * change and is ready to be painted. Used by the BufferedLayerMapBean, * which limits repaint calls to only be passed on when all background * layers are ready to be painted. This eliminates the flashing effect * caused when some layers call for a repaint before background layers do, * causing the map background to be displayed for a second. */ protected AtomicBoolean readyToPaint = new AtomicBoolean(true); /** * Returns the package of the given class as a string. * * @param c a class */ protected static String getPackage(Class c) { String className = c.getName(); int lastDot = className.lastIndexOf('.'); return className.substring(0, lastDot); } /** * Override to only allow swing package listeners. If Listeners get added to * the Layers, the mouse events don't make it to the map. Ever. * <p> * Swing popup menus, like <code>JPopupMenu</code> grab the JComponent by * adding themselves as <code>MouseListener</code> s. So this method allows * instances of classes in the xxx.swing package to be added as * <code>MouseListener</code>s, and no one else. * * @param l a mouse listener. */ public final void addMouseListener(MouseListener l) { String pkg = getPackage(l.getClass()); if (java.beans.Beans.isDesignTime() || pkg.equals(SWING_PACKAGE) || pkg.startsWith(SWING_PACKAGE) || pkg.startsWith("com.sun.java.accessibility.util")) { // Used to do nothing for the equals and startsWith // comparison, but that breaks the menus from being // recinded when something else is clicked on. Thanks to // Tom Peel for pointing this out, 11/29/00. super.addMouseListener(l); } else { throw new IllegalArgumentException("This operation is disallowed because the package \"" + getPackage(l.getClass()) + "\" is not in the swing package (\"" + SWING_PACKAGE + "\")."); } } /** * Sets the properties for the <code>Layer</code>. This particular method * assumes that the marker name is not needed, because all of the contents * of this Properties object are to be used for this layer, and scoping the * properties with a prefix is unnecessary. * * @param props the <code>Properties</code> object. */ public void setProperties(Properties props) { setProperties(getPropertyPrefix(), props); } /** * Sets the properties for the <code>Layer</code>. Part of the * PropertyConsumer interface. Layers which override this method should do * something like: * * <pre> * public void setProperties(String prefix, Properties props) { * super.setProperties(prefix, props); * // do local stuff * } * </pre> * * If the addToBeanContext property is not defined, it maintains the same * state. * * @param prefix the token to prefix the property names * @param props the <code>Properties</code> object */ public void setProperties(String prefix, Properties props) { setLayerProperties(prefix, props); } /** * Called by setProperties. Allows subclasses to avoid super.setProperties() * while still having basic Layer settings taken care of. * * @param prefix the token to prefix the property names * @param props the <code>Properties</code> object */ protected void setLayerProperties(String prefix, Properties props) { String prettyName = PrettyNameProperty; setPropertyPrefix(prefix); String realPrefix = PropUtils.getScopedPropertyPrefix(prefix); prettyName = realPrefix + PrettyNameProperty; String defaultName = getName(); if (defaultName == null) { defaultName = "Anonymous"; } setName(props.getProperty(prettyName, defaultName)); setAddToBeanContext(PropUtils.booleanFromProperties(props, realPrefix + AddToBeanContextProperty, addToBeanContext)); setAddAsBackground(PropUtils.booleanFromProperties(props, realPrefix + AddAsBackgroundProperty, addAsBackground)); setRemovable(PropUtils.booleanFromProperties(props, realPrefix + RemovableProperty, removable)); autoPalette = PropUtils.booleanFromProperties(props, realPrefix + AutoPaletteProperty, autoPalette); setMinScale(PropUtils.floatFromProperties(props, realPrefix + MinScaleProperty, getMinScale())); setMaxScale(PropUtils.floatFromProperties(props, realPrefix + MaxScaleProperty, getMaxScale())); String dataPathPrefix = props.getProperty(realPrefix + DataPathPrefixProperty, ""); if (dataPathPrefix.length() > 0) { putAttribute(DataPathPrefixProperty, dataPathPrefix); } String transClassName = props.getProperty(realPrefix + TransformProperty); if (transClassName != null) { try { coordTransform = (GeoCoordTransformation) ComponentFactory.create(transClassName, realPrefix + TransformProperty, props); } catch (ClassCastException cce) { } } } public void setName(String name) { super.setName(name); BeanContext bc = getBeanContext(); if (bc instanceof MapHandler) { LayerHandler lh = (LayerHandler) ((MapHandler) bc).get("com.bbn.openmap.LayerHandler"); if (lh != null) { lh.setLayers(); } } } /** * PropertyConsumer method, to fill in a Properties object, reflecting the * current values of the layer. If the layer has a propertyPrefix set, the * property keys should have that prefix plus a separating '.' prepended to * each property key it uses for configuration. * * @param props a Properties object to load the PropertyConsumer properties * into. If props equals null, then a new Properties object should be * created. * @return Properties object containing PropertyConsumer property values. If * getList was not null, this should equal getList. Otherwise, it * should be the Properties object created by the PropertyConsumer. */ public Properties getProperties(Properties props) { if (props == null) { props = new Properties(); } String prefix = PropUtils.getScopedPropertyPrefix(propertyPrefix); props.put(prefix + "class", this.getClass().getName()); String prettyName = getName(); if (prettyName != null) { props.put(prefix + PrettyNameProperty, prettyName); } props.put(prefix + AutoPaletteProperty, new Boolean(autoPalette).toString()); props.put(prefix + AddAsBackgroundProperty, new Boolean(addAsBackground).toString()); props.put(prefix + RemovableProperty, new Boolean(removable).toString()); props.put(prefix + AddToBeanContextProperty, new Boolean(addToBeanContext).toString()); if (getMinScale() != Float.MIN_VALUE) { props.put(prefix + MinScaleProperty, Float.toString(getMinScale())); } if (getMaxScale() != Float.MAX_VALUE) { props.put(prefix + MaxScaleProperty, Float.toString(getMaxScale())); } String dataPathPrefix = (String) getAttribute(DataPathPrefixProperty); if (dataPathPrefix != null) { props.put(prefix + DataPathPrefixProperty, dataPathPrefix); } if (coordTransform instanceof PropertyConsumer) { ((PropertyConsumer) coordTransform).getProperties(props); } return props; } /** * Method to fill in a Properties object with values reflecting the * properties able to be set on this PropertyConsumer. The key for each * property should be the raw property name (without a prefix) with a value * that is a String that describes what the property key represents, along * with any other information about the property that would be helpful * (range, default value, etc.). For Layer, this method should at least * return the 'prettyName' property. * * @param list a Properties object to load the PropertyConsumer properties * into. If getList equals null, then a new Properties object should * be created. * @return Properties object containing PropertyConsumer property values. If * getList was not null, this should equal getList. Otherwise, it * should be the Properties object created by the PropertyConsumer. */ public Properties getPropertyInfo(Properties list) { if (list == null) { list = new Properties(); } list.put("class", "Class Name used for Layer."); list.put("class.editor", "com.bbn.openmap.util.propertyEditor.NonEditablePropertyEditor"); String internString = i18n.get(Layer.class, PrettyNameProperty, I18n.TOOLTIP, "Presentable name for Layer"); list.put(PrettyNameProperty, internString); internString = i18n.get(Layer.class, PrettyNameProperty, "Layer Name"); list.put(PrettyNameProperty + LabelEditorProperty, internString); list.put(PrettyNameProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.NonEditablePropertyEditor"); internString = i18n.get(Layer.class, AutoPaletteProperty, I18n.TOOLTIP, "Flag to automatically display palette when properties are set"); list.put(AutoPaletteProperty, internString); internString = i18n.get(Layer.class, AutoPaletteProperty, "Open Palette At Start"); list.put(AutoPaletteProperty + LabelEditorProperty, internString); list.put(AutoPaletteProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); internString = i18n.get(Layer.class, AddAsBackgroundProperty, I18n.TOOLTIP, "Flag to use the layer as a background layer"); list.put(AddAsBackgroundProperty, internString); internString = i18n.get(Layer.class, AddAsBackgroundProperty, "Background"); list.put(AddAsBackgroundProperty + LabelEditorProperty, internString); list.put(AddAsBackgroundProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); internString = i18n.get(Layer.class, RemovableProperty, I18n.TOOLTIP, "Flag to allow layer to be deleted."); list.put(RemovableProperty, internString); internString = i18n.get(Layer.class, RemovableProperty, "Removable"); list.put(RemovableProperty + LabelEditorProperty, internString); list.put(RemovableProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); internString = i18n.get(Layer.class, AddToBeanContextProperty, I18n.TOOLTIP, "Flag to give the layer access to all of the other application components."); list.put(AddToBeanContextProperty, internString); internString = i18n.get(Layer.class, AddToBeanContextProperty, "Add to MapHandler"); list.put(AddToBeanContextProperty + LabelEditorProperty, internString); list.put(AddToBeanContextProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); internString = i18n.get(Layer.class, MinScaleProperty, I18n.TOOLTIP, "Minimum projection scale value that the layer will respond to."); list.put(MinScaleProperty, internString); internString = i18n.get(Layer.class, MinScaleProperty, "Minimum Scale Value"); list.put(MinScaleProperty + LabelEditorProperty, internString); internString = i18n.get(Layer.class, MaxScaleProperty, I18n.TOOLTIP, "Maximum projection scale value that the layer will respond to."); list.put(MaxScaleProperty, internString); internString = i18n.get(Layer.class, MaxScaleProperty, "Maximum Scale Value"); list.put(MaxScaleProperty + LabelEditorProperty, internString); return list; } /** * Set the property key prefix that should be used by the PropertyConsumer. * The prefix, along with a '.', should be prepended to the property keys * known by the PropertyConsumer. * * @param prefix the prefix String. */ public void setPropertyPrefix(String prefix) { propertyPrefix = prefix; } /** * Get the property key prefix that is being used to prepend to the property * keys for Properties lookups. * * @return the property prefix for the layer */ public String getPropertyPrefix() { return propertyPrefix; } /** * Set the projection the layer should use for calculations. You probably * don't need this if you are wondering if you do. Call * setProjection(projEvent) instead. */ public void setProjection(Projection proj) { projection = proj; } /** * This method lets you take the ProjectionEvent received from the MapBean, * and lets you know if you should do something with it. MUST to be called * in the projectionChanged() method of your layer, if you want to refer to * the projection later. If this methods returns null, you probably just * want to call repaint() if your layer.paint() method is ready to paint * what it should. * * @param projEvent the ProjectionEvent from the ProjectionListener method. * @return The new Projection if it is different from the one we already * have, null if is the same as the current one. */ public Projection setProjection(ProjectionEvent projEvent) { Projection newProjection = projEvent.getProjection(); if (!newProjection.equals(getProjection())) { Projection clone = newProjection.makeClone(); setProjection(clone); return clone; } else { return null; } } /** * Get the latest projection. */ public Projection getProjection() { return projection; } /** * Returns the MapMouseListener object that handles the mouse events. This * method is IGNORED in this class: it returns null. Derived Layers should * return the appropriate object if they desire to receive MouseEvents. The * easiest thing for a Layer to do in order to receive MouseEvents is to * implement the MapMouseListener interface and return itself. A code * snippet: * <p> * <pre> * public MapMouseListener getMapMouseListener() { * return this; * } * * public String[] getMouseModeServiceList() { * return new String[] { SelectMouseMode.modeID }; * } * </pre> * * @return null for the default, method can be overridden to return * something interested in mouse events. */ public MapMouseListener getMapMouseListener() { return null; } /** * A GUI query method for other components to use to display whether gui * controls are available for this layer. If your layer provides gui * controls and the LayersPanel is being used in your application, you * should override this method in your layer to return true. This will allow * the other gui components to know that your layer has controls, while * still not actually building those controls until they are needed by the * user. * * @return true if there is a GUI for controlling the layer settings, false * if not or if the getGUI() method hasn't been overridden. */ public boolean hasGUI() { boolean hasGUI = false; try { Method guiMethod = getClass().getMethod("getGUI", (Class[]) null); hasGUI = (guiMethod.getDeclaringClass() != Layer.class); } catch (Exception ex) { hasGUI = getGUI() != null; } return hasGUI; } /** * Gets the gui controls associated with the layer. This default * implementation returns null indicating that the layer has no gui * controls. * * @return java.awt.Component or null, null by default. */ public Component getGUI() { return null; } // ///////////////////////////////////////////////// // InfoDisplay Handling Setup and Firing /** * Adds a listener for <code>InfoDisplayEvent</code>s. * * @param aInfoDisplayListener the listener to add */ public void addInfoDisplayListener(InfoDisplayListener aInfoDisplayListener) { // synchronized (IDListeners) { //2012.06.15 TAW IDListeners.add(aInfoDisplayListener); // } } /** * Removes an InfoDisplayListener from this Layer. * * @param aInfoDisplayListener the listener to remove */ public void removeInfoDisplayListener(InfoDisplayListener aInfoDisplayListener) { // synchronized (IDListeners) { //2012.06.15 TAW IDListeners.remove(aInfoDisplayListener); // } } /** * Sends a request to the InfoDisplayListener to show the information in the * InfoDisplay event on an single line display facility. * * @param evt the InfoDisplay event carrying the string. */ public void fireRequestInfoLine(InfoDisplayEvent evt) { // synchronized (IDListeners) { // //2012.06.15 TAW for (InfoDisplayListener listener : IDListeners) { listener.requestInfoLine(evt); } // } } /** * Sends a request to the InfoDisplay listener to display the information on * an single line display facility. The InfoDisplayEvent is created inside * this function. * * @param infoLine the string to put in the InfoDisplayEvent. */ public void fireRequestInfoLine(String infoLine) { fireRequestInfoLine(new InfoDisplayEvent(this, infoLine)); } /** * Sends a request to the InfoDisplay listener to display the information on * an single line display facility at preferred location. The * InfoDisplayEvent is created inside this function. * * @param infoLine the string to put in the InfoDisplayEvent. * @param loc the index of a preferred location, starting at 0. */ public void fireRequestInfoLine(String infoLine, int loc) { fireRequestInfoLine(new InfoDisplayEvent(this, infoLine, loc)); } /** * Sends a request to the InfoDisplay listener to display the information in * the InfoDisplay event in a Browser. * * @param evt the InfoDisplayEvent holding the contents to put in the * Browser. */ public void fireRequestBrowserContent(InfoDisplayEvent evt) { // synchronized (IDListeners) { //2012.06.15 TAW for (InfoDisplayListener listener : IDListeners) { listener.requestBrowserContent(evt); } // } } /** * Sends a request to the InfoDisplayListener to display the information in * a Browser. The InfoDisplayEvent is created here holding the * browserContent * * @param browserContent the contents to put in the Browser. */ public void fireRequestBrowserContent(String browserContent) { fireRequestBrowserContent(new InfoDisplayEvent(this, browserContent)); } /** * Sends a request to the InfoDisplayListener to display a URL given in the * InfoDisplay event in a Browser. * * @param evt the InfoDisplayEvent holding the url location to give to the * Browser. */ public void fireRequestURL(InfoDisplayEvent evt) { // synchronized (IDListeners) { //2012.06.15 TAW for (InfoDisplayListener listener : IDListeners) { listener.requestURL(evt); } // } } /** * Sends a request to the InfoDisplayListener to display a URL in a browser. * The InfoDisplayEvent is created here, and the URL location is put inside * it. * * @param url the url location to give to the Browser. */ public void fireRequestURL(String url) { fireRequestURL(new InfoDisplayEvent(this, url)); } /** * Sends a request to the InfoDisplayListener to show a specific cursor over * its component area. * * @param cursor the cursor to use. */ public void fireRequestCursor(java.awt.Cursor cursor) { // synchronized (IDListeners) { //2012.06.15 TAW for (InfoDisplayListener listener : IDListeners) { listener.requestCursor(cursor); } // } } /** * Sends a request to the InfoDisplayListener to put the information in the * InfoDisplay event in a dialog window. * * @param evt the InfoDisplayEvent holding the message to put into the * dialog window. */ public void fireRequestMessage(InfoDisplayEvent evt) { // synchronized (IDListeners) { //2012.06.15 TAW for (InfoDisplayListener listener : IDListeners) { listener.requestMessage(evt); } // } } /** * Sends a request to the InfoDisplayListener to display the information in * a dialog window. The InfoDisplayEvent is created here, and the URL * location is put inside it. * * @param message the message to put in the dialog window. */ public void fireRequestMessage(String message) { fireRequestMessage(new InfoDisplayEvent(this, message)); } /** * Request to show the tool tips on the map. * * @param tip string to display. */ public void fireRequestToolTip(String tip) { fireRequestToolTip(new InfoDisplayEvent(this, tip)); } /** * Request to hide the tool tips on the map. */ public void fireHideToolTip() { fireRequestToolTip((InfoDisplayEvent) null); } /** * Fire off a Tool Tip request to the InfoDisplayListeners. If the * InfoDisplayEvent is null, then a requestHideToolTip will be fired. */ public void fireRequestToolTip(InfoDisplayEvent event) { // synchronized (IDListeners) { //2012.06.15 TAW for (InfoDisplayListener listener : IDListeners) { if (event != null) { listener.requestShowToolTip(event); } else { listener.requestHideToolTip(); } } // } } // ///////////////////////////////////////////////// // LayerStatus Handling Setup and Firing /** * Adds a listener for <code>LayerStatusEvent</code>s. * * @param aLayerStatusListener LayerStatusListener */ public void addLayerStatusListener(LayerStatusListener aLayerStatusListener) { // synchronized (lsListeners) { //2012.06.15 TAW lsListeners.add(aLayerStatusListener); // } } /** * Removes a LayerStatusListene from this Layer. * * @param aLayerStatusListener the listener to remove */ public void removeLayerStatusListener(LayerStatusListener aLayerStatusListener) { // synchronized (lsListeners) { //2012.06.15 TAW lsListeners.remove(aLayerStatusListener); // } } /** * Sends a status update to the LayerStatusListener. * * @param evt LayerStatusEvent */ public void fireStatusUpdate(LayerStatusEvent evt) { // synchronized (lsListeners) { //2012.06.15 TAW // AWTAvailable conditional removed, not used, not useful. for (LayerStatusListener listener : lsListeners) { listener.updateLayerStatus(evt); } // } } /** * Sends a status update to the LayerStatusListener. * * @param status the new status */ public void fireStatusUpdate(int status) { fireStatusUpdate(new LayerStatusEvent(this, status)); } /** * Repaint the layer. If you are using BufferedMapBean for your application, * WE STRONGLY RECOMMEND THAT YOU DO NOT OVERRIDE THIS METHOD. This method * marks the layer buffer so that it will be refreshed. If you override this * method, and don't call super.repaint(), the layers will not be repainted. */ public void repaint(long tm, int x, int y, int width, int height) { readyToPaint.set(true); Component p = getParent(); if (p instanceof MapBean) { ((MapBean) p).setBufferDirty(true); if (Debug.debugging("basic")) { Debug.output(getName() + "|Layer: repaint(tm=" + tm + ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ")"); } // How dangerous is this? Let the MapBean manage the // repaint call? Seems to work OK, and lets the buffered // MapBeans work better when they are embedded in other // components. It's this call here that makes the // BufferedLayer work right. // This repaint request has been changed to call a // specific // method on the MapBean, which includes the layer making // the request. This is a hook for a policy object in the // MapBean to make a decision on whether to honor the // request, or to handle it in a different way if the // environment dictates that should happen. // ((MapBean)p).repaint(); to -> ((MapBean) p).repaint(this); } else if (p != null) { p.repaint(tm, x, y, width, height); } else { super.repaint(tm, x, y, width, height); } } /** * This method is here to provide a default action for Layers as they act as * a ProjectionPainter. Normally, ProjectionPainters are expected to receive * the projection, gather/create OMGraphics that apply to the projection, * and render them into the Graphics provided. This is supposed to be done * in the same thread that calls this function, so the caller knows that * when this method returns, everything that the ProjectionPainter needed to * do is complete. * <P> * If the layer doesn't override this method, then the paint(Graphics) * method will be called. * * @param proj Projection of the map. * @param g java.awt.Graphics to draw into. */ public void renderDataForProjection(Projection proj, Graphics g) { if (isProjectionOK(proj)) { paint(g); } } /** * Method that responds whether the Layer should render on the map, given a * particular projection. The method currently just tests the projection's * scale against the min and max values set on the layer. * * @param proj The projection to test against. * @return true if OK. */ public boolean isProjectionOK(Projection proj) { return (proj != null && proj.getScale() >= minScale && proj.getScale() <= maxScale); } /** * This method is called when the layer is added to the MapBean * * @param cont Container */ public void added(Container cont) { } /** * This method is called after the layer is removed from the MapBean and * when the projection changes. We recommend that Layers override this * method and nullify memory-intensive variables. * * @param cont Container */ public void removed(Container cont) { } /** * Method called when layer detects that it has been removed from * MapHandler, assumes it's being thrown away. Use this method to let go of * everything and to make any calls necessary to remove from listener lists * that might not get picked up via MapHandler calls. */ public void dispose() { clearListeners(); if (attributes != null) { attributes.clear(); } removed(null); } /** * Part of a layer hack to notify the component listener when the component * is hidden. These components don't receive the ComponentHidden * notification. Remove when it works. */ protected ListenerSupport<ComponentListener> localHackList; /** * Part of a layer hack to notify the component listener when the component * is hidden. These components don't receive the ComponentHidden * notification. Remove when it works. Set to false to test. */ protected boolean doHack = true; /** * Part of a layer hack to notify the component listener when the component * is hidden. These components don't receive the ComponentHidden * notification. Remove when it works. */ public void setVisible(boolean show) { super.setVisible(show); if (doHack && !show) { notifyHideHack(); } } /** * Part of a layer hack to notify the component listener when the component * is hidden. These components don't receive the ComponentHidden * notification. Remove when it works. */ public void addComponentListener(ComponentListener cl) { super.addComponentListener(cl); if (localHackList == null) { localHackList = new ListenerSupport<ComponentListener>(this); } localHackList.add(cl); } /** * Part of a layer hack to notify the component listener when the component * is hidden. These components don't receive the ComponentHidden * notification. Remove when it works. */ public void removeComponentListener(ComponentListener cl) { super.removeComponentListener(cl); if (localHackList != null) { localHackList.remove(cl); } } /** * Part of a layer hack to notify the component listener when the component * is hidden. These components don't receive the ComponentHidden * notification. Remove when it works. */ public void notifyHideHack() { if (localHackList == null) { return; } ComponentEvent ce = new ComponentEvent(this, ComponentEvent.COMPONENT_HIDDEN); for (ComponentListener listener : localHackList) { listener.componentHidden(ce); } } /** * Set whether the Layer should be added to the BeanContext. */ public void setAddToBeanContext(boolean set) { addToBeanContext = set; } /** * Set whether the Layer should be added to the BeanContext. */ public boolean getAddToBeanContext() { return addToBeanContext; } /** * Mark the layer as one that should be considered a background layer. What * that means is up to the MapBean or application. */ public void setAddAsBackground(boolean set) { addAsBackground = set; } /** * Check to see if the layer is marked as one that should be considered a * background layer. What that means is up to the MapBean or application. * * @return true if layer is a background layer, meaning that it should be * dropped to the bottom of the map and incorporated into a * background layer image buffer if one is available. */ public boolean getAddAsBackground() { return addAsBackground; } /** * @return the readyToPaint */ public boolean isReadyToPaint() { return readyToPaint.get(); } /** * @param readyToPaint the readyToPaint to set */ public void setReadyToPaint(boolean readyToPaint) { this.readyToPaint.set(readyToPaint); } /** * Mark the layer as removable, or one that can be deleted from the * application. What that means is up to the LayerHandler or other * application components. */ public void setRemovable(boolean set) { removable = set; } /** * Check to see if the layer is marked as one that can be removed from an * application. * * @return true if layer should be allowed to be deleted. */ public boolean isRemovable() { return removable; } /** * Check to see if the removable layer can be removed now. * * @return true if layer should be allowed to be deleted. */ public boolean removeConfirmed() { return true; } /** * This is the method that your layer can use to find other objects within * the MapHandler (BeanContext). This method gets called when the Layer gets * added to the MapHandler, or when another object gets added to the * MapHandler after the Layer is a member. If the LayerHandler creates the * Layer from properties, the LayerHandler will add the Layer to the * BeanContext if Layer.addToBeanContext is true. It is false by default. * * For Layers, this method doesn't do anything by default. If you need your * layer to get ahold of another object, then you can use the Iterator to go * through the objects to look for the one you need. */ public void findAndInit(Iterator<?> it) { while (it.hasNext()) { findAndInit(it.next()); } } /** * This method is called by the findAndInit(Iterator) method, once for every * object inside the iterator. It's here to allow subclasses a way to * receive objects and still let the super classes have a shot at the * object. So, you can override this method can call super.findAndInit(obj), * or override the findAndInit(Iterator) method and call * super.findAndInit(obj). Whatever. */ public void findAndInit(Object obj) { } /** * BeanContextMembershipListener method. Called when a new object is added * to the BeanContext of this object. */ public void childrenAdded(BeanContextMembershipEvent bcme) { findAndInit(bcme.iterator()); } /** * BeanContextMembershipListener method. Called when a new object is removed * from the BeanContext of this object. For the Layer, this method doesn't * do anything. If your layer does something with the childrenAdded method, * or findAndInit, you should take steps in this method to unhook the layer * from the object used in those methods. */ public void childrenRemoved(BeanContextMembershipEvent bcme) { Iterator<?> it = bcme.iterator(); while (it.hasNext()) { findAndUndo(it.next()); } } /** * This is the method that does the opposite as the findAndInit(Object). * Lets you call super classes with objects that need to be removed. At this * level, if the layer detects that it is being removed from the MapHandler, * it calls dispose on itself. */ public void findAndUndo(Object obj) { if (obj == this) { this.dispose(); } } /** Method for BeanContextChild interface. */ public BeanContext getBeanContext() { return beanContextChildSupport.getBeanContext(); } /** * Method for BeanContextChild interface. Gets an iterator from the * BeanContext to call findAndInit() over. */ public void setBeanContext(BeanContext in_bc) throws PropertyVetoException { if (in_bc != null) { connectToBeanContext(in_bc); findAndInit(in_bc.iterator()); } } /** * Layer method to just connect to the BeanContext, without grabbing the * iterator as in setBeanContext(). Good for protected sub-layers where you * want to optimize the calling of the findAndInit() method over them. */ public void connectToBeanContext(BeanContext in_bc) throws PropertyVetoException { if (in_bc != null) { in_bc.addBeanContextMembershipListener(this); beanContextChildSupport.setBeanContext(in_bc); } } /** * Layer method to just disconnect from the BeanContext, without grabbing * the iterator as in setBeanContext(). Good for protected sub-layers where * you want to optimize the calling of the findAndUndo() method over them. */ public void disconnectFromBeanContext() throws PropertyVetoException { BeanContext bc = getBeanContext(); if (bc != null) { bc.removeBeanContextMembershipListener(this); beanContextChildSupport.setBeanContext(null); } } /** * Method for BeanContextChild interface. Uses the BeanContextChildSupport * to add a listener to this object's property. This listener wants to have * the right to veto a property change. */ public void addVetoableChangeListener(String propertyName, VetoableChangeListener in_vcl) { beanContextChildSupport.addVetoableChangeListener(propertyName, in_vcl); } /** * Method for BeanContextChild interface. Uses the BeanContextChildSupport * to remove a listener to this object's property. The listener has the * power to veto property changes. */ public void removeVetoableChangeListener(String propertyName, VetoableChangeListener in_vcl) { beanContextChildSupport.removeVetoableChangeListener(propertyName, in_vcl); } /** * Report a vetoable property update to any registered listeners. If anyone * vetos the change, then fire a new event reverting everyone to the old * value and then rethrow the PropertyVetoException. * <P> * * No event is fired if old and new are equal and non-null. * <P> * * @param name The programmatic name of the property that is about to change * * @param oldValue The old value of the property * @param newValue - The new value of the property * * @throws PropertyVetoException if the recipient wishes the property change * to be rolled back. */ public void fireVetoableChange(String name, Object oldValue, Object newValue) throws PropertyVetoException { super.fireVetoableChange(name, oldValue, newValue); beanContextChildSupport.fireVetoableChange(name, oldValue, newValue); } public void clearListeners() { if (localHackList != null) { localHackList.clear(); } // synchronized (IDListeners) { //2012.06.15 TAW IDListeners.clear(); // } // synchronized (lsListeners) { lsListeners.clear(); // } BeanContext bc = getBeanContext(); if (bc != null) { bc.removeBeanContextMembershipListener(this); } } protected void finalize() { if (Debug.debugging("gc")) { Debug.output("Layer |" + getName() + " |: getting GC'd"); } } /** * Fire a component event to the Layer component listeners, with the palette * as the component, letting them know if it's visible or not. */ public void firePaletteEvent(ComponentEvent event) { if (localHackList == null) { return; } palette = (Container) event.getSource(); int eventType = event.getID(); for (ComponentListener listener : localHackList) { if (eventType == ComponentEvent.COMPONENT_HIDDEN) { listener.componentHidden(event); } else if (eventType == ComponentEvent.COMPONENT_SHOWN) { listener.componentShown(event); } } if (eventType == ComponentEvent.COMPONENT_HIDDEN) { palette = null; } } /** * Return the JDialog, or JInternalFrame, that serves as the palette for the * layer. May be null. */ public Container getPalette() { return palette; } /** * Called when something about the layer has changed that would require the * palette to be reconfigured. Will cause getGUI() to be called again. You * should take steps before calling this method to make sure that the * getGUI() method is ready to recreate the palette components from scratch * if needed. */ protected void resetPalette() { java.awt.Container pal = getPalette(); boolean putUp = false; if (pal != null && pal.isVisible()) { putUp = true; setPaletteVisible(false); } if (putUp) { setPaletteVisible(true); } } /** * Make the palette visible or not, destroy if invisible. */ public void setPaletteVisible(boolean visible) { if (visible) { showPalette(); } else { hidePalette(); } } /** * Set the WindowSupport object handling the palette. */ public void setWindowSupport(WindowSupport ws) { windowSupport = ws; } /** * Get the WindowSupport object handling the palette. */ public WindowSupport getWindowSupport() { return windowSupport; } /** * Callback method to override how window support is created. * * @return WindowSupport object for layer palette. */ protected WindowSupport createWindowSupport() { return new ScrollPaneWindowSupport(getGUI(), getName()); } /** * Make the palette visible. Will automatically determine if we're running * in an applet environment and will use a JInternalFrame over a JFrame if * necessary. */ public void showPalette() { WindowSupport ws = getWindowSupport(); if (ws == null) { ws = createWindowSupport(); paletteListener = new ComponentAdapter() { public void componentShown(ComponentEvent e) { firePaletteEvent(e); } public void componentHidden(ComponentEvent e) { firePaletteEvent(e); } }; setWindowSupport(ws); } else { ws.setTitle(getName()); ws.setContent(getGUI()); } if (ws != null) { MapHandler mh = (MapHandler) getBeanContext(); // Window frame = null; // java 5 incompatibility Frame frame = null; if (mh != null) { frame = (Frame) mh.get(java.awt.Frame.class); if (frame == null) { MapBean mapBean = (MapBean) mh.get("com.bbn.openmap.MapBean"); if (mapBean == null) { Debug.message("layer", "Layer.showPalette: Warning...mapBean = null"); } else { try { java.awt.Component parent = mapBean.getParent(); while (parent.getParent() != null && !(parent instanceof java.awt.Window)) { parent = parent.getParent(); } /* java 5 incompatibility */ /* * if (parent instanceof java.awt.Window) { frame = * (java.awt.Window) parent; } */ if (parent instanceof java.awt.Frame) { frame = (java.awt.Frame) parent; } } catch (Exception e) { e.printStackTrace(); } // ignore any problems here } } } if (paletteListener != null) { ws.addComponentListener(paletteListener); } ws.displayInWindow(frame); } } /** * Hide the layer's palette. */ public void hidePalette() { WindowSupport ws = getWindowSupport(); if (ws != null) { ws.killWindow(); } } /** * The default actionPerformed method for Layer. Make sure you call * super.actionPerformed if you care about receiving palette show/hide * commands. This method is also set up to receive the DisplayPropertiesCmd, * and will bring up the Inspector for the layer. */ public void actionPerformed(ActionEvent ae) { String command = ae.getActionCommand(); if (command == DisplayPaletteCmd) { if (Debug.debugging("layer")) { Debug.output(getName() + " displaying palette"); } showPalette(); } else if (command == HidePaletteCmd) { if (Debug.debugging("layer")) { Debug.output(getName() + " hiding palette"); } hidePalette(); } else if (command == DisplayPropertiesCmd) { Inspector inspector = new Inspector(); inspector.inspectPropertyConsumer(this); } } /** * Handle Serialization a little bit better, replacing the I18n and * BeanContextChildSupport. * * @param in * @throws IOException * @throws ClassNotFoundException */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); i18n = Environment.getI18n(); beanContextChildSupport = new BeanContextChildSupport(this); } public Icon getIcon() { return icon; } public void setIcon(Icon icon) { this.icon = icon; } public boolean isAutoPalette() { return autoPalette; } public void setAutoPalette(boolean autoPalette) { this.autoPalette = autoPalette; } public float getMaxScale() { return maxScale; } public void setMaxScale(float maxScale) { this.maxScale = maxScale; } public float getMinScale() { return minScale; } public void setMinScale(float minScale) { this.minScale = minScale; } public void putAttribute(Object key, Object value) { if (attributes == null) { attributes = new Hashtable(); } attributes.put(key, value); } public Object getAttribute(Object key) { Object ret = null; if (key != null && attributes != null) { ret = attributes.get(key); } return ret; } public GeoCoordTransformation getCoordTransform() { return coordTransform; } public void setCoordTransform(GeoCoordTransformation coordTranslator) { this.coordTransform = coordTranslator; } /** * The dataPathPrefix layer attribute lets you append a path to a relative * path at runtime. This method tells the layer to check for that layer * attribute for such a prefix path, and prepends it to the given string * separating them with a '/'. * * @param fileName to prepend the attribute to */ protected String prependDataPathPrefix(String fileName) { String dataPathPrefix = (String) getAttribute(Layer.DataPathPrefixProperty); if (dataPathPrefix != null && dataPathPrefix.length() > 0) { fileName = dataPathPrefix + "/" + fileName; } return fileName; } }