// ********************************************************************** // // <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.17 $ // $Date: 2007/06/21 21:39:04 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.layer.link; /* Java Core */ import java.awt.Container; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; 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.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 requests 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 suppress 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 that 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().equals("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()) + ")"); } 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. * @throws IOException */ protected void sendMapRequest(ClientLink link, Projection proj) throws IOException { Point2D ul = proj.getUpperLeft(); Point2D lr = proj.getLowerRight(); float ulLat = (float) ul.getY(); float ulLon = (float) ul.getX(); float lrLat = (float) lr.getY(); float lrLon = (float) lr.getX(); 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); } Point2D center = proj.getCenter(); LinkMapRequest.write((float) center.getY(), (float) center.getX(), 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(); if (gu == null) { Debug .message("link", "LinkLayer.handleLinkActionList: null GraphicUpdate, skipping..."); continue; } // Take care of this first..... if (LinkUtil.isMask(gu.action, MODIFY_DESELECTALL_GRAPHIC_MASK)) { Debug .message("link", "LinkLayer.handleLinkActionList: deselecting all graphics"); graphics.deselect(); } // 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... 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.remove(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); } if (reactionGraphic != null) { // 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.getAttribute(OMGraphic.APP_OBJECT); // 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.deselect(); 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 referring 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.getAttribute(OMGraphic.APP_OBJECT)) .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 Point2D 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, (float) llpoint.getY(), (float) llpoint.getX(), 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.getAttribute(OMGraphic.APP_OBJECT); LinkProperties lp = null; if (obj instanceof LinkProperties) { lp = (LinkProperties) obj; id = lp.getProperty(LPC_GRAPHICID); } if (id == null) { // Doesn't look like it was a modified graphic already // received 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(); Point2D center = projection.getCenter(); float latitude = PropUtils.floatFromProperties(props, LPC_CENTER_LAT, (float) center.getY()); float longitude = PropUtils.floatFromProperties(props, LPC_CENTER_LONG, (float) center.getX()); 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 double dist = 0.5f * GreatCircle.sphericalDistance(ProjMath .degToRad(latmax), ProjMath.degToRad(lonmin), ProjMath .degToRad(latmin), ProjMath.degToRad(lonmax)); double azimuth = GreatCircle.sphericalAzimuth(ProjMath .degToRad(latmax), ProjMath.degToRad(lonmin), ProjMath .degToRad(latmin), ProjMath.degToRad(lonmax)); center = GreatCircle.sphericalBetween(ProjMath.degToRad(latmax), ProjMath.degToRad(lonmin), dist, azimuth); latitude = (float) center.getY(); longitude = (float) center.getX(); } 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 { center = new Point2D.Float(latitude, longitude); ProjectionFactory projFactory = mapBean.getProjectionFactory(); if (projType != null) { Class<? extends Projection> projClass = projFactory .getProjClassForName(projType); if (projClass == null) { projClass = Mercator.class; } projection = (Proj) projFactory.makeProjection(projClass, center, scale, width, height); } else { projection = (Proj) mapBean.getProjection(); projection.setCenter(center); 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) { Point2D upperLeft = new Point2D.Float(latmax, lonmin); Point2D lowerRight = new Point2D.Float(latmin, lonmax); scale = ProjMath .getScale(upperLeft, lowerRight, projection); projection.setScale(scale); Point2D ul = projection.getUpperLeft(); Point2D lr = projection.getLowerRight(); double factor1 = (latmax - latmin) / (ul.getY() - lr.getY()); double factor2 = (lonmax - lonmin) / (lr.getX() - ul.getX()); if (factor2 > factor1) factor1 = factor2; if (factor1 > 1.0) { scale *= factor1; projection.setScale(scale); } } mapBean.setProjection(projection); } } } }