// **********************************************************************
//
// <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/omGraphics/OMGraphicList.java,v $
// $RCSfile: OMGraphicList.java,v $
// $Revision: 1.22 $
// $Date: 2009/02/20 17:15:27 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.omGraphics;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
/**
* This class encapsulates a List of OMGraphics.
* <p>
* There are several things that this list does that make it better that any ol'
* List. You can make several common OMGraphic modification calls on the list,
* and the list handles the iteration and changing of all the graphics while
* taking into account a traverse order.
* <p>
* An additional benefit is that because the OMGraphicList extends OMGraphic it
* can contain other instances of OMGraphicList. This way you can manage
* groupings of graphics, (for instance, an OMGraphicList of OMGraphicLists
* which each have an OMRaster and OMText).
* <p>
* Many methods, such as generate() and findClosest() traverse the items in the
* GraphicsList recursively. The direction that the list is traversed is
* controlled by then traverseMode variable. The traverseMode mode lets you set
* whether the first or last object added to the list (FIRST_ADDED_ON_TOP or
* LAST_ADDED_ON_TOP) is drawn on top of the list and considered first for
* searches.
*/
public abstract class OMList<T extends OMGeometry> extends OMGraphicAdapter implements List<T>,
OMGraphic {
/**
* Used to set the order in which the list is traversed to draw or search
* the objects. This means that the last things on the list will be on top
* of the map because they are drawn last, on top of everything else. For
* searches, objects added last to the list will be considered first for a
* search match.
*/
public final transient static int LAST_ADDED_ON_TOP = 0;
/**
* Used to set the order in which the list is traversed to draw or search
* the objects. This means that the first things on the list will appear on
* top because they are drawn last, on top of everything else. For searches,
* objects added first to the list will be considered first for a search
* match. This is the default mode for the list.
*/
public final transient static int FIRST_ADDED_ON_TOP = 1;
/**
* Used for searches, when OMDist doesn't have a graphic. The index of a
* null graphic is NONE. If you try to remove or insert a graphic at NONE,
* an exception will be thrown. If you try to get a graphic at NONE, you'll
* get null;
*/
public final static int NONE = -1;
/**
* List traversal mode. The default is FIRST_ADDED_ON_TOP.
*/
protected int traverseMode = FIRST_ADDED_ON_TOP;
/**
* Flag to adjust behavior of OMGraphicList for certain queries. If
* OMGraphicList should act as OMGraphic, the entire list will be treated as
* one object. Otherwise, the list will act as a pass-through container, and
* internal OMGraphics will be returned. This applies to distance(),
* selectClosest(), findClosest(), getOMGraphicThatContains(), etc. This
* flag becomes really helpful for embedded OMGraphicLists, not so much for
* top-level OMGraphicLists.
*/
protected boolean vague = false;
/**
* Flag used to allow duplicates in the OMGraphicList. True by default -
* this prevents the list from doing the extra work for checking for
* duplicates at addition time.
*/
protected boolean allowDuplicates = true;
/**
* The List that actually contains the the OMGeometry/OMGraphic objects.
*/
protected List<T> graphics;
/**
* Construct an OMGraphicList.
*/
public OMList() {
graphics = Collections.synchronizedList(new ArrayList<T>());
}
public OMList(int initialCapacity) {
graphics = Collections.synchronizedList(new ArrayList<T>(initialCapacity));
}
/**
* OMGraphicList method for returning a simple description of the list. This
* is really a debugging method.
*/
public String getDescription() {
return getDescription(0);
}
/**
* OMGraphic method, for returning a simple description if the contents of
* the list. This method handles the spacing of sub-member descriptions.
* This is really a debugging method.
*
* @return String that represents the structure of the OMGraphicList.
*/
public String getDescription(int level) {
StringBuffer sb = new StringBuffer();
if (level > 0) {
sb.append("|--> ");
}
sb.append("OMList with ").append(size()).append(" object").append((size() == 1 ? "\n"
: "s\n"));
synchronized (graphics) {
StringBuffer sb1 = new StringBuffer();
for (int i = 0; i < level; i++) {
sb1.append(" ");
}
String spacer = sb1.toString();
String levelHeader = level == 0 ? "" : "|--> ";
for (OMGeometry omg : graphics) {
String description = "";
if (omg instanceof OMList<?>) {
description = ((OMList<? extends OMGeometry>) omg).getDescription(level + 1);
} else {
description = levelHeader + omg.getDescription();
}
sb.append(spacer).append(description).append("\n");
}
}
return sb.toString();
}
/**
* Set whether the list returns the specific OMGraphic in response to a
* query, or itself.
*/
public void setVague(boolean value) {
vague = value;
}
/**
* Get whether the list returns the specific OMGraphic in response to a
* query, or itself.
*/
public boolean isVague() {
return vague;
}
public Iterator<T> iterator() {
return getCopy().iterator();
}
public ListIterator<T> listIterator() {
return getCopy().listIterator();
}
public ListIterator<T> listIterator(int size) {
return getCopy().listIterator(size);
}
public List<T> subList(int fromIndex, int toIndex) {
return graphics.subList(fromIndex, toIndex);
}
public int size() {
return graphics.size();
}
public boolean isEmpty() {
return graphics.isEmpty();
}
public void clear() {
graphics.clear();
}
public int indexOf(Object o) {
return graphics.indexOf(o);
}
public int lastIndexOf(Object o) {
return graphics.lastIndexOf(o);
}
public boolean removeAll(Collection<?> c) {
boolean ret = false;
for (Object o : c) {
if (remove(o)) {
ret = true;
}
}
return ret;
}
public boolean retainAll(Collection<?> c) {
boolean ret = false;
for (Object o : c) {
if (!contains(o)) {
remove(o);
ret = true;
}
}
return ret;
}
public boolean contains(Object o) {
boolean ret = false;
if (o != null) {
synchronized (graphics) {
for (T omg : graphics) {
if (o == omg
|| (omg instanceof OMList<?> && ((OMList<? extends OMGeometry>) omg).contains(o))) {
ret = true;
break;
}
}
}
}
return ret;
}
public boolean containsAll(Collection<?> c) {
for (Object o : c) {
if (!contains(o)) {
return false;
}
}
return true;
}
/**
* Add an OMGraphic to the list.
*/
public boolean add(T g) {
checkForDuplicate(g);
return graphics.add(g);
}
public void add(int index, T g) {
checkForDuplicate(g);
graphics.add(index, g);
}
public Object[] toArray() {
return graphics.toArray();
}
public <E> E[] toArray(E[] a) {
return graphics.toArray(a);
}
/**
* Set the order in which the list is traversed to draw or search the
* objects. The possible modes for the list are FIRST_ADDED_ON_TOP or
* LAST_ADDED_ON_TOP.
*
* @param mode traversal mode
*/
public void setTraverseMode(int mode) {
traverseMode = mode;
}
/**
* Get the order in which the list is traversed to draw or search the
* objects. The possible modes for the list are FIRST_ADDED_ON_TOP or
* LAST_ADDED_ON_TOP.
*
* @return int traversal mode
*/
public int getTraverseMode() {
return traverseMode;
}
/**
* Remove the graphic. If this list is not vague, it will also ask
* sub-OMGraphicLists to remove it if the geometry isn't found on this
* OMGraphicList.
*
* @param geometry the object to remove.
* @return true if geometry was on the list, false if otherwise.
*/
public boolean remove(Object geometry) {
boolean found = false;
synchronized (graphics) {
found = graphics.remove(geometry);
if (!found && !isVague()) {
for (OMGeometry graphic : graphics) {
if (graphic instanceof OMList<?>) {
found = ((OMList<? extends OMGeometry>) graphic).remove(geometry);
}
}
}
}
return found;
}
public T remove(int index) {
return graphics.remove(index);
}
/**
* @return an unmodifiable copy of this list.
*/
public final List<T> getCopy() {
final List<T> listCopy;
listCopy = new ArrayList<T>(graphics);
return Collections.unmodifiableList(listCopy);
}
/**
* Moves the graphic at the given index to the part of the list where it
* will be drawn on top of one of the other graphics which is its neighbor
* on the list. This method does check to see what the traverseMode of the
* list is, and calls either moveIndexedToLast or moveIndexedToFirst,
* depending on what is appropriate.
*
* @param location the index location of the graphic to move.
* @see #moveIndexedOneToFront(int)
* @see #moveIndexedOneToBack(int)
*/
public void moveIndexedOneToTop(int location) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
moveIndexedOneToFront(location);
} else {
moveIndexedOneToBack(location);
}
}
/**
* Moves the graphic at the given index to the part of the list where it
* will be drawn on top of the other graphics. This method does check to see
* what the traverseMode of the list is, and calls either moveIndexedToLast
* or moveIndexedToFirst, depending on what is appropriate.
*
* @param location the index location of the graphic to move.
*/
public void moveIndexedToTop(int location) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
moveIndexedToFirst(location);
} else {
moveIndexedToLast(location);
}
}
/**
* Moves the graphic at the given index to the part of the list where it
* will be drawn under one of the other graphics, its neighbor on the list.
* This method does check to see what the traverseMode of the list is, and
* calls either moveIndexedOneToBack or moveIndexedOneToFront, depending on
* what is appropriate.
*
* @param location the index location of the graphic to move.
* @see #moveIndexedOneToFront(int)
* @see #moveIndexedOneToBack(int)
*/
public void moveIndexedOneToBottom(int location) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
moveIndexedOneToBack(location);
} else {
moveIndexedOneToFront(location);
}
}
/**
* Moves the graphic at the given index to the part of the list where it
* will be drawn under all of the other graphics. This method does check to
* see what the traverseMode of the list is, and calls either
* moveIndexedToLast or moveIndexedToFirst, depending on what is
* appropriate.
*
* @param location the index location of the graphic to move.
*/
public void moveIndexedToBottom(int location) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
moveIndexedToLast(location);
} else {
moveIndexedToFirst(location);
}
}
/**
* Moves the graphic at the given index to the front of the list, sliding
* the other graphics back on in the list in order. If the location is
* already at the beginning or beyond the end, nothing happens.
*
* @param location the index of the graphic to move.
* @see #moveIndexedToBottom(int)
* @see #moveIndexedToTop(int)
*/
public void moveIndexedToFirst(int location) {
int listSize = size();
if (location > 0 && location < listSize) {
T tmpGraphic = get(location);
for (int i = location; i > 0; i--) {
set(i, get(i - 1));
}
graphics.set(0, tmpGraphic);
}
}
/**
* Moves the graphic at the given index toward the front of the list by one
* spot, sliding the other graphic back on in the list in order. If the
* location is already at the beginning or beyond the end, nothing happens.
*
* @param location the index of the graphic to move.
*/
public void moveIndexedOneToFront(int location) {
int listSize = size();
if (location > 0 && location < listSize) {
T tmpGraphic = get(location);
graphics.set(location, get(location - 1));
graphics.set(location - 1, tmpGraphic);
}
}
/**
* Moves the graphic at the given index to the end of the list, sliding the
* other graphics up on in the list in order. If the location is already at
* the end or less than zero, nothing happens.
*
* @param location the index of the graphic to move.
* @see #moveIndexedToBottom(int)
* @see #moveIndexedToTop(int)
*/
public void moveIndexedToLast(int location) {
int listSize = size();
if (location < listSize - 1 && location >= 0) {
T tmpGraphic = get(location);
for (int i = location; i < listSize - 1; i++) {
set(i, get(i + 1));
}
graphics.set(listSize - 1, tmpGraphic);
}
}
/**
* Moves the graphic at the given index toward the back of the list by one
* spot, sliding the other graphic up on in the list in order. If the
* location is already at the end or less than zero, nothing happens.
*
* @param location the index of the graphic to move.
*/
public void moveIndexedOneToBack(int location) {
int listSize = size();
if (location < listSize - 1 && location >= 0) {
T tmpGraphic = get(location);
graphics.set(location, get(location + 1));
graphics.set(location + 1, tmpGraphic);
}
}
/**
* Projects any graphics needing projection. Use this method to project any
* new or changed OMGraphics before painting. to re-project the whole list,
* use <code>generate(Projection, boolean)</code> with
* <code>forceProjectAll</code> set to <code>true</code>. This is the same
* as calling <code> generate(p, false)</code>
*
* @param p a <code>Projection</code>
* @see #generate(Projection, boolean)
*/
public void project(Projection p) {
generate(p, false);
}
/**
* Projects the OMGraphics on the list. This is the same as calling
* <code>generate(p, forceProjectAll)</code>.
*
* @param p a <code>Projection</code>
* @param forceProjectAll if true, all the graphics on the list are
* generated with the new projection. If false they are only
* generated if getNeedToRegenerate() returns true
* @see #generate(Projection, boolean)
*/
public void project(Projection p, boolean forceProjectAll) {
generate(p, forceProjectAll);
}
/**
* Prepare the graphics for rendering. This is the same as calling
* <code>project(p, true)</code>.
*
* @param p a <code>Projection</code>
* @return boolean true
* @see #generate(Projection, boolean)
*/
public boolean generate(Projection p) {
return generate(p, true);
}
/**
* Prepare the graphics for rendering. This must be done before calling
* <code>render()</code>! This recursively calls generate() on the
* OMGraphics on the list.
*
* @param p a <code>Projection</code>
* @param forceProjectAll if true, all the graphics on the list are
* generated with the new projection. If false they are only
* generated if getNeedToRegenerate() returns true
* @return true if generation was successful for all objects on list.
* @see OMGraphic#generate
* @see OMGraphic#regenerate
*/
public boolean generate(Projection p, boolean forceProjectAll) {
boolean ret = true;
synchronized (graphics) {
Iterator<T> iterator = iterator();
// Check forceProjectAll outside the loop for slight
// performance improvement.
if (forceProjectAll) {
while (iterator.hasNext()) {
ret &= iterator.next().generate(p);
}
} else {
while (iterator.hasNext()) {
ret &= iterator.next().regenerate(p);
}
}
}
return ret;
}
/**
* Renders all the objects in the list a graphics context. This is the same
* as <code>paint()</code> for AWT components. The graphics are rendered in
* the order of traverseMode. Any graphics where <code>isVisible()</code>
* returns false are not rendered.
*
* @param gr the AWT Graphics context
*/
public void render(Graphics gr) {
if (isVague() && !isVisible())
return;
synchronized (graphics) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator(size());
while (iterator.hasPrevious()) {
OMGeometry graphic = iterator.previous();
if (shouldProcess(graphic)) {
graphic.render(gr);
}
}
} else {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
OMGeometry graphic = iterator.next();
if (shouldProcess(graphic)) {
graphic.render(gr);
}
}
}
}
renderLabel(gr);
}
/**
* Renders all the objects in the list a graphics context, in their
* 'selected' mode. This is the same as <code>paint()</code> for AWT
* components. The graphics are rendered in the order of traverseMode. Any
* graphics where <code>isVisible()</code> returns false are not rendered.
* All of the graphics on the list are returned to their deselected state.
*
* @param gr the AWT Graphics context
*/
public void renderAllAsSelected(Graphics gr) {
synchronized (graphics) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator(size());
while (iterator.hasPrevious()) {
OMGeometry graphic = iterator.previous();
if (shouldProcess(graphic)) {
graphic.select();
graphic.render(gr);
graphic.deselect();
}
}
} else {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
OMGeometry graphic = iterator.next();
if (shouldProcess(graphic)) {
graphic.select();
graphic.render(gr);
graphic.deselect();
}
}
}
}
}
/**
* Override flag for shouldProcess method. The setting will override the
* OMGraphicList from using the OMGraphic's visibility settings in
* determining which OMGraphics should be used in different distance,
* generate and render methods.
*/
protected boolean processAllGeometries = true;
/**
* This method is called internally for those methods where skipping
* invisible OMGeometries would save processing time and effort. If you
* don't want visibility to be considered when processing
* OMGeometries/OMGraphics, override this method and return true.
*/
protected boolean shouldProcess(OMGeometry graphic) {
return processAllGeometries || graphic.isVisible();
}
/**
* Set the programmatic override for shouldProcess method to always process
* geometries.
*/
public void setProcessAllGeometries(boolean set) {
processAllGeometries = set;
}
/**
* Get the settings for the programmatic override for shouldProcess method
* to always process geometries.
*/
public boolean getProcessAllGeometries() {
return processAllGeometries;
}
/**
* Finds the distance to the closest OMGraphic.
*
* @param x x coordinate
* @param y y coordinate
* @return float distance
* @see #findClosest(double, double, float)
*/
public float distance(double x, double y) {
return findClosest(x, y, Float.MAX_VALUE, false).d;
}
/**
* RetVal for closest object/distance calculations.
*/
protected static class OMDist<T> {
public T omg = null;
public float d = Float.POSITIVE_INFINITY;
public int index = NONE; // unknown
public String toString() {
return "OMDist: omg=" + (omg == null ? "null" : omg.getClass().getName()) + ", d=" + d
+ ", index=" + index;
}
}
protected abstract OMDist<T> createDist();
/**
* Find the closest Object and its distance. The search is always conducted
* from the topmost graphic to the bottom-most, depending on the
* traverseMode.
*
* @param x x coordinate
* @param y y coordinate
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @param resetSelect deselect any OMGraphic touched.
* @return OMDist
*/
public OMDist<T> findClosest(double x, double y, float limit, boolean resetSelect) {
OMDist<T> omd = new OMDist<T>();
OMDist<T> tomd;
int i;
synchronized (graphics) {
if (!isEmpty()) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
i = 0;
ListIterator<T> iterator = graphics.listIterator();
while (iterator.hasNext()) {
tomd = findClosestTest(omd, i++, iterator.next(), x, y, limit, resetSelect);
if (tomd == null)
continue;
omd = tomd; // for style
if (omd.d == 0)
break;
}
} else {
i = size();
ListIterator<T> iterator = graphics.listIterator(i);
while (iterator.hasPrevious()) {
tomd = findClosestTest(omd, i--, iterator.previous(), x, y, limit, resetSelect);
if (tomd == null)
continue;
omd = tomd; // for style
if (omd.d == 0)
break;
}
}
}
}
if (Debug.debugging("omgraphics")) {
int size = size();
if (omd.omg != null && isVague()) {
omd.omg = (T) this;
Debug.output(this.getClass().getName() + "(" + size
+ ") detecting hit and vagueness, returning " + omd);
} else if (omd.omg != null && !isVague()) {
Debug.output(this.getClass().getName() + "(" + size
+ ") detecting hit, no vagueness, returning contained " + omd);
} else {
Debug.output(this.getClass().getName() + "(" + size + ") omd.omg "
+ (omd.omg == null ? "== null" : "!= null"));
}
}
return omd;
}
/**
* Test the graphic distance away from the x, y point, and compare it to the
* current OMDist passed in. If the graphic is the new closest, return the
* same OMDist object filled in with the new value. Otherwise, return null.
*
* @param current the OMDist that contains the current best result of a
* search.
* @param index the index in the graphic list of the provided OMGraphic
* @param graphic the OMGraphic to test
* @param x the window horizontal pixel value.
* @param y the window vertical pixel value.
* @param resetSelect flag to call deselect on any OMGraphic contacted. Used
* here to pass on in case the OMGraphic provided is an
* OMGraphicList, and to use to decide if deselect should be called
* on the provided graphic.
* @return OMDist with an OMGraphic if the graphic passed in is the current
* closest. OMDist.graphic could be null, OMDist.d could be
* Infinity.
*/
protected OMDist<T> findClosestTest(OMDist<T> current, int index, OMGeometry graphic, double x,
double y, float limit, boolean resetSelect) {
if (current == null) {
current = createDist();
}
OMList<? extends OMGeometry> omgl;
float currentDistance = Float.MAX_VALUE;
// cannot select a graphic which isn't visible
if (!shouldProcess(graphic)) {
return current;
}
if (graphic instanceof OMList<?>) {
omgl = (OMList<T>) graphic;
OMDist<T> dist = (OMDist<T>) omgl.findClosest(x, y, limit, resetSelect);
if (dist.omg != null) {
currentDistance = dist.d;
graphic = dist.omg;
}
} else {
if (resetSelect)
graphic.deselect();
currentDistance = graphic.distance(x, y);
}
if (currentDistance < limit && currentDistance < current.d) {
if (!isVague()) {
current.omg = (T) graphic;
} else {
current.omg = (T) this;
}
current.index = index;
current.d = currentDistance;
}
return current;
}
/**
* Finds the object located the closest to the point, if the object distance
* away is within the limit. The search is always conducted from the topmost
* graphic to the bottom-most, depending on the traverseMode. Any graphics
* where <code>isVisible()</code> returns false are not considered.
*
* @param x the x coordinate on the component the graphics are displayed on.
* @param y the y coordinate on the component the graphics are displayed on.
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @return OMGraphic the closest on the list within the limit, or null if
* not found.
*/
public T findClosest(double x, double y, float limit) {
return findClosest(x, y, limit, false).omg;
}
/**
* Find all of the OMGraphics on this list that are located within the pixel
* limit of the x, y pixel location.
*
* @param x the x coordinate on the component the graphics are displayed on.
* @param y the y coordinate on the component the graphics are displayed on.
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @return OMGraphicList containing all of the OMGraphics within the limit.
*/
public OMList<T> findAll(int x, int y, float limit) {
return findAll(x, y, limit, false, null);
}
/**
* Find all of the OMGraphics on this list that are located within the pixel
* limit of the x, y pixel location.
*
* @param x the x coordinate on the component the graphics are displayed on.
* @param y the y coordinate on the component the graphics are displayed on.
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @param resetSelect call deselect on OMGraphics not within limit.
* @param addTo OMGraphicList to add found OMGraphics to, if null a list
* will be created.
* @return OMGraphicList containing all of the OMGraphics within the limit,
* empty if none are found.
*/
public OMList<T> findAll(int x, int y, float limit, boolean resetSelect,
OMList<T> addTo) {
if (addTo == null) {
addTo = create();
}
OMDist<T> omd = createDist();
if (!isEmpty()) {
synchronized (graphics) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
if (!findAllTest(x, y, limit, resetSelect, addTo, iterator.next(), omd)) {
break;
}
}
} else {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator(size());
while (iterator.hasPrevious()) {
if (!findAllTest(x, y, limit, resetSelect, addTo, iterator.previous(), omd)) {
break;
}
}
}
}
}
if (Debug.debugging("omgraphics")) {
Debug.output(this.getClass().getName() + "(" + size()
+ ") detecting hits and vagueness, returning list with " + addTo.size()
+ " graphics.");
}
return addTo;
}
public abstract OMList<T> create();
/**
* Test to find out if an OMGraphic is located within the pixel limit of the
* x, y pixel location.
*
* @param x the x coordinate on the component the graphics are displayed on.
* @param y the y coordinate on the component the graphics are displayed on.
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @param resetSelect call deselect on OMGraphic not within limit.
* @param addTo OMGraphicList to add found OMGeometries to, if null a list
* will be created.
* @param geometry OMGraphic to test.
* @param omd OMDist to use for test, provided to avoid recurring memory
* allocations for loops.
* @return true of this method should still be called again in a loop, false
* of this list is vague and we have a hit.
*/
protected boolean findAllTest(int x, int y, float limit, boolean resetSelect, OMList<T> addTo,
OMGeometry geometry, OMDist<T> omd) {
if (geometry instanceof OMList<?>) {
OMList<T> tempList = create();
((OMList<T>) geometry).findAll(x, y, limit, resetSelect, tempList);
if (!tempList.isEmpty()) {
if (isVague()) {
addTo.add((T) this);
// Vague with hit, no need to check further on this list...
return false;
} else {
addTo.addAll(tempList);
// Move on to check next T on this list...
return true;
}
}
} else {
omd = findClosestTest(omd, 0 /* doesn't matter */, geometry, x, y, limit, resetSelect);
if (omd == null || omd.omg == null) {
// no hit, but continue testing...
return true;
}
// Measurements passed, add OMGraphic to addTo list and
// continue
if (isVague()) {
addTo.add((T) this);
return false;
}
addTo.add(omd.omg);
omd.d = Float.MAX_VALUE; // reset for next OMGraphic
omd.omg = null;
}
return true;
}
/**
* Finds the object located the closest to the point, regardless of how far
* away it is. This method returns null if the list is not valid. The search
* starts at the first-added graphic. <br>
* This is the same as calling
* <code>findClosest(x, y, Float.MAX_VALUE)</code>.
*
* @param x the horizontal pixel position of the window, from the left of
* the window.
* @param y the vertical pixel position of the window, from the top of the
* window.
* @return the closest graphic to the xy window point.
* @see #findClosest(double, double, float)
*/
public T findClosest(int x, int y) {
return findClosest(x, y, Float.MAX_VALUE);
}
/**
* Finds the object located the closest to the point, if the object distance
* away is within the limit. The search is always conducted from the topmost
* graphic to the bottom-most, depending on the traverseMode. Any graphics
* where <code>isVisible()</code> returns false are not considered.
*
* @param x the x coordinate on the component the graphics are displayed on.
* @param y the y coordinate on the component the graphics are displayed on.
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @return index of the closest on the list within the limit, or
* OMGraphicList.NONE if not found.
*/
public int findIndexOfClosest(int x, int y, float limit) {
return findClosest(x, y, limit, false).index;
}
/**
* Finds the object located the closest to the point, regardless of how far
* away it is. This method returns null if the list is not valid. The search
* starts at the first-added graphic. <br>
* This is the same as calling
* <code>findClosest(x, y, Float.MAX_VALUE)</code>.
*
* @param x the horizontal pixel position of the window, from the left of
* the window.
* @param y the vertical pixel position of the window, from the top of the
* window.
* @return index of the closest graphic to the xy window point, or
* OMGraphicList.NONE if not found.
* @see #findIndexOfClosest(int, int, float)
*/
public int findIndexOfClosest(int x, int y) {
return findClosest(x, y, Float.MAX_VALUE, false).index;
}
/**
* Finds the object located the closest to the coordinates, regardless of
* how far away it is. Sets the select paint of that object, and resets the
* paint of all the other objects. The search starts at the first-added
* graphic.
*
* @param x the x coordinate on the component the graphics are displayed on.
* @param y the y coordinate on the component the graphics are displayed on.
* @return the closest OMGraphic on the list, with selected having been
* called on that OMGraphics. This OMGraphic will be within the
* limit or null if none found. Will return this list if this list
* is set to be vague.
*/
public T selectClosest(int x, int y) {
return selectClosest(x, y, Float.MAX_VALUE);
}
/**
* Finds the object located the closest to the point, if the object distance
* away is within the limit, and sets the paint of that graphic to its
* select paint. It sets the paints to all the other objects to the regular
* paint. The search starts at the first-added graphic. Any graphics where
* <code>isVisible()</code> returns false are not considered.
*
* @param x the horizontal pixel position of the window, from the left of
* the window.
* @param y the vertical pixel position of the window, from the top of the
* window.
* @param limit the max distance that a graphic has to be within to be
* returned, in pixels.
* @return the closest OMGraphic on the list, with selected having been
* called on that OMGraphics. This OMGraphic will be within the
* limit or null if none found. Will return this list if this list
* is set to be vague.
*/
public T selectClosest(int x, int y, float limit) {
OMDist<T> omd = null;
OMDist<T> tomd;
T ret = null;
// Handle vagueness.
if (isVague()) {
omd = findClosest(x, y, limit, true);
if (omd != null) {
select();
return (T) this;
}
}
synchronized (graphics) {
if (!isEmpty()) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
tomd = selectClosestTest(omd, 0, iterator.next(), x, y, limit);
if (tomd == null)
continue;
omd = tomd; // for style
if (omd.d == 0)
break;
}
} else {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator(size());
while (iterator.hasPrevious()) {
tomd = selectClosestTest(omd, 0, iterator.previous(), x, y, limit);
if (tomd == null)
continue;
omd = tomd; // for style
if (omd.d == 0)
break;
}
}
}
}
if (omd != null) {
ret = omd.omg;
}
return ret;
}
/**
* A variation on findClosestTest, manages select() and deselect().
*
* @param current the OMDist that contains the current best result of a
* search.
* @param index the index in the graphic list of the provided OMGraphic
* @param graphic the OMGraphic to test
* @param x the window horizontal pixel value.
* @param y the window vertical pixel value.
* @return OMDist if the graphic passed in is the current closest. OMGraphic
* will be set in OMDist and selected(). OMGraphic will be
* de-selected if not the closest, and the OMDist will be null. This
* method will return this list if it is set to be vague and one of
* its children meet the criteria.
*/
protected OMDist<T> selectClosestTest(OMDist<T> current, int index, OMGeometry graphic, int x,
int y, float limit) {
if (current == null) {
current = createDist();
}
T oldGraphic = current.omg;
OMDist<T> ret = findClosestTest(current, index, graphic, x, y, limit, true);
// Test for the OMDist still holding the same OMGraphicList,
// which will be the case if this list is vague. The distance
// will be updated, though.
if (ret != null && oldGraphic != ret.omg) {
if (oldGraphic != null) {
oldGraphic.deselect();
}
ret.omg.select();
}
return ret;
}
/**
* Finds the first OMGraphic (the one on top) that is under this pixel. If
* an OMGraphic is an OMGraphicList, its contents will be checked. If that
* check is successful and OMGraphicList is not vague, its OMGraphic will be
* returned - otherwise the list will be returned.
*
* @param x the horizontal pixel position of the window, from the left of
* the window.
* @param y the vertical pixel position of the window, from the top of the
* window.
* @return the graphic that contains the pixel, NONE (null) if none are
* found.
*/
public T getContains(int x, int y) {
T ret = null;
synchronized (graphics) {
if (!isEmpty()) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
OMGeometry graphic = iterator.next();
// cannot select a graphic which isn't visible
if (!shouldProcess(graphic))
continue;
if (graphic instanceof OMList<?>) {
OMList<? extends OMGeometry> tomgl = (OMList<? extends OMGeometry>) graphic;
ret = (T) tomgl.getContains(x, y);
if (ret != null) {
if (tomgl.isVague()) {
ret = (T) graphic;
}
break;
}
} else if (graphic.contains(x, y)) {
ret = (T) graphic;
break;
}
}
} else {
ListIterator<? extends OMGeometry> iterator = graphics.listIterator(size());
while (iterator.hasPrevious()) {
OMGeometry graphic = iterator.previous();
// cannot select a graphic which isn't visible
if (!shouldProcess(graphic))
continue;
if (graphic instanceof OMList<?>) {
OMList<? extends OMGeometry> tomgl = (OMList<? extends OMGeometry>) graphic;
ret = (T) tomgl.getContains(x, y);
if (ret != null) {
if (tomgl.isVague()) {
ret = (T) graphic;
}
break;
}
} else if (graphic.contains(x, y)) {
ret = (T) graphic;
break;
}
}
}
}
}
if (ret != null && this.isVague()) {
ret = (T) this;
}
return ret;
}
/**
* If you call deselect() on an OMGraphicList, it calls deselect() all the
* graphics it contains, as well as the deselect method on it's super class.
*/
public void deselect() {
super.deselect();
synchronized (graphics) {
for (OMGeometry omg : graphics) {
omg.deselect();
}
}
}
/**
* Calls select() on all the items on the graphic list, as well as select()
* on the super class.
*/
public void select() {
super.select();
synchronized (graphics) {
for (OMGeometry omg : graphics) {
omg.select();
}
}
}
/**
* Perform an action on the provided geometry. If the geometry is not
* currently on the list, it is added (if the action doesn't say to delete
* it). If the geometry is null, the list checks for an action to take on
* the list (deselectAll).
*/
public void doAction(T graphic, OMAction action) {
Debug.message("omgl", "OMList.doAction()");
if (graphic == null) {
return;
}
int i = indexOf(graphic);
boolean alreadyOnList = (i != -1);
if (action == null || action.getValue() == 0 && !alreadyOnList) {
Debug.message("omgl", "OMGraphicList.doAction: adding graphic with null action");
add(graphic);
return;
}
if (action.isMask(ADD_GRAPHIC_MASK) || action.isMask(UPDATE_GRAPHIC_MASK) && !alreadyOnList) {
Debug.message("omgl", "OMGraphicList.doAction: adding graphic");
add(graphic);
}
if (action.isMask(DELETE_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: removing graphic");
remove(graphic);
}
if (action.isMask(RAISE_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: raising graphic");
moveIndexedOneToTop(i);
}
if (action.isMask(RAISE_TO_TOP_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: raising graphic to top");
moveIndexedToTop(i);
}
if (action.isMask(LOWER_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: lowering graphic");
moveIndexedOneToBottom(i);
}
if (action.isMask(LOWER_TO_BOTTOM_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: lowering graphic to bottom");
moveIndexedOneToBottom(i);
}
if (action.isMask(DESELECTALL_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: deselecting all graphics.");
deselect();
}
if (action.isMask(SORT_GRAPHICS_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: sorting the list");
sort();
}
if (action.isMask(SELECT_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: selecting graphic");
graphic.select();
}
if (action.isMask(DESELECT_GRAPHIC_MASK)) {
Debug.message("omgl", "OMGraphicList.doAction: deselecting graphic");
graphic.deselect();
}
}
/**
* Set the visibility variable. NOTE: <br>
* This is checked by the OMGraphicList when it iterates through its list
* for render and gesturing. It is not checked by the internal OMGraphic
* methods, although maybe it should be...
*
* @param visible boolean
*/
public void setVisible(boolean visible) {
super.setVisible(visible);
if (!isVague()) {
synchronized (graphics) {
for (Iterator<? extends OMGeometry> it = iterator(); it.hasNext();) {
it.next().setVisible(visible);
}
}
}
}
/**
* Get the visibility variable. For the OMGraphicList, if any part of it is
* visible, then it is considered visible.
*
* @return boolean
*/
public boolean isVisible() {
if (!isVague()) {
synchronized (graphics) {
for (T omg : graphics) {
if (omg.isVisible()) {
return true;
}
}
}
return false;
} else {
return super.isVisible();
}
}
/**
* Set whether the list will allow duplicate entries added. If not, then the
* copy will be added, and the previous version removed.
*/
public void setAllowDuplicates(boolean set) {
allowDuplicates = set;
}
/**
* Get whether the list will allow duplicate entries added. If not, then the
* copy will be added, and the previous version removed.
*/
public boolean getAllowDuplicates() {
return allowDuplicates;
}
/**
* Convenience function for methods that may add a OMGraphic. Method checks
* to see if duplicates are allowed, and if they are not, it will remove the
* OMGraphic from the list. The calling method can be confident that it will
* be adding a unique OMGraphic. Internal methods that call this method
* should be synchronized on the graphics list.
*/
protected void checkForDuplicate(T g) {
if (!allowDuplicates) {
// Why check first, just remove it if it's found?!
// if (graphics.contains(g)) {
remove(g);
// }
}
}
/**
* Checks if an OMGraphic is on this list. Checks sublists, too.
*/
public boolean contains(OMGraphic g) {
if (g != null) {
synchronized (graphics) {
for (OMGeometry omg : graphics) {
if (g == omg
|| (omg instanceof OMList<?> && ((OMList<? extends OMGeometry>) omg).contains(g))) {
return true;
}
}
}
}
return false;
}
/**
* This sort method is a place-holder for OMGraphicList extensions to
* implement their own particular criteria for sorting an OMGraphicList.
* Does nothing for a generic OMGraphicList.
*/
public void sort() {
}
/**
* Convenience method to cast an object to an OMGraphic if it is one.
* Returns null if it isn't.
*/
protected OMGraphic objectToOMGraphic(Object obj) {
if (obj instanceof OMGraphic) {
return (OMGraphic) obj;
} else {
return null;
}
}
/**
* You need to make sure that the Generic type of the source matches the
* generic type of this list. Will fail silently. Not sure if this is the
* right way to handle it, though.
*/
public void restore(OMGeometry source) {
super.restore(source);
try {
if (source instanceof OMList<?>) {
OMList<T> list = (OMList<T>) source;
for (T omg : list) {
T newCopy = (T) ComponentFactory.create(omg.getClass().getName());
if (newCopy != null) {
newCopy.restore(source);
}
}
}
} catch (ClassCastException cce) {
// Should really check better. Duh.
}
}
}