Shed shadows into the light!
Let’s shed light into the shadows of how to implement shadows in the light of the ILOG JViews Graphic Framework. Some graphic objects have built-in shadows, for instance IlvShadowRectangle and IlvShadowLabel. Shadows can also be implemented by creating symbols in Diagrammer’s symbol editor that have a shadow element. We can implement a specialized shadow feature for each graphic object class that is used in our application, and this will allow us maximal flexibility, but it is a lot of work. Instead, I want to have a shadow feature that is generic and works for all graphic objects.
A shadow layer
Shadows are always behind the object that produces the shadow. In a manager, the graphic objects are organized in manager layers that determine the drawing order. Hence, the obvious idea is to implement a specialized manager layer that handles the shadows, and put the real objects in the layers above the shadow layer. If we add the shadow layer as layer 0 to the manager and the real objects in the layers above layer 0, then the shadows are always drawn behind the real graphic objects:
// create the shadow layer as layer 0 manager.addLayer(new ShadowLayer(), 0); // add an ellipse into layer 1 IlvEllipse object = new IlvEllipse(new IlvPoint(40,40), 30); manager.addObject(object, 1, true); // the layer should now automatically take care of the ellipse's shadow
We want the shadow layer to manage the shadows automatically. The idea is to create a graphic object in the shadow layer representing the shadow whenever a real object is added to one of the other layers. To detect when a shadow must be added, the shadow layer must listen to the changes in the manager. Here, the ManagerContentChangedListener mechanism comes handy. The shadow layer registers itself as listener to the manager when it gets assigned to a manager. To do so, we need to override the method setManager:
public class ShadowLayer extends IlvManagerLayer
implements ManagerContentChangedListener
{
protected void setManager(IlvManager m)
{
// remove the listener from the old manager
if (getManager() != null) {
getManager().removeManagerContentChangedListener(this);
}
super.setManager(m);
// add the listener to the new manager
if (getManager() != null) {
getManager().addManagerContentChangedListener(this);
}
}
// called when the manager sends an event
public void contentsChanged(ManagerContentChangedEvent evt)
{
...
}
}
The shadow layer updates itself automatically
Shadows are graphic objects that occur in the shadow layer, but we don’t add them manually; the shadow layers does it automatically for us. A shadow should have the same shape as the real object and is always drawn with a slight offset. Let’s assume the light source is at the bottom right, then the shadow must be shifted towards the top left, relative to the real object. When the real object moves, the shadow should follow the real object. We use a hash map to quickly determine which shadow belongs to the object that moved:
public class ShadowLayer extends IlvManagerLayer
implements ManagerContentChangedListener
{
HashMap<IlvGraphic,IlvGraphic> shadowMap;
int shadowLayerNumber;
float shadowOffsetX = -6;
float shadowOffsetY = -6;
boolean blockEvents = false;
public ShadowLayer()
{
shadowMap = new HashMap<IlvGraphic,IlvGraphic>();
}
protected void setManager(IlvManager m)
{
removeAllShadows();
// remove the listener from the old manager
if (getManager() != null) {
getManager().removeManagerContentChangedListener(this);
}
super.setManager(m);
if (getManager() != null) {
// add the listener to the new manager
getManager().addManagerContentChangedListener(this);
// determine the shadow layer inside the manager
for (int i = 0; i < getManager().getLayersCount(); i++) {
if (getManager().getManagerLayer(i) == this)
shadowLayerNumber = i;
}
}
addAllShadows();
}
private void removeAllShadows()
{
boolean old = blockEvents;
blockEvents = true;
try {
ArrayList<IlvGraphic> a = new ArrayList<IlvGraphic>(shadowMap.values());
Iterator<IlvGraphic> iter = a.iterator();
while (iter.hasNext()) {
IlvGraphic shadow = iter.next();
getManager().removeObject(shadow, true);
}
shadowMap = new HashMap<IlvGraphic,IlvGraphic>();
} finally {
blockEvents = old;
}
}
private void addAllShadows()
{
boolean old = blockEvents;
blockEvents = true;
try {
ArrayList<IlvGraphic> objects = new ArrayList<IlvGraphic>();
IlvGraphicEnumeration e = getManager().getObjects();
while (e.hasMoreElements())
objects.add(e.nextElement());
Iterator<IlvGraphic> iter = objects.iterator();
while (iter.hasNext())
addShadow(iter.next());
} finally {
blockEvents = old;
}
}
}
When the shadow layer is added to a different manager, it must remove all shadows of objects that were in the old manager, and then add shadows for objects that belong to the new manager. It must also determine its own layer number. We said above that the shadow layer should be layer 0, but in fact we want to design the shadow layer so that it can work if any layer number is assigned to it. The most important step is that when adding a shadow, we do not want the event listener to add a shadow for the shadow. This would result in an endless loop where shadows of shadows of shadows of … shadows of the real object are created. This must be avoided. Therefore, we must block the events that are caused by shadows. The variable blockEvents serves this purpose. Whenever we manipulate the shadow, we do it in the following reentrance-safe scheme to set the variable to true:
boolean old = blockEvents;
blockEvents = true;
try {
...
} finally {
blockEvents = old;
}
Listening to events
The core of the mechanism is the event listener method: it adds a shadow when a new real object is added to the manager, removes the shadow when its corresponding object is removed from the manager, and updates the shadow position when the corresponding object is moved. However, when the event is caused by the shadow itself, the listener can ignore the event. This is detected through the variable blockEvents:
public void contentsChanged(ManagerContentChangedEvent evt)
{
if (blockEvents) return;
switch (evt.getType()) {
case ManagerContentChangedEvent.OBJECT_ADDED:
ObjectInsertedEvent evt1 = (ObjectInsertedEvent)evt;
if (evt1.getLayer() == this) return;
addShadow(evt1.getGraphicObject());
break;
case ManagerContentChangedEvent.OBJECT_REMOVED:
ObjectRemovedEvent evt2 = (ObjectRemovedEvent)evt;
if (evt2.getLayer() == this) return;
removeShadow(evt2.getGraphicObject());
break;
case ManagerContentChangedEvent.OBJECT_BBOX_CHANGED:
ObjectBBoxChangedEvent evt3 = (ObjectBBoxChangedEvent)evt;
moveShadow(evt3.getGraphicObject());
break;
}
}
When adding the shadow, we shift its position relative to the original object. Shadows should not be manipulated interactively, therefore we set them nonselectable and noneditable. Finally we store the shadow and the original object in the shadow map to remember which shadow belongs to which original object:
protected IlvRect shadowBounds(IlvRect origBounds)
{
origBounds.x += shadowOffsetX;
origBounds.y += shadowOffsetX;
return origBounds;
}
private void addShadow(IlvGraphic g)
{
boolean old = blockEvents;
blockEvents = true;
try {
IlvManagerLayer layer = getManager().getManagerLayer(g);
if (layer == this) return;
IlvRect bbox = shadowBounds(g.boundingBox());
IlvGraphic shadow = createShadow(g, bbox);
shadow.setSelectable(false);
shadow.setEditable(false);
shadow.setMovable(false);
shadowMap.put(g, shadow);
getManager().addObject(shadow, shadowLayerNumber, true);
} finally {
blockEvents = old;
}
}
When an object moves, we access its shadow and update it accordingly. Removing the shadow is similarly easy. We just need to remember to update the shadow map as well.
private void removeShadow(IlvGraphic g)
{
boolean old = blockEvents;
blockEvents = true;
try {
IlvGraphic shadow = shadowMap.get(g);
if (shadow == null) return;
shadowMap.remove(g);
getManager().removeObject(shadow, true);
} finally {
blockEvents = old;
}
}
private void moveShadow(IlvGraphic g)
{
boolean old = blockEvents;
blockEvents = true;
try {
IlvGraphic shadow = shadowMap.get(g);
if (shadow == null) return;
IlvRect bbox = shadowBounds(g.boundingBox());
getManager().moveObject(shadow, bbox.x, bbox.y, true);
} finally {
blockEvents = old;
}
}
How to create a shadow
The remaining trick is how to create the shadow object itself. The following trick works for most IlvGraphic classes, but not for IlvJComponentGraphic or subclasses of IlvLinkImage. We simply create a copy of the object, change its color to the shadow color, and we are done. Since the copy has the exact same shape as the original object, it looks like a shadow of the object:
Color shadowColor = Color.black;
protected IlvGraphic createShadow(IlvGraphic g, IlvRect bounds)
{
IlvGraphic shadow = g.copy();
shadow.move(bounds.x, bounds.y);
shadow.setFillOn(true);
shadow.setBackground(shadowColor);
shadow.setForeground(shadowColor);
return shadow;
}
The result
This was a sketch of how to implement a shadow layer that works for most graphic objects. When the layer is installed, all objects will automatically have a shadow. The method createShadow works for most objects, but not all. It can be extended if you are using specific graphic objects that cannot be handled with the trick we used above. The entire little program can be found here.
The shadow layer behaves a little bit differently than the graphic classes such as IlvShadowRectangle:
- When using IlvShadowRectangle, the shadow is selectable. When using the shadow layer, the shadow is not selectable. Only the original object is selectable.
- When links connect to nodes, links never connect to shadows, since the shadow is not part of the node, but rather a separate object.
- The shadow of overlapping objects are never drawn on top of the objects. The shadows always remain behind the objects. This is illustrated with the following picture.
![]() |
![]() |
| Overlapping IlvShadowRectangle |
Overlapping rectangles with ShadowLayer |
Tags: Code Optimization, Event Listener, JViews, Manager Layer









