// **********************************************************************
//
// <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/corba/com/bbn/openmap/layer/specialist/Specialist.java,v $
// $RCSfile: Specialist.java,v $
// $Revision: 1.3.2.1 $
// $Date: 2004/10/14 18:26:32 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.specialist;
import com.bbn.openmap.CSpecialist.*;
import com.bbn.openmap.util.corba.CORBASupport;
import com.bbn.openmap.util.Debug;
import java.util.*;
/**
* The Specialist is the base class for all specialists. It assists
* the developer in creating a specialist and sending back graphics to
* OpenMap(TM) or MATT. This class performs management of the client
* graphics for multiple clients, and there are functions in the class
* specifically designed to do that. These functions have not been
* declared final in order to allow you to extend their functionality,
* depending on the behavior you want in your specialist.
*
* <P>
* To create your own specialist, you need to create a class that
* inherits from this one. Then, you need to define fillRectangle,
* which loads the list of graphics to send back to the client. For
* threading reasons, you must maintain the list of graphics yourself.
* The GraphicList class can assist you in that. signOff() should be
* overloaded to receive information about which client have stopped
* using the specialist.
*
* <P>
* If you want a palette, define a new version of makePalette(), using
* addPalette() to add widgets to the palette widget list.
*
* <P>
* If you want gesture information, overload receiveGesture().
*
* <P>
* As for running a specialist, in the specialist main() function, you
* only need to create an instance of your specialist, and then call
* the start() function that is a part of this class. start(),
* parseArgs() and printHelp() all are written here for simple
* execution. If your specialist has more complicated options and
* help, you'll need to overload these functions.
*
* @see GraphicList
*/
public abstract class Specialist extends ServerPOA {
protected static String iorfile = null;
protected static String naming = null;
Dictionary clientPaletteLists;
Dictionary clientGestureActionLists;
protected Vector currentPaletteList;
protected Vector currentGestureActionList;
private short selectionDistance;
private boolean wantAreaEvents;
protected Vector graphicUpdates;
/**
* Default constructor. This is used to load specialist classes
* directly into the OpenMap VM.
*/
public Specialist() {
this("Default", (short) 0, false);
}
/** The argument to the constructor is the name of the specialist. */
public Specialist(String name) {
this(name, (short) 0, false);
}
public Specialist(String name, short sd, boolean wae) {
super();
clientPaletteLists = new Hashtable();
clientGestureActionLists = new Hashtable();
graphicUpdates = new Vector();
setSelectionDistance(sd);
setWantAreaEvents(wae);
}
/**
* This is the call for graphics that is made to the specialist.
* You should never do anything with this method, because it does
* the management of different clients for you. All of the
* client's <b>getRectangle </b> requests are forwarded to
* <b>fillRectangle </b>, which is the method you should override.
* <P>
*/
public UGraphic[] getRectangle(
CProjection p,
LLPoint llnw,
LLPoint llse,
String staticArgs,
org.omg.CORBA.StringHolder dynamicArgs,
org.omg.CORBA.ShortHolder graphicSelectableDistance,
org.omg.CORBA.BooleanHolder areaEvents,
GraphicChange notifyOnChange, String uniqueID) {
try {
UGraphic gl[] = fillRectangle(p,
llnw,
llse,
staticArgs,
dynamicArgs,
notifyOnChange,
uniqueID);
graphicSelectableDistance.value = selectionDistance;
areaEvents.value = wantAreaEvents;
return gl;
} catch (Throwable t) {
Debug.error("Specialist.getRectangle(): " + t);
t.printStackTrace();
throw new RuntimeException();
}
}
/**
* The signoff function lets the specialist know that a client is
* checking out. You should not override this method, because this
* is where the specialist cleans up lists that have been used
* internally to handle bookkeeping functions. <b>signoff </b>
* calls <b>signOff </b> (big O) which is the specialist-specific
* call to let a programmer clean up after a client.
*/
public void signoff(String uniqueID) {
signOff(uniqueID); // call to the specialist specific version
clientPaletteLists.remove(uniqueID);
}
/**
* getPaletteConfig is the idl call to get a palette. Palettes are
* now managed by the specialist internally, and the specialist
* only needs to implement <b>makePalette </b> instead. Shouldn't
* be modified or overridden.
*/
public UWidget[] getPaletteConfig(WidgetChange notifyOnChange,
String staticArgs,
org.omg.CORBA.StringHolder dynamicArgs,
String uniqueID) {
currentPaletteList = (Vector) clientPaletteLists.get(uniqueID);
if (currentPaletteList == null) {
currentPaletteList = new Vector();
clientPaletteLists.put(uniqueID, currentPaletteList);
}
makePalette(notifyOnChange, staticArgs, dynamicArgs, uniqueID);
return packPalette();
}
/*
* packPalette is used internally, to create the UWidget[] out of
* the Dictionary UWidgets.
*/
protected UWidget[] packPalette() {
int num_widgets = currentPaletteList.size();
UWidget[] widgets = new UWidget[num_widgets];
for (int i = 0; i < num_widgets; i++)
widgets[i] = (UWidget) currentPaletteList.elementAt(i);
return widgets;
}
/**
* addPalette adds a palette widget to the list of palette
* widgets. Should be called during <b>makePalette </b>.
*/
public void addPalette(UWidget uwidget) {
if (uwidget == null)
return;
currentPaletteList.addElement(uwidget);
}
/**
* Sets the number of elements in the current palette widget list
* to zero.
*/
public void clearPalette() {
currentPaletteList.removeAllElements();
}
/**
* <b>sendGesture </b> is the arrival point for gesture
* notifications. Again, this should not be overridden because
* there is bookkeeping going on. All requests are sent on to
* <b>receiveGesture </b>, which should be overridden.
*/
public ActionUnion[] sendGesture(MouseEvent gesture, String uniqueID) {
currentGestureActionList = (Vector) clientGestureActionLists.get(uniqueID);
if (currentGestureActionList == null) {
currentGestureActionList = new Vector();
clientGestureActionLists.put(uniqueID, currentGestureActionList);
}
graphicUpdates.removeAllElements();
currentGestureActionList.removeAllElements();
try {
receiveGesture(gesture, uniqueID);
} catch (Exception e) {
e.printStackTrace();
}
// Handle the graphics object updates as a special case,
// because
// the ActionUnion sequence can be made up of several objects.
// All of the other types of gesture responses should have
// been
// loaded via the helper functions below.
int num_graphicUpdates = graphicUpdates.size();
if (num_graphicUpdates > 0) {
UpdateRecord[] urs = new UpdateRecord[num_graphicUpdates];
for (int i = 0; i < num_graphicUpdates; i++)
urs[i] = (UpdateRecord) graphicUpdates.elementAt(i);
ActionUnion au = new ActionUnion();
au.ginfo(urs);
currentGestureActionList.addElement(au);
}
return packGestures();
}
protected ActionUnion[] packGestures() {
int num_actions = currentGestureActionList.size();
ActionUnion[] actions = new ActionUnion[num_actions];
for (int i = 0; i < num_actions; i++)
actions[i] = (ActionUnion) currentGestureActionList.elementAt(i);
return actions;
}
/**
* Sets the number of elements in the current gesture action union
* list to zero.
*/
public void clearGesture() {
currentGestureActionList.removeAllElements();
}
/**
* The <b>itext </b> string will appear in the information window
* of the client.
*/
public void addInfoText(String itext) {
ActionUnion ret = new ActionUnion();
ret.itext(itext);
currentGestureActionList.addElement(ret);
}
public void addPlainText(String ptext) {
ActionUnion ret = new ActionUnion();
ret.ptext(ptext);
currentGestureActionList.addElement(ret);
}
/** The HTML string should be HTML formatted. */
public void addHTMLText(String htext) {
ActionUnion ret = new ActionUnion();
ret.htext(htext);
currentGestureActionList.addElement(ret);
}
/**
* The URL string should also be formatted, as if you were passing
* it to a browser (which you are!).
*/
public void addURL(String url) {
ActionUnion ret = new ActionUnion();
ret.url(url);
currentGestureActionList.addElement(ret);
}
/**
* Get the UpdateRecord from the getGraphicUpdates method in the
* graphic that was updated. The <b>UpdateRecord </b> can be
* obtained by calling the <b>getGraphicUpdates </b> method of the
* graphic object.
*/
public void addGraphic(UpdateRecord ur) {
graphicUpdates.addElement(ur);
}
/**
* <b>fillRectangle </b> is the method that the specialist needs
* to overridden to add the graphics to the list to be sent back.
* The CProjection structure can provide the lat/lon center,
* height, width, projection type and scale of the screen of the
* client. <b>llnw </b> is the upper left coordinate of the
* screen, and <b>llse </b> is the south east. For an OpenMap or
* Matt client, <b>staticArgs </b> are defined in the overlay
* table, while <b>dynamicArgs </b> are defined for the specialist
* by the client.
*/
public abstract UGraphic[] fillRectangle(
CProjection p,
LLPoint llnw,
LLPoint llse,
String staticArgs,
org.omg.CORBA.StringHolder dynamicArgs,
GraphicChange notifyOnChange,
String uniqueID);
/**
* <b>receiveGesture </b> is the arrival point for gesture
* notifications. Use the calls defined above to add object
* updates, URLs, HTML text, etc, as return actions.
*
* @see #addInfoText
* @see #addPlainText
* @see #addHTMLText
* @see #addURL
* @see #addGraphic
*/
public void receiveGesture(MouseEvent gesture, String uniqueID) {}
/**
* <b>makePalette </b> is used to create the palette for the
* specialist. The palette widgets are: <b>SRadioButton,
* SCheckBox, SRadioButton, SButtonBox, SSlider, SListBox </b>.
* Add them to the palette using <b>addPalette </b>.
*
* @see #addPalette
*/
public void makePalette(WidgetChange notifyOnChange, String staticArgs,
org.omg.CORBA.StringHolder dynamicArgs,
String uniqueID) {}
protected void setWantAreaEvents(boolean setting) {
wantAreaEvents = setting;
}
protected void setSelectionDistance(short value) {
selectionDistance = value;
}
protected boolean getWantAreaEvents() {
return wantAreaEvents;
}
protected short getSelectionDistance() {
return selectionDistance;
}
/**
* SignOff is called when a client stops. Use this to clean up
* after a particular client.
*/
public abstract void signOff(String uniqueID);
/**
* This is a default start method that initializes the specialist,
* and handles the boa. It also handles parsing simple line
* options (-ior and -help). If your specialist needs more
* options, copy this function and add what you need. The args are
* command line arguments, and the ior filename is really being
* looked for here.
*/
public void start(String[] args) {
CORBASupport cs = new CORBASupport();
if (args != null) {
parseArgs(args);
}
cs.start(this, args, iorfile, naming);
}
/**
* <b>parseArgs </b> should reflect the needs of your specialist.
* It is presently defined to run with the default start() method,
* looking for -ior and -help strings.
*/
public void parseArgs(String[] args) {
for (int i = 0; i < args.length; i++) {
if (args[i].equalsIgnoreCase("-ior")) {
iorfile = args[++i];
} else if (args[i].equalsIgnoreCase("-name")) {
naming = args[++i];
} else if (args[i].equalsIgnoreCase("-help")) {
printHelp();
} else if (args[i].equalsIgnoreCase("-h")) {
printHelp();
}
}
// must specify an iorfile
if (iorfile == null && naming == null) {
Debug.error("IOR file and name service name are null! Use `-ior' or '-name' flag!");
}
}
/**
* <b>printHelp </b> should print a usage statement which reflects
* the command line needs of your specialist.
*/
public void printHelp() {
Debug.output("usage: java <specialist> [-ior <file> || -name <NAME>]");
System.exit(1);
}
}