Package com.bbn.openmap.layer.link

Source Code of com.bbn.openmap.layer.link.LinkLayer

// **********************************************************************
//
// <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/link/LinkLayer.java,v $
// $RCSfile: LinkLayer.java,v $
// $Revision: 1.12.2.4 $
// $Date: 2007/06/21 21:41:38 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.layer.link;

/*  Java Core  */
import java.awt.Container;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;

import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.MapHandler;
import com.bbn.openmap.event.MapMouseListener;
import com.bbn.openmap.event.SelectMouseMode;
import com.bbn.openmap.layer.OMGraphicHandlerLayer;
import com.bbn.openmap.omGraphics.OMAction;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.grid.OMGridGenerator;
import com.bbn.openmap.proj.GreatCircle;
import com.bbn.openmap.proj.Mercator;
import com.bbn.openmap.proj.Proj;
import com.bbn.openmap.proj.ProjMath;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.ProjectionFactory;
import com.bbn.openmap.tools.drawing.DrawingToolRequestor;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;

/**
* The LinkLayer is a Swing component, and an OpenMap layer, that communicates
* with a server via the Link protocol. It transmits graphics requests and
* gesture information, and handles the responses to those queries. The entry in
* the openmap.properties file looks like this:
* <P>
*
* <pre>
*   
*    
*     
*     
*       # port number of server
*       link.port=3031
*     
*       # host name of server
*       link.host=host.com
*     
*       # URL of properties file for server attributes.  Properties
*       # contained in this file are passed directly to the server to provide
*       # additional information to the server about how to provide the
*       # graphics.  Some standard properties are listed in the
*       # LinkPropertiesConstants file, but any property can be passed to the
*       # server.  How the server handles the property depends on the server,
*       # but non-applicable properties are ignored.
*       link.propertiesURL=http://location.of.properties.file.com
*     
*      
*     
*    
* </pre>
*
* You have to call setProperties() on this layer to set its parameters, and to
* start the thread that listens to updates from the server.
*/
public class LinkLayer extends OMGraphicHandlerLayer implements
        MapMouseListener, LinkPropertiesConstants, LinkActionConstants,
        DrawingToolRequestor {

    /**
     * The thread listener used to communicate asynchronously. The LinkLayer
     * sends out requsts and notifications to the server, and the LinkListener
     * reads any input from the server, making calls on the LinkLayer as
     * appropriate.
     */
    protected LinkListener listener;
    /**
     * A masked integer describing which gestures should be sent to the server.
     */
    protected int gestureDescriptor = 0;
    /** The port to connect to the server on. */
    protected int port;
    /** The host where the server is running. */
    protected String host;
    /**
     * The special parameters (attributes) transmitted to the server with every
     * query.
     */
    protected LinkProperties args;
    /**
     * The object that provides a link to the layer (and its various threads) on
     * a coordinateed basis.
     */
    protected LinkManager linkManager = null;
    /** The flag to supress pop-up messages. */
    protected boolean quiet = false;
    /** The generator to use with LinkGrid objects. */
    protected OMGridGenerator currentGenerator = null;

    /**
     * The property name to specify what port the server is running on. "port"
     */
    public final static String PortProperty = "port";
    /**
     * The property name to specify the hostname the server is running on.
     * "host"
     */
    public final static String HostProperty = "host";
    /**
     * The property name to specify a URL of a properties file containing
     * properties that will be sent to the server within requests to it. The
     * contents of this file depends on the server. "propertiesURL"
     */
    public final static String ArgsProperty = "propertiesURL";
    public final static String ServerLocationProperty = "isl";
    /**
     * The property to make the layer quiet. "quiet"
     */
    public final static String QuietProperty = "quiet";
    /**
     * The property to specify which grid generator to use for grid objects.
     * "gridGenerator"
     */
    public final static String GridGeneratorProperty = "gridGenerator";
    /**
     * The property to set a pixel distance limit for gestures. "distanceLimit"
     */
    public final static String DistanceLimitProperty = "distanceLimit";

    public final static int DEFAULT_DISTANCE_LIMIT = 4;

    /**
     * The maximum distance away a mouse event can happen away from a graphic in
     * order for it to be considered to have touched.
     */
    protected int distanceLimit = DEFAULT_DISTANCE_LIMIT;

    /**
     * The property to set to true if the server should be able to decide when
     * to kill the client, the overall application. False by default, only
     * modified in setProperties. "exitOnCommand"
     */
    public final static String ExitOnCommandProperty = "exitOnCommand";

    /**
     * The default constructor for the Layer. All of the attributes are set to
     * their default values.
     */
    public LinkLayer() {
        // We don't want to reset the OMGraphicsList automatically
        // when the projection changes, now. With ansynchronous
        // behavior, the current list should be reprojected and the
        // server notified, and the server will update itself if
        // needed.
        setProjectionChangePolicy(new com.bbn.openmap.layer.policy.ListResetPCPolicy(
                this) {
            // Modified so it doesn't reset the OMGraphicList when
            // the SwingWorker thread returns. The list has
            // already been nulled out, will be reset when the
            // asynchronous thread decides it is.
            public void workerComplete(OMGraphicList list) {}
        });
    }

    /**
     * Constructor to use when LinkLayer is not being used with OpenMap
     * application.
     *
     * @param host the hostname of the server's computer.
     * @param port the port number of the server.
     * @param propertiesURL the URL of a properties file that contains
     *        parameters for the server.
     */
    public LinkLayer(String host, int port, String propertiesURL) {
        this();
        this.host = host;
        this.port = port;
        linkManager = new LinkManager(host, port);

        args = new LinkProperties();

        if (propertiesURL != null) {
            try {
                URL propertiesFile = new URL(propertiesURL);
                args.load(propertiesFile.openStream());
            } catch (java.net.MalformedURLException mue) {
                System.err.println("LinkLayer:  Properties URL isn't valid: "
                        + propertiesURL);
                System.err.println(mue);
            } catch (IOException ioe) {
                System.err.println("LinkLayer: IOException reading properties file:");
                System.err.println(ioe);
            }
        }
    }

    /**
     * Sets the current graphics list to the given list.
     *
     * @param aList a list of OMGraphics
     */
    public synchronized void setGraphicList(LinkOMGraphicList aList) {
        super.setList(aList);
    }

    /**
     * Retrieves the current graphics list.
     */
    public synchronized LinkOMGraphicList getGraphicList() {
        return (LinkOMGraphicList) getList();
    }

    /**
     * Called when the layer is no longer part of the map. In this case, we
     * should disconnect from the server if we have a link.
     */
    public void removed(Container cont) {
        linkManager.resetLink();
    }

    /**
     * Sets the masked integer which indicates what types of events get sent to
     * the server.
     *
     * @param descriptor masked int
     * @see LinkActionRequest
     */
    public synchronized void setGestureDescriptor(int descriptor) {
        gestureDescriptor = descriptor;
    }

    /**
     * Gets the masked integer which indicates what types of events get sent to
     * the server.
     *
     * @return descriptor masked int
     * @see LinkActionRequest
     */
    public synchronized int getGestureDescriptor() {
        return gestureDescriptor;
    }

    /**
     * Set all the Link properties from a properties object.
     *
     * @param prefix the prefix to the properties tha might individualize it to
     *        a particular layer.
     * @param properties the properties for the layer.
     */
    public void setProperties(String prefix, Properties properties) {

        super.setProperties(prefix, properties);

        String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);

        String quietString = properties.getProperty(realPrefix + QuietProperty);
        if (quietString != null && quietString.intern() == "true") {
            quiet = true;
        }

        host = properties.getProperty(realPrefix + HostProperty);

        port = PropUtils.intFromProperties(properties, realPrefix
                + PortProperty, LinkServerStarter.DEFAULT_PORT);

        linkManager = new LinkManager(host, port);

        linkManager.setObeyCommandToExit(PropUtils.booleanFromProperties(properties,
                realPrefix + ExitOnCommandProperty,
                false));

        String propertiesURL = properties.getProperty(realPrefix + ArgsProperty);

        args = new LinkProperties(); // Empty if not filled.
        if (propertiesURL != null) {
            try {
                URL propertiesFile = new URL(propertiesURL);
                args.load(propertiesFile.openStream());

                // Need to do something here. The LinkPropertiesConstants have
                // changed in LinkProtocol version .6, to much small strings
                // that don't match up with the DrawingAttributes properties. We
                // need to check for the old property names and replace them
                // with the new property names.

                checkAndReplaceOldPropertyNames(args);

            } catch (java.net.MalformedURLException mue) {
                System.err.println("LinkLayer:  Properties URL isn't valid: "
                        + realPrefix + ArgsProperty);
                System.err.println(mue);
            } catch (IOException ioe) {
                System.err.println("LinkLayer: IOException reading properties file:");
                System.err.println(ioe);
            }
        }

        currentGenerator = (OMGridGenerator) PropUtils.objectFromProperties(properties,
                realPrefix + GridGeneratorProperty);

        if (currentGenerator == null) {
            Debug.message("linkdetail", getName()
                    + "|LinkLayer: no generator for grid objects.");
        }

        distanceLimit = PropUtils.intFromProperties(properties, realPrefix
                + DistanceLimitProperty, distanceLimit);

        // listener = new LinkListener(linkManager, this,
        // currentGenerator);
    }

    /**
     * The LinkPropertiesConstants have changed in LinkProtocol version .6, to
     * much small strings that don't match up with the DrawingAttributes
     * properties. We need to check for the old property names and replace them
     * with the new property names.
     *
     * @param props
     */
    public void checkAndReplaceOldPropertyNames(LinkProperties props) {
        checkAndReplaceOldPropertyName(props, LPC_OLD_LINECOLOR, LPC_LINECOLOR);
        checkAndReplaceOldPropertyName(props, LPC_OLD_LINESTYLE, LPC_LINESTYLE);
        checkAndReplaceOldPropertyName(props,
                LPC_OLD_HIGHLIGHTCOLOR,
                LPC_HIGHLIGHTCOLOR);
        checkAndReplaceOldPropertyName(props, LPC_OLD_FILLCOLOR, LPC_FILLCOLOR);
        checkAndReplaceOldPropertyName(props,
                LPC_OLD_FILLPATTERN,
                LPC_FILLPATTERN);
        checkAndReplaceOldPropertyName(props, LPC_OLD_LINEWIDTH, LPC_LINEWIDTH);
        checkAndReplaceOldPropertyName(props,
                LPC_OLD_LINKTEXTSTRING,
                LPC_LINKTEXTSTRING);
        checkAndReplaceOldPropertyName(props,
                LPC_OLD_LINKTEXTFONT,
                LPC_LINKTEXTFONT);
    }

    public void checkAndReplaceOldPropertyName(LinkProperties props,
                                               String oldPropertyName,
                                               String newPropertyName) {
        String property = props.getProperty(oldPropertyName);
        if (property != null) {
            props.remove(oldPropertyName);
            props.put(newPropertyName, property);
        }
    }

    protected void setListener(LinkListener ll) {
        listener = ll;
    }

    protected LinkListener getListener() {
        return listener;
    }

    /**
     * Prepares the graphics for the layer. This is where the getRectangle()
     * method call is made on the link.
     * <p>
     * Occasionally it is necessary to abort a prepare call. When this happens,
     * the map will set the cancel bit in the LayerThread, (the thread that is
     * running the prepare). If this Layer needs to do any cleanups during the
     * abort, it should do so, but return out of the prepare asap.
     *
     * @return a list of graphics.
     */
    public synchronized OMGraphicList prepare() {

        OMGraphicList currentList = getList();

        if (listener == null) {
            listener = new LinkListener(linkManager, this, currentGenerator);
        }

        if (listener != null && !listener.isListening()) {
            // Call LinkListener to launch SwingWorker to kick off a
            // thread for the listener.
            listener.startUp();
        }

        if (Debug.debugging("link")) {
            Debug.output(getName() + "|LinkLayer.prepare(): Listener "
                    + (listener == null ? "is null," : "is OK,")
                    + " listening ("
                    + (listener == null ? "nope" : "" + listener.isListening())
                    + ")");
        }

        if (isCancelled()) {
            Debug.message("link", getName() + "|LinkLayer.prepare(): aborted.");
            return currentList;
        }

        Projection projection = getProjection();
        if (projection == null) {
            Debug.error("Link Layer needs to be added to the MapBean before it can get graphics!");
            return currentList;
        } else if (currentList != null) {
            // If the list isn't empty, it isn't being cleared when a
            // new projection is received, as dictated by the policy
            // of the layer. Should regenerate it here. If it's
            // understood that a new list will be sent by the server,
            // then a different ProjectionChangePolicy should be used.
            currentList.generate(projection);
        }

        Debug.message("basic", getName() + "|LinkLayer.prepare(): doing it");

        // Setting the OMGraphicsList for this layer. Remember, the
        // LinkOMGraphicList is made up of OMGraphics, which are
        // generated
        // (projected) when the graphics are added to the list. So,
        // after this call, the list is ready for painting.

        // call getRectangle();
        if (Debug.debugging("link")) {
            System.out.println(getName() + "|LinkLayer.prepare(): "
                    + "calling getRectangle " + " with projection: "
                    + projection + " ul = " + projection.getUpperLeft()
                    + " lr = " + projection.getLowerRight());
        }

        // LinkOMGraphicList omGraphicList;

        // //////////// Call getRectangle for server....
        try {
            // We do want the link object here... If another thread is
            // using the link, wait.
            ClientLink l = linkManager.getLink(true);

            if (l == null) {
                System.err.println("LinkLayer: unable to get link in prepare().");
                return currentList;
            }

            synchronized (l) {
                // omGraphicList = getGraphics(l, projection);
                sendMapRequest(l, projection);
            }

            linkManager.finLink();

        } catch (UnknownHostException uhe) {
            System.err.println("LinkLayer: unknown host!");
            // return currentList;
        } catch (java.io.IOException ioe) {
            System.err.println("LinkLayer: IOException contacting server for map request!");
            System.err.println(ioe);

            linkManager.resetLink();

            if (!quiet) {
                fireRequestMessage("Communication error between " + getName()
                        + " layer\nand Link Server: Host: " + host + ", Port: "
                        + port);
            }

            System.err.println("LinkLayer: Communication error between "
                    + getName() + " layer\nand Link Server: Host: " + host
                    + ", Port: " + port);
            // return currentList;
        }

        // ///////////////////////////////////////////////////
        // With asynchronous behavior, we don't listen to the reply
        // now. The LinkListener will handle setting the new
        // OMGraphicList if one is needed as decided by the server.

        // ///////////////////
        // safe quit
        // int size = 0;
        // if (omGraphicList != null) {
        // size = omGraphicList.size();

        // if (Debug.debugging("basic")) {
        // System.out.println(getName()+
        // "|LinkLayer.prepare(): finished with "+
        // size+" graphics");
        // }

        // // omGraphicList.project(projection);
        // }
        // else
        // Debug.message("basic", getName()+
        // "|LinkLayer.prepare(): finished with null graphics list");

        return currentList;
    }

    /**
     * Creates the LinkMapRequest.
     *
     * @param link the link to communicate over.
     * @param proj the projection to give to the graphics.
     * @return LinkOMGraphicList containing graphics from the server.
     * @throws IOException
     */
    protected void sendMapRequest(ClientLink link, Projection proj)
            throws IOException {

        LatLonPoint ul = proj.getUpperLeft();
        LatLonPoint lr = proj.getLowerRight();
        float ulLat = ul.getLatitude();
        float ulLon = ul.getLongitude();
        float lrLat = lr.getLatitude();
        float lrLon = lr.getLongitude();

        LinkBoundingPoly[] boundingPolys = null;

        if (ProjMath.isCrossingDateline(ulLon, lrLon, proj.getScale())) {
            Debug.message("link", "Dateline is on screen");

            float ymin = (float) Math.min(ulLat, lrLat);
            float ymax = (float) Math.max(ulLat, lrLat);

            // xmin, ymin, xmax, ymax
            boundingPolys = new LinkBoundingPoly[2];
            boundingPolys[0] = new LinkBoundingPoly(ulLon, ymin, 180.0f, ymax);
            boundingPolys[1] = new LinkBoundingPoly(-180.0f, ymin, lrLon, ymax);

        } else {
            boundingPolys = new LinkBoundingPoly[1];
            boundingPolys[0] = new LinkBoundingPoly(ulLon, lrLat, lrLon, ulLat);
        }

        LinkMapRequest.write(proj.getCenter().getLatitude(),
                proj.getCenter().getLongitude(),
                proj.getScale(),
                proj.getHeight(),
                proj.getWidth(),
                boundingPolys,
                args,
                link);

        // ///////////////////////////////////////////////////
        // With asynchronous behavior, we don't listen to the reply
        // now. The LinkListener will handle it.

        // link.readAndParse(proj, currentGenerator);

        // // While we are here, check for any change in gesture query
        // // requests.
        // LinkActionRequest lar = link.getActionRequest();
        // if (lar != null) {
        // setGestureDescriptor(lar.getDescriptor());
        // }

        // handleLinkGraphicList(link.getGraphicList());
        // ///////////////////////////////////////////////////
    }

    public void handleLinkGraphicList(LinkGraphicList lgl) {
        Debug.message("link", "LinkLayer.handleLinkGraphicList()");

        if (lgl != null) {
            // Deal with all the messaging....
            handleMessages(lgl.getProperties());
            LinkOMGraphicList lomgl = lgl.getGraphics();
            setGraphicList(lomgl);
            // Do we need to regenerate?
            Projection proj = getProjection();
            if (lomgl.getNeedToRegenerate(proj)) {
                // set to false in LinkGraphicList.readGraphics if the
                // projection was there when the LinkGraphicList was
                // created. If it wasn't there, we need to try to
                // project them before calling repaint(). Projection
                // will be null if the layer hasn't been added to the
                // map.
                lomgl.generate(proj);
            }

            repaint();
        }
    }

    public void handleLinkActionList(LinkActionList lal) {
        Debug.message("link", "LinkLayer.handleLinkActionList()");

        if (lal == null) {
            return;
        }

        handleMessages(lal.getProperties());

        // The only thing we need to do is handle any gesture
        // changes...
        Vector updates = lal.getGraphicUpdates();
        Enumeration items = updates.elements();
        boolean needRepaint = false;
        LinkOMGraphicList graphics = getGraphicList();

        Projection proj = getProjection();

        if (graphics == null) {
            Debug.message("link",
                    "LinkLayer.handleLinkActionList: null LinkOMGraphicList, making new one...");
            // Why ignore what the server has to say, set the new
            // OMGraphicList and react accordingly.
            graphics = new LinkOMGraphicList();
            setGraphicList(graphics);
        }

        while (items.hasMoreElements()) {
            needRepaint = true; // We do!
            GraphicUpdate gu = (GraphicUpdate) items.nextElement();

            // Take care of this first.....
            if (LinkUtil.isMask(gu.action, MODIFY_DESELECTALL_GRAPHIC_MASK)) {
                Debug.message("link",
                        "LinkLayer.handleLinkActionList: deselecting all graphics");
                graphics.deselectAll();
            }

            // Find the graphic that we are talking about - if the
            // ID is not "none", or if the id doesn't match the
            // gesGraphic LinkGraphicID, then look for the new
            // graphic. Otherwise, assume that the gesGraphic is
            // the one that the action refers to.

            // This code was moved from handleGesture to here, the
            // main difference being that in handleGesture any actual
            // OMGraphic that was gestured over was already known at
            // this point, and there was no sense looking for it if
            // you already had it. Since we moved the code, and this
            // method is being called from a different thread, we
            // don't have that luxury - we have to look up the
            // OMGraphic again...

            if (gu == null) {
                Debug.message("link",
                        "LinkLayer.handleLinkActionList: null GraphicUpdate, skipping...");
                continue;
            }

            OMGraphic gug = gu.graphic;
            OMGraphic reactionGraphic = null;
            int reactionGraphicIndex = Link.UNKNOWN;

            if (LinkUtil.isMask(gu.action, UPDATE_ADD_GRAPHIC_MASK)) {
                if (Debug.debugging("link")) {
                    Debug.output("LinkLayer.handleLinkActionList: adding graphic, id:"
                            + gu.id);
                }
                if (gug != null) {
                    gug.generate(proj);
                    graphics.add(gug);
                    reactionGraphic = gug;
                } else {
                    Debug.message("link",
                            "LinkLayer.handleLinkActionList: trying to add null OMGraphic, id: "
                                    + gu.id);
                }
            } else if (gu.id != null) {
                reactionGraphicIndex = graphics.getOMGraphicIndexWithId(gu.id);
                if (reactionGraphicIndex == Link.UNKNOWN) {
                    // Must be an addition/new graphic
                    if (LinkUtil.isMask(gu.action, UPDATE_ADD_GRAPHIC_MASK)) {
                        // If gu.graphic is null, this will throw an
                        // exception
                        if (Debug.debugging("link")) {
                            Debug.output("LinkLayer.handleLinkActionList: adding graphic "
                                    + gu.id);
                        }
                        if (gug != null) {
                            gug.generate(proj);
                            graphics.add(gug);
                            reactionGraphic = gug;
                        } else {
                            Debug.message("link",
                                    "LinkLayer.handleLinkActionList: trying to add null OMGraphic, id: "
                                            + gu.id);
                        }
                    } else {
                        gu.action = 0; // No action...
                        Debug.error("LinkLayer.handleLinkActionList: Gesture Response on an unknown graphic.");
                    }
                } else if (LinkUtil.isMask(gu.action, UPDATE_GRAPHIC_MASK)) {
                    if (gug != null) {
                        gug.generate(proj);
                        reactionGraphic = gug;
                    } else {
                        Debug.message("link",
                                "LinkLayer.handleLinkActionList: trying to update null OMGraphic, id: "
                                        + gu.id);
                    }
                } else {
                    reactionGraphic = graphics.getOMGraphicWithId(gu.id);
                }
            } else {
                Debug.error("LinkLayer.handleLinkActionList:  null ID for graphic");
            }

            // Now, perform the appropriate action on the graphic...

            // Delete a graphic... If you do this, nothing else
            // gets done on the graphic...
            if (LinkUtil.isMask(gu.action, MODIFY_DELETE_GRAPHIC_MASK)) {
                Debug.message("link", "LinkLayer: deleting graphic");
                graphics.removeOMGraphicAt(reactionGraphicIndex);
            } else {

                // For properties updating, or graphic replacement
                if (LinkUtil.isMask(gu.action, UPDATE_GRAPHIC_MASK)) {
                    Debug.message("link", "LinkLayer: updating graphic");
                    graphics.setOMGraphicAt(reactionGraphic,
                            reactionGraphicIndex);
                }

                // For graphic selection and deselection
                if (LinkUtil.isMask(gu.action, MODIFY_SELECT_GRAPHIC_MASK)) {
                    Debug.message("link", "LinkLayer: selecting graphic");
                    reactionGraphic.select();
                } else if (LinkUtil.isMask(gu.action,
                        MODIFY_DESELECT_GRAPHIC_MASK)) {
                    Debug.message("link", "LinkLayer: deselecting graphic");
                    reactionGraphic.deselect();
                }

                // Now, raising or lowering the graphic...
                if (LinkUtil.isMask(gu.action, MODIFY_RAISE_GRAPHIC_MASK)) {
                    Debug.message("link", "LinkLayer: raising graphic");
                    graphics.moveIndexedToTop(reactionGraphicIndex);
                } else if (LinkUtil.isMask(gu.action, MODIFY_LOWER_GRAPHIC_MASK)) {
                    Debug.message("link", "LinkLayer: lowering graphic");
                    graphics.moveIndexedToBottom(reactionGraphicIndex);
                }

            } // else if not deleting it...
        } // while

        if (lal.getNeedMapUpdate()) {
            updateMap(lal.getMapProperties());
            lal.setNeedMapUpdate(false);
            needRepaint = false;
        }

        if (needRepaint) {
            repaint();
        }
    }

    public void handleLinkActionRequest(LinkActionRequest lar) {
        Debug.message("link", "LinkLayer.handleLinkActionRequest()");
        if (lar != null) {
            setGestureDescriptor(lar.getDescriptor());
        }
    }

    /**
     * Looks at a properties object, and checks for the pre-defined messaging
     * attributes. Then, the information delegator is called to handle their
     * display.
     *
     * @param props LinkProperties containing messages.
     */
    public void handleMessages(LinkProperties props) {
        String value = props.getProperty(LPC_INFO);
        if (value != null)
            fireRequestInfoLine(value);

        value = props.getProperty(LPC_URL);
        if (value != null) {
            fireRequestURL(value);
        } else {
            value = props.getProperty(LPC_HTML);
            if (value != null)
                fireRequestBrowserContent(value);
        }
        value = props.getProperty(LPC_MESSAGE);
        if (value != null)
            fireRequestMessage(value);
    }

    // ----------------------------------------------------------------------
    // MapMouseListener interface implementation
    // ----------------------------------------------------------------------
    /** Return the MapMouseListener for the layer. */
    public synchronized MapMouseListener getMapMouseListener() {
        return this;
    }

    /**
     * Return the strings identifying the Mouse Modes that the MapMouseListener
     * wants to receive gestures from.
     */
    public String[] getMouseModeServiceList() {
        String[] services = { SelectMouseMode.modeID };
        return services;
    }

    public boolean mousePressed(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_PRESSED_MASK)) {
            return handleGesture(MOUSE_PRESSED_MASK, e);
        }
        return false;
    }

    public boolean mouseReleased(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_RELEASED_MASK)) {
            return handleGesture(MOUSE_RELEASED_MASK, e);
        }
        return false;
    }

    public boolean mouseClicked(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_CLICKED_MASK)) {
            return handleGesture(MOUSE_CLICKED_MASK, e);
        }
        return false;
    }

    public void mouseEntered(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_ENTERED_MASK)) {
            handleGesture(MOUSE_ENTERED_MASK, e);
        }
    }

    public void mouseExited(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_EXITED_MASK)) {
            handleGesture(MOUSE_EXITED_MASK, e);
        }
    }

    public boolean mouseDragged(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_DRAGGED_MASK)) {
            return handleGesture(MOUSE_DRAGGED_MASK, e);
        }
        return false;
    }

    public boolean mouseMoved(MouseEvent e) {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_MOVED_MASK)) {
            return handleGesture(MOUSE_MOVED_MASK, e);
        }
        return false;
    }

    public void mouseMoved() {
        if (LinkUtil.isMask(getGestureDescriptor(), MOUSE_MOVED_MASK)) {
            handleGesture(MOUSE_MOVED_MASK, null);
        }
    }

    /**
     * Given a graphic and the type of gesture caught, react to it based on the
     * properties object located in the Graphic. The default behavior here is
     * that if the gesture is a MouseMoved, select the graphic, and if there is
     * an info line, show it. If the gesture is a MouseRelease, display the info
     * line, and also check the following, in this order: url and then html. If
     * there is a message property, the message is sent in a pop-up window.
     *
     * @param graphic the graphic to check out.
     * @param descriptor the type of gesture.
     * @param e mouse event, to get location.
     * @return true if the server still needs to be told - per descriptor bit
     *         11.
     */
    protected boolean graphicGestureReaction(OMGraphic graphic, int descriptor,
                                             MouseEvent e) {
        LinkProperties props = (LinkProperties) graphic.getAppObject();

        // Mouse clicked
        boolean mc = LinkUtil.isMask(descriptor, MOUSE_CLICKED_MASK);
        // Mouse released
        boolean mr = LinkUtil.isMask(descriptor, MOUSE_RELEASED_MASK);
        // Mouse moved
        boolean mm = LinkUtil.isMask(descriptor, MOUSE_MOVED_MASK);
        // server inform
        boolean si = LinkUtil.isMask(getGestureDescriptor(),
                SERVER_NOTIFICATION_MASK);

        boolean ret = true;

        if (mr || mc) {
            String url = props.getProperty(LPC_URL);
            if (url != null) {
                if (Debug.debugging("link")) {
                    System.out.println("LinkLayer:graphicGestureReaction: displaying url: "
                            + url);
                }
                fireRequestURL(url);
                ret = si;
            } else {
                String html = props.getProperty(LPC_HTML);
                if (html != null) {
                    fireRequestBrowserContent(html);
                    ret = si;
                }
            }

            // Just reuse url instead of declaring another string
            // object
            url = props.getProperty(LPC_MESSAGE);
            if (url != null) {
                fireRequestMessage(url);
                ret = si;
            }
        }

        if (mr || mm || mc) {
            String info = props.getProperty(LPC_INFO);
            if (info != null) {
                if (Debug.debugging("link")) {
                    System.out.println("LinkLayer:graphicGestureReaction: displaying info line: "
                            + info);
                }
                fireRequestInfoLine(info);
                ret = si;
            }
        }

        return ret;
    }

    /**
     * Send the query, act on the response, and tell the caller if the gesture
     * was consumed. The Link actually gets a copy of the layer to handle
     * communication with the InformationDelegator. The GraphicUpdates are
     * handled in this method - the graphics list is modified.
     *
     * @param descriptor a masked integer telling the type of gesture.
     * @param e the MouseEvent.
     * @return true if the event was consumed.
     */
    protected boolean handleGesture(int descriptor, MouseEvent e) {
        Debug.message("link", "LinkLayer: handleGesture:");

        try {
            LinkOMGraphicList graphics = getGraphicList(); // Get old
            // list

            OMGraphic gesGraphic = null;
            if (graphics == null) {
                // Nothing to search on - this condition occurs when
                // the layer is already busy getting new graphics as a
                // result of a changed projection.
                // It also occurs when the layer does not have any graphics
                // in it.
                Debug.message("link", "LinkLayer: null graphics list.");
            } else {
                if (e == null) {
                    graphics.deselectAll();
                    return false;
                }

                // Find out if a graphic is closeby...
                // int gesGraphicIndex = graphics.findIndexOfClosest(e.getX(),
                // e.getY(),
                // distanceLimit);

                // We need to do this to deselect everything else too.
                gesGraphic = graphics.selectClosest(e.getX(),
                        e.getY(),
                        distanceLimit);
            }

            String id = null;

            // If there was a graphic, set the mask to indicate that,
            // and keep track of the graphic and the list index of the
            // graphic for the response. If a graphic modify command
            // comes back without an ID, then we'll assume the server
            // was refering to this graphic.
            if (gesGraphic != null) {

                boolean tellServer = graphicGestureReaction(gesGraphic,
                        descriptor,
                        e);

                if (!tellServer) {
                    repaint();
                    return true;
                }

                // needRepaint = true; // Why? At this point, we
                // should wait to see what the server wants us to do,
                // we should only repaint if a graphic update comes
                // back.

                descriptor = LinkUtil.setMask(descriptor, GRAPHIC_ID_MASK);
                id = ((LinkProperties) gesGraphic.getAppObject()).getProperty(LPC_GRAPHICID);
            } else {
                // clear out info line
                fireRequestInfoLine("");
            }

            // server inform
            if (!LinkUtil.isMask(getGestureDescriptor(),
                    SERVER_NOTIFICATION_MASK)) {
                return false;
            }

            // Get the lat/lon point of the event
            LatLonPoint llpoint = getProjection().inverse(e.getX(), e.getY());

            // Don't need these anymore, look below for explaination
            // for asynchronous operation.
            // LinkActionList lal;
            // LinkActionRequest lar;

            ClientLink l = linkManager.getLink(false);

            // We'll check this here because we don't want to wait if
            // it is not available - it could be used for another
            // graphics or gui fetch.
            if (l == null) {
                Debug.message("link",
                        "LinkLayer: unable to get link in handleGesture().");
                return false;
            }

            // Using the link - carefully prevent others from using it
            // too!
            synchronized (l) {
                if (id != null) {
                    args.setProperty(LPC_GRAPHICID, id);
                } else {
                    // Reset this to prevent sending the id of a previously
                    // selected graphic when no graphic is clicked on.
                    args.remove(LPC_GRAPHICID);
                }

                // Send the query
                LinkActionRequest.write(descriptor,
                        e,
                        llpoint.getLatitude(),
                        llpoint.getLongitude(),
                        args,
                        l);

                // ///////////////////////////////////////////////////
                // With asynchronous behavior, we don't listen to the
                // reply
                // now. The LinkListener will handle it.

                // // Read the response
                // l.readAndParse(getProjection(), currentGenerator,
                // this);

                // lal = l.getActionList();
                // lar = l.getActionRequest();

                // if (id != null) {
                // args.remove(LPC_GRAPHICID);
                // }
                // ///////////////////////////////////////////////////

            }

            linkManager.finLink();

            // ///////////////////////////////////////////////////
            // With asynchronous behavior, we don't listen to the
            // reply
            // now. The LinkListener will handle it.

            // handleLinkActionRequest(lar);

            // // If nothing else was returned concerning the gesture
            // query
            // if (lal == null) {
            // return false;
            // }

            // handleLinkActionList(lal);
            // return lal.consumedGesture();

            // ///////////////////////////////////////////////////

            // I don't know what to answer here, we really don't know
            // at this point. There may be something we can do to set
            // up some lag circle to start returning true if we need
            // to, but if we're not listening in this thread, we just
            // don't know if the gesture is consumed here and we can't
            // hold up the event thread to find out.
            return false;

        } catch (IOException ioe) {
            System.err.println("LinkLayer: IOException contacting server during gesture handling!");
            System.err.println(ioe);
            linkManager.resetLink();
            return false;
        }
    }

    // DrawingToolRequestor method
    public void drawingComplete(OMGraphic omg, OMAction action) {
        // //////////// send the new graphic, along with instructions
        // on what to do with it, to the server.
        String id = null; // unknown

        Object obj = omg.getAppObject();
        LinkProperties lp = null;
        if (obj != null && obj instanceof LinkProperties) {
            lp = (LinkProperties) obj;
            id = lp.getProperty(LPC_GRAPHICID);
        }

        if (id == null) {
            // Doesn't look like it was a modified graphic already
            // recieved from the server, so we should tell the server
            // to add it to its list.
            action.setMask(OMAction.ADD_GRAPHIC_MASK);
        }

        try {
            // We do want the link object here... If another thread is
            // using the link, wait.
            ClientLink l = linkManager.getLink(true);

            if (l == null) {
                System.err.println("LinkLayer.drawingComplete: unable to get link.");
                return;
            }

            synchronized (l) {
                LinkActionList lal = new LinkActionList(l, new LinkProperties());

                if (action.isMask(OMAction.ADD_GRAPHIC_MASK)
                        || action.isMask(OMAction.UPDATE_GRAPHIC_MASK)) {
                    lal.writeGraphicGestureHeader(action.getValue());
                    LinkGraphic.write(omg, l);
                } else {
                    // This shouldn't ever get called with a null lp
                    // properties object. If the object is new or
                    // doesn't have an ID, the upper paragraph will
                    // get called.
                    lal.modifyGraphic(action.getValue(), lp);
                }
                lal.end(Link.END_TOTAL);
            }

            // ///////////////////////////////////////////////////
            // With asynchronous behavior, we don't listen to the
            // reply
            // now. The LinkListener will handle it.

            // l.readAndParse(getProjection(), currentGenerator);
            // ///////////////////////////////////////////////////

            linkManager.finLink();

        } catch (UnknownHostException uhe) {
            Debug.error("LinkLayer: unknown host!");
            return;
        } catch (java.io.IOException ioe) {
            Debug.error("LinkLayer: Communication error between " + getName()
                    + " layer\nand Link Server: Host: " + host + ", Port: "
                    + port + "LinkLayer: IOException contacting server!\n"
                    + ioe.getMessage());

            linkManager.resetLink();

            if (!quiet) {
                fireRequestMessage("Communication error between " + getName()
                        + " layer\nand Link Server: Host: " + host + ", Port: "
                        + port);
            }

            return;
        }
    }

    /**
     * Set the search distance limit pixel distance for graphics searches. When
     * the graphics list is checked for a graphic that is closest to a mouse
     * event, this is the pixel limit within hits are considered.
     *
     * @param limit the pixel limit to consider something "closest".
     */
    public void setDistanceLimit(int limit) {
        if (limit < 0) {
            distanceLimit = 0;
        } else {
            distanceLimit = limit;
        }
    }

    /**
     * Get the search distance limit pixel distance for graphics searches.
     */
    public int getDistanceLimit() {
        return distanceLimit;
    }

    /**
     * Looks at a properties object, and checks for map updates.
     *
     * @param props LinkProperties containing map parameters.
     */
    public void updateMap(LinkProperties props) {

        Proj projection = (Proj) getProjection();
        float latitude = PropUtils.floatFromProperties(props,
                LPC_CENTER_LAT,
                projection.getCenter().getLatitude());
        float longitude = PropUtils.floatFromProperties(props,
                LPC_CENTER_LONG,
                projection.getCenter().getLongitude());
        float scale = PropUtils.floatFromProperties(props,
                LPC_SCALE,
                projection.getScale());
        int width = PropUtils.intFromProperties(props,
                LPC_WIDTH,
                projection.getWidth());
        int height = PropUtils.intFromProperties(props,
                LPC_HEIGHT,
                projection.getHeight());

        String projType = props.getProperty(LPC_PROJECTION);

        float latmin = PropUtils.floatFromProperties(props, LPC_LATMIN, -1000.f);
        float latmax = PropUtils.floatFromProperties(props, LPC_LATMAX, -1000.f);
        float lonmin = PropUtils.floatFromProperties(props, LPC_LONMIN, -1000.f);
        float lonmax = PropUtils.floatFromProperties(props, LPC_LONMAX, -1000.f);

        if (latmin >= -90.f && latmax <= 90.f && lonmin >= -180.f
                && lonmax <= 180.f && latmin <= latmax && lonmin <= lonmax) {
            // Calculate center point
            float dist = 0.5f * GreatCircle.spherical_distance(ProjMath.degToRad(latmax),
                    ProjMath.degToRad(lonmin),
                    ProjMath.degToRad(latmin),
                    ProjMath.degToRad(lonmax));
            float azimuth = GreatCircle.spherical_azimuth(ProjMath.degToRad(latmax),
                    ProjMath.degToRad(lonmin),
                    ProjMath.degToRad(latmin),
                    ProjMath.degToRad(lonmax));
            LatLonPoint center = GreatCircle.spherical_between(ProjMath.degToRad(latmax),
                    ProjMath.degToRad(lonmin),
                    dist,
                    azimuth);
            latitude = center.getLatitude();
            longitude = center.getLongitude();
        }

        MapHandler mapHandler = (MapHandler) getBeanContext();
        if (mapHandler == null) {
            Debug.message("link", "Warning...mapHandler = null");
        } else {
            MapBean mapBean = (MapBean) mapHandler.get("com.bbn.openmap.MapBean");
            if (mapBean == null) {
                Debug.message("link", "Warning...mapBean = null");
            } else {
                if (projType != null) {
                    Class projClass = ProjectionFactory.getProjClassForName(projType);
                    if (projClass == null) {
                        projClass = Mercator.class;
                    }
                    projection = (Proj) ProjectionFactory.makeProjection(projClass,
                            latitude,
                            longitude,
                            scale,
                            width,
                            height);
                } else {
                    projection = (Proj) mapBean.getProjection();
                    projection.setCenter(latitude, longitude);
                    projection.setScale(scale);
                    projection.setWidth(width);
                    projection.setHeight(height);
                }

                if (latmin >= -90.f && latmax <= 90.f && lonmin >= -180.f
                        && lonmax <= 180.f && latmin <= latmax
                        && lonmin <= lonmax) {
                    LatLonPoint upperLeft = new LatLonPoint(latmax, lonmin);
                    LatLonPoint lowerRight = new LatLonPoint(latmin, lonmax);
                    scale = ProjMath.getScale(upperLeft, lowerRight, projection);
                    projection.setScale(scale);
                    LatLonPoint ul = projection.getUpperLeft();
                    LatLonPoint lr = projection.getLowerRight();
                    float factor1 = (latmax - latmin)
                            / (ul.getLatitude() - lr.getLatitude());
                    float factor2 = (lonmax - lonmin)
                            / (lr.getLongitude() - ul.getLongitude());
                    if (factor2 > factor1)
                        factor1 = factor2;
                    if (factor1 > 1.0) {
                        scale *= factor1;
                        projection.setScale(scale);
                    }
                }

                mapBean.setProjection(projection);
            }
        }
    }
}
TOP

Related Classes of com.bbn.openmap.layer.link.LinkLayer

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.