/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue;
import tufts.Util;
import tufts.vue.gui.GUI;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import java.awt.Frame;
import javax.swing.JComponent;
import javax.swing.JMenu;
/**
*
* Generic AWT containment event raiser. Will deliver an event to ANY
* Component in the entire hierarchy for which
* getTargetClass().isInstance(Component) returns true. Event
* delivery is done by an override of the abstrict dispatch(Object
* listener).
*
* Alternatively, you may override visit(Component c), and perform
* whatever tests you'd like for dispatching, or just use it for AWT
* tree traversal.
*
* May be started at arbitrary points in the AWT tree
* by using raiseStartingAt(Contaner) instead if raise()
*
* After the event has been raised, traversal (and subsequent event dispatch) may be stopped
* by calling stop() (e.g., from within dispatch() or visit()).
*
* Although this runs surprisingly fast (order of 0.3ms to 3ms),
* it's probably wise to use this with care.
*
* Note that if targetClass is null, visit(Component) will throw
* a NullPointerException, so only do this if you are overriding visit(Component).
*
* Does not currently traverse into children of popup menus.
*
* @version $Revision: 1.17 $ / $Date: 2010-02-03 19:17:41 $ / $Author: mike $
* @author Scott Fraize
*/
public abstract class EventRaiser<T>
{
private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(EventRaiser.class);
private int depth = 0;
public static boolean INCLUDE_MENUS = true;
public final Class targetClass;
public final Object source;
private final boolean includeMenus;
public EventRaiser(Object source, Class clazz, boolean includeMenus)
{
this.source = source;
this.targetClass = clazz;
this.includeMenus = includeMenus;
}
public EventRaiser(Object source, Class clazz)
{
this(source, clazz, false);
}
public EventRaiser(Object source)
{
this.source = source;
this.targetClass = getTargetClass();
this.includeMenus = false;
}
public Object getSource() {
return source;
}
public Class getTargetClass() {
return targetClass;
}
public void raise() {
traverse(null);
}
public void raiseStartingAt(Container start) {
traverse(start);
}
public abstract void dispatch(T target);
/**
* Stop traversal: nothing further can be dispatched.
*
* This works by throwing a special completion exception, so
* it must be called from within the traversal (e.g., dispatch(), or visit()).
*
*/
protected static void stop() {
throw new Completion();
}
private static class Completion extends RuntimeException {}
private void traverse(Container start)
{
long startTime = 0;
// startTime = System.nanoTime(); // java 1.5
if (DEBUG.EVENTS && DEBUG.META) {
startTime = System.currentTimeMillis();
out("raising " + this + " "
+ (start == null ? "everywhere" : ("starting at: " + GUI.name(start))));
}
try {
if (start == null) {
traverseFrames();
} else {
traverseContainer(start);
}
} catch (Completion e) {
if (DEBUG.EVENTS) out("traversal stopped; done dispatching events");
}
if (DEBUG.EVENTS && DEBUG.META) {
long delta = System.currentTimeMillis() - startTime;
//double delta = (System.nanoTime() - startTime) / 1000000.0; // java 1.5
out(this + " " + delta + "ms delivery");
}
}
private void traverseFrames()
{
Frame frames[] = Frame.getFrames();
if (DEBUG.EVENTS && DEBUG.META) out("FRAMES: " + java.util.Arrays.asList(frames));
traverseChildren(frames);
}
protected void dispatchSafely(Component target)
{
if (DEBUG.EVENTS) out("DISPATCHING "
+ this
+ " to " + tufts.vue.gui.GUI.name(target)
//+ " " + (target instanceof JComponent ? ((JComponent)target).getUIClassID() : "")
);
try {
dispatch((T) target);
} catch (Completion e) {
throw e;
} catch (Throwable e) {
Util.printStackTrace(e, EventRaiser.class.getName() + " exception during dispatch: "
+ "\n\t event source: " + source + " (" + Util.objectTag(source) + ")"
+ "\n\t event raised: " + this
+ "\n\t targetClass: " + targetClass
+ "\n\tdispatching to: " + GUI.name(target) + " (" + Util.objectTag(target) + ")"
+ "\n\t also known as: " + target
);
}
}
protected void visit(Component c) {
if (targetClass.isInstance(c)) {
dispatchSafely(c);
}
}
protected void traverseContainer(Container parent)
{
visit(parent);
if (parent.getComponentCount() > 0)
traverseChildren(parent.getComponents());
if (parent instanceof Window) {
Window[] owned = ((Window)parent).getOwnedWindows();
if (owned.length > 0) {
if (DEBUG.EVENTS && DEBUG.META)
eoutln(" OWNED by " + GUI.name(parent) + ": " + java.util.Arrays.asList(owned));
traverseChildren(owned);
}
}
else if (parent instanceof javax.swing.JMenu) {
traverseChildren( ((javax.swing.JMenu)parent).getMenuComponents() );
}
}
protected void traverseChildren(Component[] children)
{
depth++;
for (int i = 0; i < children.length; i++) {
if (DEBUG.EVENTS && DEBUG.META && DEBUG.FOCUS) eoutln(getType(children[i]) + i + " " + GUI.name(children[i]));
if (children[i] instanceof Container)
traverseContainer((Container)children[i]);
else
visit(children[i]);
}
depth--;
}
private static String getType(Component c) {
if (c instanceof Frame)
return "FRAME";
if (c instanceof Window)
return "owned";
return "child";
}
private void out(String s) {
Log.debug(s);
}
protected void eout(String s) {
for (int x = 0; x < depth; x++) System.out.print(" ");
System.out.print(s);
}
protected void eoutln(String s) {
eout(s + "\n");
}
}