/*******************************************************************************
* Copyright (c) 2013, 2014 Ericsson.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marc Dumais (Ericsson) - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.visualizer.ui.canvas;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
/**
* Graphic object that can be used as a container for child objects. Each object
* is sized and positioned in virtual units, and positioned relative to the parent
* object's position. Setting the real (pixel) bounds of an object recursively sets
* the bounds of any contained child objects, taking into account its virtual
* bounds, compared to its parent.
* @since 1.1
*/
public class VirtualBoundsGraphicObject extends GraphicObject {
// --- members ---
/**
* Holds the virtual position and size of graphical object.
* Position is relative to parent object
*/
protected Rectangle m_virtualBounds = new Rectangle(0,0,0,0);
/** List of children objects contained in this one */
protected ArrayList<VirtualBoundsGraphicObject> m_childrenObjects =
new ArrayList<VirtualBoundsGraphicObject>();
/** Map of contained objects and their identifying labels. for quick look-up */
protected HashMap<String, VirtualBoundsGraphicObject> m_childrenObjectsMap =
new HashMap<String, VirtualBoundsGraphicObject>();
/** Whether the container's boundaries should be drawn */
protected boolean m_drawContainerBounds = true;
/** Is the object selectable? */
protected boolean m_selectable = false;
/** Color to use when this object is Selected */
protected Color m_selectedColor = null;
/** Value for the margin in pixels */
protected int m_childMargin = 0;
/** Default for the margin in pixels */
protected static final int MARGIN_PIXELS_DEFAULT = 1;
// --- constructors/destructors ---
/** Constructor */
public VirtualBoundsGraphicObject() {
// default is not selectable
this(false, MARGIN_PIXELS_DEFAULT);
}
/** Alternate constructor */
public VirtualBoundsGraphicObject(boolean selectable, int childMargin) {
m_selectable = selectable;
setDrawContainerBounds(true);
m_childMargin = childMargin;
}
/** Dispose method - recursively dispose this object and children objects */
@Override
public void dispose() {
super.dispose();
if (m_childrenObjects != null) {
for (VirtualBoundsGraphicObject o : m_childrenObjects) {
o.dispose();
}
m_childrenObjects.clear();
m_childrenObjects = null;
}
if (m_childrenObjectsMap != null) {
m_childrenObjectsMap.clear();
m_childrenObjectsMap = null;
}
}
// --- Object methods ---
/** Returns string representation. */
@Override
public String toString() {
return String.format("Class: %s, Real bounds: %s, Virtual bounds: %s" +
", Draw container bounds: %s ",
this.getClass().getSimpleName(),
this.getBounds().toString(),
m_virtualBounds.toString(),
m_drawContainerBounds
);
}
// --- accessors ---
/**
* Sets whether the container's boundary should be drawn / filled-in.
* If false, indicates child objects are displayed without showing
* the parent container.
*/
public void setDrawContainerBounds(boolean draw) {
m_drawContainerBounds = draw;
}
/** Sets whether the object is selectable */
public void setSelectable(boolean sel) {
m_selectable = sel;
}
/** Returns whether the object is selectable */
public boolean isSelectable() {
return m_selectable;
}
/** Set the color used to highlight a selection */
public void setSelectedColor(Color color) {
m_selectedColor = color;
}
/** Get the color used to highlight a selection */
public Color getSelectedColor() {
return m_selectedColor;
}
/** Set the margin to be inserted between a parent and a child object,
* in pixels */
public void setChildMargin(int margin) {
m_childMargin = margin;
}
// --- methods ---
/**
* Sets the absolute bounds (in pixels) of this container object. If it has
* children objects, recursively set their absolute bounds.
* Overridden to delegate to setBounds(int,int,int,int) in this class,
* rather than the base class.
*/
@Override
public void setBounds(Rectangle bounds) {
setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
}
/**
* Sets the absolute bounds (in pixels) of this container object. If it has
* children objects, recursively set their absolute bounds.
*/
@Override
public void setBounds(int x, int y, int w, int h) {
super.setBounds(x, y, w, h);
for(VirtualBoundsGraphicObject o : m_childrenObjects) {
o.setBounds(virtualToRealBounds(o.getVirtualBounds()));
}
}
/**
* Set bounds of current object relative to its container.
* The x and y coordinate are relative to the parent container.
* The unit is arbitrary integer but have to be consistent between
* parent and children. Width and height must be greater than zero.
*/
public void setVirtualBounds(int[] bounds) {
m_virtualBounds.x = bounds[0];
m_virtualBounds.y = bounds[1];
m_virtualBounds.width = bounds[2];
m_virtualBounds.height = bounds[3];
checkVirtualBounds();
}
/**
* Set bounds of current object relative to its container.
* The x and y coordinate are relative to the parent container.
* The unit is arbitrary integer but have to be consistent between
* parent and children. Width and height must be greater than zero.
*/
public void setVirtualBounds(Rectangle bounds) {
m_virtualBounds = bounds;
checkVirtualBounds();
}
/**
* Set bounds of current object relative to its container.
* The x and y coordinate are relative to the parent container.
* The unit is arbitrary integer but have to be consistent between
* parent and children. Width and height must be greater than zero.
*/
public void setVirtualBounds(int x, int y, int width, int height) {
m_virtualBounds.x = x;
m_virtualBounds.y = y;
m_virtualBounds.width = width;
m_virtualBounds.height = height;
checkVirtualBounds();
}
/** Get the relative bounds of current object, relative to its parent container */
public Rectangle getVirtualBounds() {
return m_virtualBounds;
}
/** Performs a sanity check of the virtual bounds of this object */
private void checkVirtualBounds() {
if (m_virtualBounds.x < 0) {
throw new IllegalArgumentException("Illegal x: " + m_virtualBounds.x);
}
if (m_virtualBounds.y < 0) {
throw new IllegalArgumentException("Illegal y: " + m_virtualBounds.y);
}
if (m_virtualBounds.width <= 0) {
throw new IllegalArgumentException("Illegal width: " + m_virtualBounds.width);
}
if (m_virtualBounds.height <= 0) {
throw new IllegalArgumentException("Illegal height: " + m_virtualBounds.height);
}
}
/**
* Returns the computed absolute (canvas) bounds of passed child object,
* considering its virtual bounds compared to its parent's (current object)
*/
public Rectangle virtualToRealBounds(Rectangle childsVirtualBounds) {
float ox = 0.0f;
float oy = 0.0f;
float ow = 0.0f;
float oh = 0.0f;
ox = (float) this.getBounds().x + childsVirtualBounds.x
* ( (float) this.getBounds().width / (this.getVirtualBounds().width) );
oy = (float) this.getBounds().y + childsVirtualBounds.y
* ( (float) this.getBounds().height / this.getVirtualBounds().height );
ow = ( (float) childsVirtualBounds.width
/ this.getVirtualBounds().width) * this.getBounds().width;
oh = ( (float) childsVirtualBounds.height
/ this.getVirtualBounds().height) * this.getBounds().height;
// add margin
ox += m_childMargin;
oy += m_childMargin;
ow -= 2 * m_childMargin;
oh -= 2 * m_childMargin;
// make sure computed width and height are positive
ow = (ow > 0) ? ow : 0.0f;
oh = (oh > 0) ? oh : 0.0f;
return new Rectangle(Math.round(ox), Math.round(oy), Math.round(ow), Math.round(oh));
}
/** Add children graphical object in this container. Provided label can be used to later retrieve object */
public VirtualBoundsGraphicObject addChildObject(String label, VirtualBoundsGraphicObject obj) {
m_childrenObjects.add(obj);
m_childrenObjectsMap.put(label, obj);
return obj;
}
/** Returns a list of child objects of a given derived class, optionally recursing through child objects */
public ArrayList<VirtualBoundsGraphicObject> getChildObjects(Class<?> type, boolean recurse) {
ArrayList<VirtualBoundsGraphicObject> objs = new ArrayList<VirtualBoundsGraphicObject>();
for (VirtualBoundsGraphicObject o : this.getAllObjects(recurse)) {
if(type.isInstance(o) ) {
objs.add(o);
}
}
return objs;
}
/** Searches recursively for a child object matching a label.
Returns null if object is not found */
public VirtualBoundsGraphicObject getObject(String label) {
return getObject(label, true);
}
/** Searches for a child object matching a label. Recurse flag
controls whether the search is recursive. */
public VirtualBoundsGraphicObject getObject(String label, boolean recurse) {
if (m_childrenObjectsMap.containsKey(label)) {
return m_childrenObjectsMap.get(label);
}
else if (recurse) {
for(VirtualBoundsGraphicObject o : m_childrenObjects) {
if (o.getObject(label) != null) {
return o.getObject(label, true);
}
}
}
return null;
}
/** Gets all objects from this container. Optionally recurse to all sub-objects */
public ArrayList<VirtualBoundsGraphicObject> getAllObjects(boolean recurse) {
ArrayList<VirtualBoundsGraphicObject> list = new ArrayList<VirtualBoundsGraphicObject>();
for (VirtualBoundsGraphicObject o : m_childrenObjects) {
list.add(o);
if (recurse) {
list.addAll(o.getAllObjects(recurse));
}
}
return list;
}
/** Returns a list of selectable objects */
public List<VirtualBoundsGraphicObject> getSelectableObjects() {
List<VirtualBoundsGraphicObject> list = new ArrayList<VirtualBoundsGraphicObject>();
for (VirtualBoundsGraphicObject o : m_childrenObjects) {
if (o.isSelectable()) {
list.add(o);
}
list.addAll(o.getSelectableObjects());
}
return list;
}
/**
* Returns whether an immediate child of current object reports
* having decorations to display
*/
public boolean hasChildrenWithDecorations() {
for (VirtualBoundsGraphicObject o : getAllObjects(false)) {
if (o.hasDecorations()) {
return true;
}
}
return false;
}
// --- paint methods ---
/**
* Invoked to allow element to paint itself on the viewer canvas
* Also paints any children objects(s)
*/
@Override
public void paint(GC gc, boolean decorations) {
// Set GC to reflect object properties, if set.
Color oldForeground = null;
Color oldBackground = null;
if (m_foreground != null) {
oldForeground = gc.getForeground();
gc.setForeground(m_foreground);
}
if (m_background != null) {
oldBackground = gc.getBackground();
gc.setBackground(m_background);
}
if (!decorations) {
// Paint the object.
if (isVisible()) {
paintContent(gc);
}
}
else {
// Paint decorations
if (isVisible() && hasDecorations()) {
paintDecorations(gc);
}
}
// recursively paint children objects
if (m_childrenObjects != null) {
for (VirtualBoundsGraphicObject o : m_childrenObjects) {
o.paint(gc, decorations);
}
}
// Restore old state.
if (m_foreground != null && oldForeground != null)
gc.setForeground(oldForeground);
if (m_background != null && oldBackground != null)
gc.setBackground(oldBackground);
}
/**
* Invoked to allow element to paint itself on the viewer canvas.
*/
@Override
public void paintContent(GC gc) {
if (m_drawContainerBounds) {
if (isSelected() && m_selectedColor != null) {
gc.setForeground(m_selectedColor);
}
else {
gc.setForeground(m_foreground);
}
gc.setBackground(m_background);
gc.fillRectangle(m_bounds);
super.paintContent(gc);
}
}
/**
* Recursively checks if children objects have decorations to draw.
* If overridden in a derived type, this behavior should be preserved
* to ensure that children's decorations are drawn.
*/
@Override
public boolean hasDecorations() {
return hasChildrenWithDecorations();
}
}