/*
* (C) 2004 - Geotechnical Software Services
*
* This code is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
package no.geosoft.cc.graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.Collection;
import no.geosoft.cc.geometry.Region;
import no.geosoft.cc.geometry.Rect;
import no.geosoft.cc.geometry.Box;
/**
* Class representing a graphical object. The representation is
* by actual geometry (GSegments) or in terms of sub objects (or both).
* <p>
* Typically GObject is extended and its draw() method is overloaded:
*
* <pre>
* public class ClientGraphicsObject extends GObject
* {
* public ClientGraphicsObject()
* {
* // Prepare the graphics object here, i.e. add the
* // GSegments and sub-GObjects it consists of, and
* // set rendering style (GStyle) on the elements
* // and add annotations (GText) and images (GImage).
* }
*
* public void draw()
* {
* // Set geometry for all containing GSegments.
* }
* }
* </pre>
*
* A top level GObject is inserted into the scene like this:
*
* <pre>
* GScene scene = new GScene (window);
*
* GObject object = new ClientGraphicsObject();
* scene.add (object);
* </pre>
*
* @author <a href="mailto:info@geosoft.no">GeoSoft</a>
*/
public class GObject
implements GStyleListener
{
private static final int FORWARD = -2;
private static final int BACKWARD = -3;
public static final int DATA_VISIBLE = 1;
public static final int ANNOTATION_VISIBLE = 2;
public static final int SYMBOLS_VISIBLE = 4;
public static final int WIDGETS_VISIBLE = 8;
public static final int VISIBLE = 15;
public static final int DATA_INVISIBLE = 16;
public static final int ANNOTATION_INVISIBLE = 32;
public static final int SYMBOLS_INVISIBLE = 64;
public static final int WIDGETS_INVISIBLE = 128;
public static final int INVISIBLE = 240;
private String name_;
private Region region_; // Including all children
private boolean isRegionValid_; // Has it been computed?
private GObject parent_; // Immediate ancestor
private int visibilityMask_;
private List children_; // of GObject
private GStyle style_; // As specified by application
private GStyle actualStyle_; // Adjusted for inherits
private List segments_; // of GSegment
private boolean isDrawn_;
private Object userData_; // Application defined
/**
* Create a graphical object with specified name.
*
* @param name Name of object.
*/
public GObject (String name)
{
name_ = name;
parent_ = null;
region_ = new Region();
children_ = new ArrayList();
isRegionValid_ = true;
segments_ = null;
visibilityMask_ = VISIBLE;
isDrawn_ = false;
style_ = null;
actualStyle_ = new GStyle();
}
/**
* Create a new unnamed graphic object.
*/
public GObject()
{
this (null);
}
/**
* Return name of this graphic object.
*
* @return Name of this graphic object.
*/
public String getName()
{
return name_;
}
/**
* Set name of this graphic object.
*
* @param name New name of this graphic object.
*/
public void setName (String name)
{
name_ = name;
}
/**
* Return scene of this graphic object. The scene defines the
* world-to-device transformation.
*
* @return Scene of this graphic object (or null if the object is not
* attached to a scene).
*/
public GScene getScene()
{
return parent_ != null ? getParent().getScene() : null;
}
/**
* Conveience method for getting the transformation object of the
* scene of this object.
*
* @return Transformation object for this graphic object (or null if the
* object is not attached to a scene).
*/
public GTransformer getTransformer()
{
GScene scene = getScene();
return scene != null ? scene.getTransformer() : null;
}
/**
* Return the window of this graphic object.
*
* @return Window of this graphic object (or null if this object is
* not attached to a window).
*/
public GWindow getWindow()
{
GScene scene = getScene();
return scene != null ? scene.getWindow() : null;
}
/**
* Return the region of this graphic object. The region is a union of
* all children objects and the geometry of this object.
*
* @return The region of this graphic object.
*/
Region getRegion()
{
return region_;
}
/**
* Return all segments of this graphic object.
*
* @return All segments of this object (or null if none).
*/
public List getSegments()
{
return segments_;
}
/**
* Return first segment of this graphic object. Convenient if the caller
* knows that it contains exactly one segment.
*
* @return First segment of this graphics object (or null if none).
*/
public GSegment getSegment()
{
return segments_ != null && segments_.size() > 0 ?
(GSegment) segments_.get(0) : null;
}
/**
* Return the current visibility mask for this object.
* @see #setVisibility(int)
*
* @return Current visibility of this object.
*/
public int getVisibility()
{
return visibilityMask_;
}
/**
* Set user data of this graphics object.
*
* @param userData User data of this graphics object.
*/
public void setUserData (Object userData)
{
userData_ = userData;
}
/**
* Return user data of this graphics object.
*
* @return User data of this graphics object.
*/
public Object getUserData()
{
return userData_;
}
/**
* Find sub object based on user data tag. Search from this (included)
* and in entire sub tree, depth first.
*
* @param userData User data of child to find.
* @return Requested object (or null if not found).
*/
public GObject find (Object userData)
{
if (userData_ == userData)
return this;
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
GObject found = child.find (userData);
if (found != null) return found;
}
// Not found
return null;
}
/**
* Find a segment with specified user data. Start search in this node.
*
* @param userData User data of requested segment.
* @return Segment (or null if not found).
*/
public GSegment findSegment (Object userData)
{
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (userData.equals (segment.getUserData()))
return segment;
}
}
if (children_ != null) {
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
GSegment segment = child.findSegment (userData);
if (segment != null)
return segment;
}
}
// Not found
return null;
}
/**
* Compute the region for this graphics object.
*
* @param visibilityMask Visibility of object.
*/
void computeRegion (int visibilityMask)
{
visibilityMask &= visibilityMask_;
// Stop here if nothing is visible anyway
if (visibilityMask == 0) return;
// First compute regions for all children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.computeRegion (visibilityMask);
}
// The region of the scene is always valid
if (this instanceof GScene) {
isRegionValid_ = true;
return;
}
// Then compute region as union of all children regions and
// content of this object
if (!isRegionValid_) {
region_.clear();
// Children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
region_.union (child.region_);
}
// Graphics components at this level
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (segment.isVisible())
region_.union (segment.getRectangle());
// Add text rectangles to region
Collection texts = segment.getTexts();
if (texts != null) {
for (Iterator j = texts.iterator(); j.hasNext(); ) {
GPositional text = (GPositional) j.next();
region_.union (text.getRectangle());
}
}
// Add image rectangles to region
Collection images = segment.getImages();
if (images != null) {
for (Iterator j = images.iterator(); j.hasNext(); ) {
GPositional image = (GPositional) j.next();
region_.union (image.getRectangle());
}
}
// Add component rectangles to region
Collection components = segment.getComponents();
if (components != null) {
for (Iterator j = components.iterator(); j.hasNext(); ) {
GPositional component = (GPositional) j.next();
region_.union (component.getRectangle());
}
}
// Add vertex image rectangles to region
GImage vertextImage = segment.getVertexImage();
if (vertextImage != null && segment.getNPoints() > 0) {
Rect imageRectangle = vertextImage.getRectangle();
// The rectangle needs to be positioned for every point
int[] x = segment.getX();
int[] y = segment.getY();
for (int j = 0; j < x.length; j++) {
Rect rectangle = new Rect (imageRectangle);
rectangle.x += x[j];
rectangle.y += y[j];
region_.union (rectangle);
}
}
}
}
// It might be more efficient to continue with the extent only
if (region_.getNRectangles() > 100) region_.collapse();
// This region is now OK, but parent region has to be updated
isRegionValid_ = true;
if (parent_ != null && !(parent_ instanceof GScene))
parent_.isRegionValid_ = false;
}
}
/**
* Flag the region of this graphics object as either valid or invalid.
*
* @param isValid True if region is valid, false if it is invalid.
*/
void flagRegionValid (boolean isValid)
{
isRegionValid_ = isValid;
}
/**
* Add a sub object to this graphics object.
* Use as:
*
* <ul>
* <li>add (object, inFronOf (otherObject));
* <li>add (object, behind (otherObject));
* <li>add (object, front());
* <li>add (object, back());
* <li>add (object, 4);
* <li>add (object); // Same as add (object, front());
* </ul>
*
* Add is performed on a parent node, adding child to self.
*
* @param child Object to add
* @param position Position to add to
*/
public void add (GObject child, int position)
{
// Remove object from current location as it can only have one parent
if (child.parent_ != null)
child.parent_.remove (child);
// Brain damage
if (position > children_.size()) position = children_.size();
if (position < 0) position = 0;
// Add to this object
children_.add (position, child);
child.parent_ = this;
// child.window_ = window_;
// child.view_ = view_;
// Since we inherit from parent, style may have changed
child.updateStyle();
// Adding stuff means we need to recompute stuff
GScene scene = getScene();
if (scene != null)
scene.setAnnotationValid (false); // Possibly
isRegionValid_ = false;
}
/**
* Add child object to this object. Add to front (on screen).
*
* @param child Child to add.
*/
public void add (GObject child)
{
add (child, front());
}
/**
* Move this object to the front of its siblings in its parent
* object. If doesn't have a parent, this method has no effect.
* This is a convenience shorthand of
* getParent().reposition (this, getParent().front());
*/
public void toFront()
{
if (parent_ != null)
parent_.reposition (this, parent_.front());
}
/**
* Move this object to the behind its siblings in its parent
* object. If doesn't have a parent, this method has no effect.
* This is a convenience shorthand of
* getParent().reposition (this, getParent().back());
*/
public void toBack()
{
if (parent_ != null)
parent_.reposition (this, parent_.back());
}
/**
* Reposition specified child object.
* Use as:
*
* <ul>
* <li>reposition (inFronOf (otherObject));
* <li>reposition (behind (otherObject));
* <li>reposition (front());
* <li>reposition (back());
* <li>reposition (4);
* </ul>
*
* @param child Child object to be repositioned.
* @param position New position of child.
*/
public void reposition (GObject child, int position)
{
if (position == FORWARD)
position = getPositionOfChild (child) + 1;
else if (position == BACKWARD)
position = getPositionOfChild (child) - 1;
children_.remove (child);
add (child, position);
updateDamage();
}
/**
* Return the position code for the position behind the specified
* child node.
* @see #reposition(GObject,int)
*
* @param child Child node.
* @return Position for "behind" child.
*/
public int behind (GObject child)
{
return getPositionOfChild (child);
}
/**
* Return the position code for the position in front of the specified
* child node.
* @see #reposition(GObject,int)
*
* @param child Child node.
* @return Position for "in front of" child.
*/
public int inFrontOf (GObject child)
{
return getPositionOfChild (child) + 1;
}
/**
* Return the position code for front.
* @see #reposition(GObject,int)
*
* @return Position code for "front".
*/
public int front()
{
return getNChildren();
}
/**
* Return the position code for back.
* @see #reposition(GObject,int)
*
* @return Position code for "back".
*/
public int back()
{
return 0;
}
/**
* Return position code for "forward".
* @see #reposition(GObject,int)
*
* @return Position code for "forward".
*/
public int forward()
{
return FORWARD;
}
/**
* Return position code for "backward".
* @see #reposition(GObject,int)
*
* @return Position code for "backward".
*/
public int backward()
{
return BACKWARD;
}
/**
* Return the position of the specified child among the children
* of this GObject.
*
* @param child Child to find index of.
* @return Index of child (or -1 if no such child).
* 0 indicates back, and so on.
*/
private int getPositionOfChild (GObject child)
{
return children_.indexOf (child);
}
/**
* Return all children objects of this GObject.
*
* @return All children objects of this GObject.
*/
public List getChildren()
{
return children_;
}
/**
* Return number of children of this graphic object.
*
* @return Number of children of this GObject.
*/
public int getNChildren()
{
return children_.size();
}
/**
* Find a child object with specified name. Search entire sub tree
* from this node, depth first.
*
* @param name Name of object to find.
* @return Requested object (or null if not found).
*/
public GObject find (String name)
{
if ((name_ == null && name == null) ||
(name_ != null && name_.equals (name)))
return this;
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
GObject foundObject = child.find (name);
if (foundObject != null)
return foundObject;
}
return null;
}
/**
* Return all segments that intersects the specified rectangle.
* Serach this node, and all sub nodes.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return List of segments intersecting the rectangle.
* If none do, an empty list is returned.
*/
public List findSegments (int x0, int y0, int x1, int y1)
{
List segments = new ArrayList();
findSegments (x0, y0, x1, y1, segments);
return segments;
}
/**
* Return all segments that are completely inside of the specified
* rectangle. Serach this node, and all sub nodes.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return List of segments intersecting the rectangle.
* If none do, an empty list is returned.
*/
public List findSegmentsInside (int x0, int y0, int x1, int y1)
{
List segments = new ArrayList();
findSegmentsInside (x0, y0, x1, y1, segments);
return segments;
}
/**
* Return the first segment found that intersects the specified rectangle
* Search this node, and all sub nodes.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return A segment intersecting the rectangle (or null
* if none do).
*/
public GSegment findSegment (int x0, int y0, int x1, int y1)
{
// Tailormake to stop after first found
List segments = findSegments (x0, y0, x1, y1);
return segments.size() == 0 ? null :
(GSegment) segments.get (segments.size() - 1);
}
/**
* Return the first segment found that are completely inside
* the specified rectangle. Search this node, and all sub nodes.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return A segment intersecting the rectangle (or null
* if none do).
*/
public GSegment findSegmentInside (int x0, int y0, int x1, int y1)
{
// TODO: Stop after first found
List segments = findSegmentsInside (x0, y0, x1, y1);
return segments.size() == 0 ? null :
(GSegment) segments.get (segments.size() - 1);
}
/**
* Return the first segment found that intersects the specified point.
* Search this node, and all sub nodes.
*
* @param x X coordinate of point to check.
* @param y Y coordinate of point to check.
* @return Front-most segment intersecting the point (or null if none do).
*/
public GSegment findSegment (int x, int y)
{
List segments = findSegments (x, y);
return segments.size() == 0 ? null :
(GSegment) segments.get (segments.size() - 1);
}
/**
* Return all segments that intersects with the specified point.
* Search this node, and all sub nodes.
*
* @param x, y Point to check.
* @return All segments intersecting the point. If none do, an
* empty list is returned.
*/
public List findSegments (int x, int y)
{
List segments = new ArrayList();
findSegments (x, y, segments);
return segments;
}
/**
* Find all segments of the subtree rooted at this GObject that
* intersects the specified rectangle.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @param segments List to add segments to.
*/
private void findSegments (int x0, int y0, int x1, int y1, List segments)
{
// Don't scan any furher if the region doesn't intersect
if (!region_.isIntersecting (new Rect (x0, y0, x1-x0+1, y1-y0+1)))
return;
// The region intersects, but we need to consider each segment
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (segment.isIntersectingRectangle (x0, y0, x1, y1))
segments.add (segment);
}
}
// Check subobjects
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.findSegments (x0, y0, x1, y1, segments);
}
}
/**
* Find all segments of the subtree rooted at this GObject that are
* intersects the specified point.
*
* @param x X coordinate of point to check.
* @param y Y coordinate of point to check.
* @param segments List to add segments to.
*/
private void findSegments (int x, int y, List segments)
{
// Don't scan any furher if the region doesn't intersect
if (!region_.isInside (x, y))
return;
// The region intersects, but we need to consider each segment
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (segment.isIntersectingPoint (x, y))
segments.add (segment);
}
}
// Check subobjects
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.findSegments (x, y, segments);
}
}
/**
* Find all segments of the subtree rooted at this GObject that are
* inside the specified rectangle.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @param segments List to add segments to.
*/
private void findSegmentsInside (int x0, int y0, int x1, int y1,
List segments)
{
// Don't scan any furher if the region doesn't intersect
if (!region_.isIntersecting (new Rect (x0, y0, x1-x0+1, y1-y0+1)))
return;
// The region intersects, but we need to consider each segment
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (segment.isInsideRectangle (x0, y0, x1, y1))
segments.add (segment);
}
}
// Check subobjects
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.findSegmentsInside (x0, y0, x1, y1, segments);
}
}
/**
* Return front-most object intersecting the specfied rectangle.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return Front-most object intersecting the specified rectangle
* (or null if none do).
*/
public GObject find (int x0, int y0, int x1, int y1)
{
// TODO: As it needs the first only, this can be optimized
List objects = findAll (x0, y0, x1, y1);
return objects.size() == 0 ? null :
(GObject) objects.get (objects.size() - 1);
}
/**
* Find front-most object that are inside the specified rectangle.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return Front most child object that are fully inside the
* specified rectangle (or null if none are).
*/
public GObject findInside (int x0, int y0, int x1, int y1)
{
// TODO: As it needs the first only, this can be optimized
List objects = findAllInside (x0, y0, x1, y1);
return objects.size() == 0 ? null :
(GObject) objects.get (objects.size() - 1);
}
/**
* Return front-most object intersecting the specfied point.
*
* @param x X coordinate of point to check.
* @param y Y coordinate of point to check.
* @return Front-most object intersecting the point
* (or null if none do).
*/
public GObject find (int x, int y)
{
return find (x - 1, y - 1, x + 1, y + 1);
}
/**
* Find all objects intersecting a specified rectangle. Objects are
* returned with front most object on screen last in list. Seacrh is
* done in the subtree of this object (including this). If no objects
* intersects, an empty list is returned.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @return All objects intersecting the specified rectangle.
*/
public List findAll (int x0, int y0, int x1, int y1)
{
List objects = new ArrayList();
findAll (x0, y0, x1, y1, objects);
return objects;
}
/**
* Find all objects inside a specified rectangle. Objects are
* returned with front most object on screen last in list. Seacrh is
* done in the subtree of this object (including this). If no objects
* is inside, an empty list is returned.
*
* @param x0, y0, x1, y1 Rectangle to check.
* @return All objects intersecting the rectangle.
*/
public List findAllInside (int x0, int y0, int x1, int y1)
{
List objects = new ArrayList();
findAllInside (x0, y0, x1, y1, objects);
return objects;
}
/**
* Find all objects intersecting a specified point. Objects are
* returned with front most object on screen last in list. Seacrh is
* done in the subtree of this object (including this). If no objects
* intersects, an empty list is returned.
*
* @param x X coordinate of point to check.
* @param y Y coordinate of point to check.
* @return All objects intersecting the specfied point.
*/
public List findAll (int x, int y)
{
return findAll (x - 1, y - 1, x + 1, y + 1);
}
/**
* Find all sub objects that intersects the given rectangle.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @param objects Object collection to add to.
*/
private void findAll (int x0, int y0, int x1, int y1, List objects)
{
// Don't scan any furher if the region doesn't intersect
if (!region_.isIntersecting (new Rect (x0, y0, x1 - x0 + 1, y1 - y0 + 1)))
return;
// Check subobjects first
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.findAll (x0, y0, x1, y1, objects);
}
// The region intersects, but we need to consider each segment
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (segment.isIntersectingRectangle (x0, y0, x1, y1)) {
objects.add (this);
break;
}
}
}
}
/**
* Find all sub objects that are inside the given rectangle.
*
* @param x0 X coordinate of upper left corner of rectangle.
* @param y0 Y coordinate of upper left corner of rectangle.
* @param x1 X coordinate of lower right corner of rectangle.
* @param y1 Y coordinate of lower right corner of rectangle.
* @param objects Object collection to add to.
*/
private void findAllInside (int x0, int y0, int x1, int y1, List objects)
{
Box box = new Box (x0, y0, x1, y1);
// Don't scan any furher if the region doesn't intersect
if (!region_.isIntersecting (new Rect (box)))
return;
// Check subobjects first
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.findAllInside (x0, y0, x1, y1, objects);
}
if (region_.isInsideOf (new Rect (box)))
objects.add (this);
}
/**
* Unlink self from parent child list.
*/
public void remove()
{
if (parent_ != null)
parent_.remove (this);
}
/**
* Remove child from child list.
*
* @param child Child object to remove.
*/
public void remove (GObject child)
{
// Update damage with removed region
child.updateDamage();
children_.remove (child);
child.parent_ = null;
// Annotation might need to be recomputed
// TODO: Check if subtree actually has texts before assuming this
GScene scene = getScene();
if (scene != null)
scene.setAnnotationValid (false);
// Force a region recompmutation
isRegionValid_ = false;
}
/**
* Remove all children objects.
*/
public void removeAll()
{
// Loop over a copy of the children list as they are removed
Collection children = new ArrayList (children_);
for (Iterator i = children.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
remove (child);
}
}
/**
* Return the position of this object among its siblings.
*
* @return Position of this object among its siblings.
*/
private int getPosition()
{
if (parent_ == null) return -1;
return parent_.children_.indexOf (this);
}
/**
* Return the child object at specified position.
*
* @param position Position to return child of.
* @return Requested child (or null if non existing).
*/
public GObject getChild (int position)
{
if (position < 0 || position > children_.size() - 1)
return null;
return (GObject) children_.get (position);
}
/**
* Return true if this object is in front of all its siblings.
*
* @return True if this object is in fron of all siblings, false otherwise.
*/
public boolean isInFront()
{
return parent_ == null ? false : getPosition() == parent_.getNChildren()-1;
}
/**
* Return true if this object is behind all its siblings.
*
* @return True if this object is behind all siblings, false otherwise.
*/
public boolean isInBack()
{
return parent_ == null ? false : getPosition() == 0;
}
/**
* Return the sibling object in the immediate front of this object.
*
* @return The sibling object in the immediate front of this object
* (or null if this object is in front, or it is not attach to a
* parent).
*/
public GObject getObjectInFront()
{
if (parent_ == null) return null;
if (isInFront()) return null;
int position = getPosition();
return parent_.getChild (position + 1);
}
/**
* Return the sibling object immediately behind this object.
*
* @return The sibling object immediately behind this object
* (or null if this object is in back, or it is not attach to a
* parent).
*/
public GObject getObjectBehind()
{
if (parent_ == null) return null;
if (isInBack()) return null;
int position = getPosition();
return parent_.getChild (position - 1);
}
/**
* Add a new segment to this object.
*
* @param segment Segment to add.
*/
public void addSegment (GSegment segment)
{
// Lazy create as not all GObjects will have segments
if (segments_ == null)
segments_ = new ArrayList();
// Do nothing if it is there already
if (segments_.contains (segment))
return;
segments_.add (segment);
segment.setOwner (this);
// Since segment style may inherit from this
segment.updateStyle();
}
/**
* Return the n'th segment of this object.
*
* @param segmentNo Segment number to return.
* @return N'th segment (or null if non existent).
*/
public GSegment getSegment (int segmentNo)
{
int nSegments = getNSegments();
return segmentNo < 0 || segmentNo >= nSegments ? null :
(GSegment) segments_.get (segmentNo);
}
/**
* Return number of segments in this GObject.
*
* @return Number of segments in this GObject.
*/
public int getNSegments()
{
return segments_ != null ? segments_.size() : 0;
}
/**
* Remove the specified segment from this GObject.
*
* @param segment Segment to remove. If the specified segment is not
* a child of this GObject, this call has no effect.
*/
public void removeSegment (GSegment segment)
{
// Cannot remove if it doesn't belong here
if (segment.getOwner() != this)
return;
// Update damage
Region region = segment.getRegion();
GWindow window = getWindow();
if (window != null)
window.updateDamageArea (region);
// If there was texts on this segment, flag annotation to be redone
if (segment.getTexts() != null) {
GScene scene = getScene();
if (scene != null)
scene.setAnnotationValid (false);
}
segments_.remove (segment);
segment.setOwner (null);
if (segments_.size() == 0)
segments_ = null;
isRegionValid_ = false;
}
/**
* Remove specified segment from this GObject.
*
* @param segmentNo Segment to remove. If the specified segment does not
* exist, this casll has no effect.
*/
public void removeSegment (int segmentNo)
{
GSegment segment = getSegment (segmentNo);
if (segment != null)
removeSegment (segment);
}
/**
* Remove all segments from this GObject.
*/
public void removeSegments()
{
while (segments_ != null)
removeSegment (0);
}
/**
* Remove sequence of segments from this object.
*
* @param from Starting segment
* @param to Ending segment (-1 indicates all).
*/
public void removeSegments (int from, int to)
{
if (segments_ == null || from >= segments_.size())
return;
if (from < 0) from = 0;
if (to >= segments_.size() || to == -1)
to = segments_.size() - 1;
if (to < 0) to = 0;
int nSegments = from - to + 1;
for (int i = 0; i < nSegments; i++)
removeSegment (from);
isRegionValid_ = false;
}
/**
* Remove sequence of segments from this object. Remove all from
* specified start segment.
*
* @param from Starting segment
*/
public void removeSegments (int from)
{
removeSegments (from, -1);
}
/**
* Refresh all segments of this GObject.
*
* @param visibilityMask Visibility of parent object.
*/
void refreshData (int visibilityMask)
{
// Compute actual visibility of this object
visibilityMask &= visibilityMask_;
// If data is not visible on this level, return
if ((visibilityMask & DATA_VISIBLE) == 0)
return;
// If we don't intersect with the damage, return
if (!region_.isIntersecting (getWindow().getDamageRegion()))
return;
// Refreshing self
if (segments_ != null) {
GCanvas canvas = (GCanvas) getWindow().getCanvas();
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (!segment.isVisible()) continue;
// Render the segment
canvas.render (segment.getX(), segment.getY(),
segment.getActualStyle());
// Render vertex images
GImage vertexImage = segment.getVertexImage();
if (vertexImage != null)
canvas.render (segment.getX(), segment.getY(),
vertexImage);
// Render the images
Collection images = segment.getImages();
if (images != null) {
for (Iterator j = images.iterator(); j.hasNext(); ) {
GImage image = (GImage) j.next();
if (image != null && image.isVisible())
canvas.render (image);
}
}
}
}
// Refreshing children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.refreshData (visibilityMask);
}
}
/**
* Refresh all annotations of this GObject.
*
* @param visibilityMask Visibility of parent object.
*/
void refreshAnnotation (int visibilityMask)
{
// Compute actual visibility of this object
visibilityMask &= visibilityMask_;
// If annotation is not visible on this level, return
if ((visibilityMask & GObject.ANNOTATION_VISIBLE) == 0)
return;
// If we don't intersect with damage, return
if (!region_.isIntersecting (getWindow().getDamageRegion()))
return;
// Refreshing self
if (segments_ != null) {
GCanvas canvas = (GCanvas) getWindow().getCanvas();
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (!segment.isVisible()) continue;
Collection texts = segment.getTexts();
if (texts != null) {
for (Iterator j = texts.iterator(); j.hasNext(); ) {
GText text = (GText) j.next();
if (text != null && text.isVisible())
canvas.render (text, text.getActualStyle());
}
}
}
}
// Refreshing children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.refreshAnnotation (visibilityMask);
}
}
/**
* Refresh all AWT components of this GObject.
*
* @param visibilityMask Visibility of parent object.
*/
void refreshComponents (int visibilityMask)
{
// Compute actual visibility of this object
visibilityMask &= visibilityMask_;
// If components are not visible on this level, return
if ((visibilityMask & GObject.WIDGETS_VISIBLE) == 0)
return;
// If we don't intersect with damage, return
if (!region_.isIntersecting (getWindow().getDamageRegion()))
return;
// Refreshing self
if (segments_ != null) {
GCanvas canvas = (GCanvas) getWindow().getCanvas();
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
if (!segment.isVisible()) continue;
Collection components = segment.getComponents();
if (components != null) {
for (Iterator j = components.iterator(); j.hasNext(); ) {
GComponent component = (GComponent) j.next();
canvas.render (component);
}
}
}
}
// Refreshing children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.refreshComponents (visibilityMask);
}
}
/**
* The redraw method ensures that the geometry in the entire
* scene becomes up to date. Called initially and then in the
* following cases:
*
* <ul>
* <li>resize
* <li>setViewport
* <li>setWorldExtent
* <li>setVisibility to on, because invisible views are not
* redrawn in the case above.
* </ul>
*
* @param visibilityMask
*/
protected void redraw (int visibilityMask)
{
// If not visible at this level, skip here but mark as not drawn
isDrawn_ = false;
visibilityMask &= visibilityMask_;
if (visibilityMask == 0) return;
// Mark annotation as dirty
if (this instanceof GScene) {
GScene scene = (GScene) this;
scene.setAnnotationValid (false);
}
// Redraw children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.redraw (visibilityMask);
}
// Let application object draw itself
draw();
// Compute posistions for all images
computeImagePositions();
// Marks as redrawn
isDrawn_ = true;
}
/**
* Force a redraw of this object.
* <p>
* Normally this method is called automatically whenever needed
* (typically on retransformations).
* A client application <em>may</em> call this method explicitly
* if some external factor that influence the graphics has been
* changed. However, beware of the performance overhead of such
* an approach, and consider reorganizing the code so that model
* changes explictly affects the graphics elements.
*/
public void redraw()
{
redraw (getVisibility());
}
/**
* Set new style for this object. Style elements not explicitly
* set within this GStyle object are inherited from parent
* objects. Child objects with not explicit set style will
* inherit from this style object. A GObject does not need a style
* instance; in this case all style elements are inherited.
*
* @param style Style of this object (or null if the intent is
* to unset the current style).
*/
public void setStyle (GStyle style)
{
if (style_ != null)
style_.removeListener (this);
style_ = style;
if (style_ != null)
style_.addListener (this);
updateStyle();
}
/**
* Return style of this object. This is the style set by setStyle()
* and not necesserily the style as it appears on screen as unset
* style elements are inherited from parents.
*
* @return Style of this object as specified with setStyle(), (or
* null if no style has been provided).
*/
public GStyle getStyle()
{
return style_;
}
/**
* This is the actual style used for this object when
* inheritance for unset values are resolved.
*
* @return Actual style for this segment.
*/
GStyle getActualStyle()
{
return actualStyle_;
}
/**
* Resolve unset values in the style element of this object.
* This involved creating a default "actual style" element,
* override with style elements of the parents actual style
* and override with style elements explicitly set in the style
* of this object (if any).
*/
private void updateStyle()
{
// Initialize the actual style used for this object
actualStyle_ = new GStyle();
// Update with parent style
if (parent_ != null)
actualStyle_.update (parent_.getActualStyle());
// Update (and possible override) with present style
if (style_ != null)
actualStyle_.update (style_);
// Update style of segments
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
segment.updateStyle();
}
}
// Update style of children objects
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.updateStyle();
}
}
/**
* Change the visibility for this object.
* <p>
* NOTE: The present visibility is maintained, and the new
* flag is taken as an <em>instruction</em> for change.
*
* The instruction is an or'ed combination of:
*
* <ul>
* <li> DATA_VISIBLE
* <li> ANNOTATION_VISIBLE
* <li> SYMBOLS_VISIBLE
* <li> WIDGETS_VISIBLE
* <li> DATA_INVISIBLE
* <li> ANNOTATION_INVISIBLE
* <li> SYMBOLS_INVISIBLE
* <li> WIDGETS_INVISIBLE
* </ul>
*
* The settings VISIBLE and INVISIBLE are conveniences compound
* for all visible and invisible settings respectively.
*
* @param visibilityMask Visibility instruction.
*/
public void setVisibility (int visibilityMask)
{
int oldVisibilityMask = visibilityMask_;
// Determine the new visibility code
if ((visibilityMask & DATA_VISIBLE) != 0)
visibilityMask_ |= DATA_VISIBLE;
if ((visibilityMask & ANNOTATION_VISIBLE) != 0)
visibilityMask_ |= ANNOTATION_VISIBLE;
if ((visibilityMask & SYMBOLS_VISIBLE) != 0)
visibilityMask_ |= SYMBOLS_VISIBLE;
if ((visibilityMask & WIDGETS_VISIBLE) != 0)
visibilityMask_ |= WIDGETS_VISIBLE;
if ((visibilityMask & DATA_INVISIBLE) != 0)
visibilityMask_ &= ~DATA_VISIBLE;
if ((visibilityMask & ANNOTATION_INVISIBLE) != 0)
visibilityMask_ &= ~ANNOTATION_VISIBLE;
if ((visibilityMask & SYMBOLS_INVISIBLE) != 0)
visibilityMask_ &= ~SYMBOLS_VISIBLE;
if ((visibilityMask & WIDGETS_INVISIBLE) != 0)
visibilityMask_ &= ~WIDGETS_VISIBLE;
// Return if nothing has changed
if (oldVisibilityMask == visibilityMask_)
return;
// Redraw if something was turned on and draw has not been done
if (visibilityMask_ != 0 && !isDrawn_)
redraw (visibilityMask_);
// Symbol visibility has changed
if ((oldVisibilityMask & SYMBOLS_VISIBLE) !=
(visibilityMask_ & SYMBOLS_VISIBLE))
changeSymbolVisibility();
// Widget visibility has changed
if ((oldVisibilityMask & WIDGETS_VISIBLE) !=
(visibilityMask_ & WIDGETS_VISIBLE))
changeWidgetVisibility ();
// Annotation visibility has changed
if ((oldVisibilityMask & ANNOTATION_VISIBLE) !=
(visibilityMask_ & ANNOTATION_VISIBLE)) {
GScene scene = getScene();
if (scene != null)
scene.setAnnotationValid (false);
// Redo annotation unless this was an empty object
if (children_.size() > 0 || getNSegments() > 0) {
updateDamage();
// Don't think we need this. Check for errors later.
// getWindow().computeTextPositions();
}
}
// Data visibility has changed
if ((oldVisibilityMask & DATA_VISIBLE) !=
(visibilityMask_ & DATA_VISIBLE)) {
// If the visibility is turned on, we must find the region first
if ((visibilityMask & DATA_VISIBLE) != 0)
getWindow().computeRegion();
updateDamage();
}
}
private void changeSymbolVisibility()
{
// TODO
// Handle children first
}
private void changeWidgetVisibility()
{
// TODO
// Handle children first
}
/**
* Compute position of all GTexts in the subtree rooted
* at this GObject.
*/
void computeTextPositions()
{
GScene scene = getScene();
if (scene == null) return;
// Stop here if annotation is not visible
if ((visibilityMask_ & ANNOTATION_VISIBLE) == 0)
return;
//
// Do annotation on children
//
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.computeTextPositions();
}
//
// Do annotation on self
//
// Do annotation on all segments
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
Collection texts = segment.getTexts();
scene.computePositions (texts);
}
}
}
/**
* Compute position of all GComponents in the subtree
* rooted at this GObject.
*/
void computeComponentPositions()
{
GScene scene = getScene();
if (scene == null) return;
// Stop here if annotation is not visible
if ((visibilityMask_ & WIDGETS_VISIBLE) == 0)
return;
//
// Do annotation on children
//
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.computeComponentPositions();
}
//
// Do annotation on self
//
// Do annotation on all segments
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
Collection components = segment.getComponents();
scene.computePositions (components);
}
}
}
/**
* Compute position of all GImages in the subtree rooted
* at this GObject.
*/
void computeImagePositions()
{
GScene scene = getScene();
if (scene == null) return;
// Loop over all segments and position their images
if (segments_ != null) {
for (Iterator i = segments_.iterator(); i.hasNext(); ) {
GSegment segment = (GSegment) i.next();
Collection images = segment.getImages();
scene.computePositions (images);
GImage vertexImage = segment.getVertexImage();
if (vertexImage != null)
scene.computeVertexPositions (vertexImage);
}
}
// Compute image positions for children
for (Iterator i = children_.iterator(); i.hasNext(); ) {
GObject child = (GObject) i.next();
child.computeImagePositions();
}
}
/**
* Update window damage area with the region extent of this object.
*/
private void updateDamage()
{
GWindow window = getWindow();
if (window != null)
window.updateDamageArea (region_);
}
/**
* Return parent GObject of this object.
*
* @return Parent of this object (or null if it doesn't have a parent).
*/
public GObject getParent()
{
return parent_;
}
/**
* Convenience method for refreshing the window canvas.
* Equivalent to <code>getWindow().refresh();</code>.
*/
public void refresh()
{
GWindow window = getWindow();
if (window != null)
window.refresh();
}
/**
* This method should be overloaded for graphics objects with drawable
* elements (GSegments). Intermediate nodes should leave the method empty.
*/
public void draw()
{
}
/**
* Called when the style of this object is changed.
*
* @param style Style that has changed.
*/
public void styleChanged (GStyle style)
{
updateStyle();
updateDamage();
}
/**
* Return a string representation of this object.
*
* @return String representation of this object.
*/
public String toString()
{
return "GObject: " + name_;
}
}