/*
* Beryl - A web platform based on XML, XSLT and Java
* This file is part of the Beryl XML GUI
*
* Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
*
* This program 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 program 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-3107 USA
*/
package org.beryl.gui;
import java.awt.Component;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;
import javax.help.HelpBroker;
import javax.help.HelpSet;
import javax.swing.JComponent;
import javax.swing.UIManager;
import org.beryl.gui.model.MapDataModel;
import org.beryl.gui.model.ModelChangeEvent;
import org.beryl.gui.validators.ValidationException;
import org.beryl.gui.validators.Validator;
import cz.autel.dmi.HIGConstraints;
/**
* <tt>Widget</tt> is the base class of all components using the
* Beryl XML GUI framework.
*/
public abstract class Widget extends AbstractView implements LFConstants {
/* Method cache used to accelerate swing widget introspection */
private static HashMap methodCache = new HashMap();
/* Commonly used inside widget sublcasses */
protected static HIGConstraints constraints = new HIGConstraints();
/* Widget property information */
protected static WidgetInfo widgetInfo = null;
/* Used to invoke getters via reflection */
private static final Object[] EMPTY_ARRAY = new Object[] {
};
/* JavaHelp support */
protected static HelpBroker helpBroker = null;
protected static HelpSet helpSet = null;
/* Members */
private ArrayList children = null;
private String name = null;
private Widget parent = null;
private HashMap widgetMap = null;
private ArrayList validators = null;
static {
/* Add the basic properties which should be available to most widgets */
widgetInfo = new WidgetInfo(Widget.class);
widgetInfo.addProperty("anchor", "anchor", null);
widgetInfo.addProperty("background", "color", UIManager.getDefaults().getColor("Label.background"));
widgetInfo.addProperty("foreground", "color", UIManager.getDefaults().getColor("Label.foreground"));
widgetInfo.addProperty("enabled", "bool", Boolean.TRUE);
widgetInfo.addProperty("opaque", "bool", Boolean.TRUE);
widgetInfo.addProperty("helpid", "string", null);
};
/**
* Create a widget with a predefined type
* @param parent The widget's parent
* @param name The name of the widget
* @param preset The preset to use
* @throws GUIException If something goes wrong during the construction
*/
public Widget(Widget parent, String name, String preset) throws GUIException {
throw new GUIException("Could not create the preset [" + preset + "] because there is no implementation");
}
/**
* Create a widget
* @param parent The widget's parent
* @param name The name of the widget
* @throws GUIException If something goes wrong during the construction
*/
public Widget(Widget parent, String name) throws GUIException {
children = new ArrayList();
this.name = name;
this.parent = parent;
if (parent != null) {
widgetMap = parent.widgetMap;
} else {
widgetMap = new HashMap();
}
}
/**
* Return the widget's name
*/
public String getName() {
return name;
}
/**
* Change the widget's name. This should
* only be used by the GUI Builder
*/
public void setName(String newName) {
if (name != null) {
widgetMap.remove(name);
}
if (newName != null) {
widgetMap.put(newName, this);
}
name = newName;
}
/**
* Recursively search the widget tree for
* a child widget
*/
public Widget getChildWidgetByName(String name) {
for (int i = 0; i < children.size(); i++) {
Widget widget = (Widget) children.get(i);
if (name.equals(widget.getName())) {
return widget;
} else {
Widget result = widget.getChildWidgetByName(name);
if (result != null)
return result;
}
}
return null;
}
/**
* Lookup a widget in the widget map. Note that
* this could be below or above the current widget.
* Search will, however, be restricted to one widget tree
*/
public Widget getWidget(String name) {
return (Widget) widgetMap.get(name);
}
/**
* Return the parent widget
*/
public Widget getParentWidget() {
return parent;
}
/**
* Return a parent widget with a given name
*/
public Widget getParentWidgetByName(String name) {
if (parent == null)
return null;
if (name.equals(parent.getName()))
return parent;
else
return parent.getParentWidgetByName(name);
}
/**
* Return the first parent widget with is an
* instance of the given class
*/
public Widget getParentWidgetByClass(Class type) {
if (parent == null)
return null;
if (type.equals(parent.getClass()))
return parent;
else
return parent.getParentWidgetByClass(type);
}
/**
* Return the amount of child widgets
*/
public int getChildCount() {
return children.size();
}
/**
* Return the child widget at the given index
*/
public Widget getChild(int index) {
return (Widget) children.get(index);
}
/**
* Return the index of a child
*/
public int getChildIndex(Widget child) {
return children.indexOf(child);
}
/**
* Set a property using the reflection api
*/
public void setProperty(String name, Object value) throws GUIException {
if (name.equals("helpid")) {
if (helpBroker == null)
throw new GUIException("JavaHelp has not been activated");
if (value != null)
helpBroker.enableHelp(getRealWidget(), (String) value, helpSet);
} else {
String cacheName = this.getClass().getName() + ":" + name + ":" + value.getClass().getName() + "S";
try {
Method setter = (Method) methodCache.get(cacheName);
if (setter == null) {
setter = findSetter(name, value);
methodCache.put(cacheName, setter);
}
setter.invoke(getRealWidget(), new Object[] { value });
} catch (InvocationTargetException e) {
throw new GUIException("Property setter threw an exception", e);
} catch (IllegalAccessException e) {
throw new GUIException("Property setter method is inaccessible", e);
}
}
}
/**
* Get a property using the reflection API
*/
public Object getProperty(String name) throws GUIException {
String cacheName = this.getClass().getName() + ":" + name + "G";
try {
Method getter = (Method) methodCache.get(cacheName);
if (getter == null) {
getter = findGetter(name);
methodCache.put(cacheName, getter);
}
return getter.invoke(getRealWidget(), EMPTY_ARRAY);
} catch (InvocationTargetException e) {
throw new GUIException("Property getter threw an exception", e);
} catch (IllegalAccessException e) {
throw new GUIException("Property getter method is inaccessible", e);
}
}
private Method findGetter(String name) throws GUIException {
Object widget = getRealWidget();
String setterName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
try {
return widget.getClass().getMethod(setterName, new Class[] {
});
} catch (Exception e) {
throw new GUIException(
"Cannot acquire getter method in widget ["
+ this.getClass().getName()
+ "] for property ["
+ name
+ "]",
e);
}
}
private Method findSetter(String name, Object value) throws GUIException {
Object widget = getRealWidget();
Class valueClass = value.getClass();
String setterName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
/* Prefer builtin types to their boxed counterparts */
if (valueClass.equals(Boolean.class))
valueClass = boolean.class;
else if (valueClass.equals(Integer.class))
valueClass = int.class;
else if (valueClass.equals(Long.class))
valueClass = long.class;
else if (valueClass.equals(Double.class))
valueClass = double.class;
else if (valueClass.equals(Float.class))
valueClass = float.class;
else if (valueClass.equals(Character.class))
valueClass = char.class;
else if (valueClass.equals(Short.class))
valueClass = short.class;
else if (valueClass.equals(Byte.class))
valueClass = byte.class;
try {
/* 1. Simple method */
return widget.getClass().getMethod(setterName, new Class[] { valueClass });
} catch (NoSuchMethodException e) {
/* 2. Tricky method */
Method methods[] = widget.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getName().equals(setterName)
&& method.getParameterTypes().length == 1
&& method.getParameterTypes()[0].isAssignableFrom(valueClass)) {
return method;
}
}
}
throw new GUIException(
"Cannot acquire setter method in widget ["
+ this.getClass().getName()
+ "] for property ["
+ name
+ "], value class ["
+ valueClass.getName()
+ "] and value ["
+ value
+ "]");
}
/**
* Notify the widget about a data model event
*/
public void modelChanged(ModelChangeEvent e) throws GUIException {
/* Do nothing, to be overwritten by subclasses */
}
/**
* Add a child widget with a constraint
*/
public void addChild(Widget widget, Object constraint) throws GUIException {
throw new GUIException(
"Child widget functionality not implemented for widget [" + this.getClass().getName() + "]");
}
/**
* Add a child widget to the widget tree
*/
public void addChild(Widget widget) throws GUIException {
children.add(widget);
String name = widget.getName();
if (name != null) {
if (!widgetMap.containsKey(name))
widgetMap.put(name, widget);
else
throw new GUIException("There is already a widget named [" + name + "]");
}
}
/**
* Return a structure containing the widget's children
*/
protected final List getChildren() {
return children;
}
/**
* Remove all of this widget's children from the
* widget tree
*/
public void removeAllChildWidgets() {
for (Iterator i = children.iterator(); i.hasNext();) {
Widget child = (Widget) i.next();
widgetMap.remove(child.getName());
i.remove();
}
}
/**
* Remove a child widget from the widget tree
*/
public void removeChildWidget(Widget widget) throws GUIException {
widgetMap.remove(widget.getName());
children.remove(widget);
}
/**
* Add a listener for a given event
*
* Every widget can support DnD, to enable it add a "drag"
* event or an event in the form "drop[mimeType1,mimeType2]"
*/
public void addListener(String event, String name, GUIEventListener listener) throws GUIException {
if (event.equals("drag")) {
DragHandler handler = new DragHandler(this, listener, name);
DragSource dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(getRealWidget(), DnDConstants.ACTION_COPY_OR_MOVE, handler);
} else if (event.startsWith("drop[") && event.endsWith("]")) {
try {
StringTokenizer tokenizer = new StringTokenizer(event.substring(5, event.length() - 1), ", ");
DataFlavor flavors[] = new DataFlavor[tokenizer.countTokens()];
int counter = 0;
while (tokenizer.hasMoreTokens()) {
flavors[counter++] = new DataFlavor(tokenizer.nextToken());
}
DropHandler handler = new DropHandler(this, listener, name, flavors);
new DropTarget(getRealWidget(), handler);
} catch (Exception e) {
throw new GUIException("Error while creating drop target support", e);
}
} else {
throw new GUIException(
"Event functionality not implemented for widget [" + this.getClass().getName() + "]");
}
}
/**
* Add a validator to this widget
*/
public void addValidator(Validator validator) {
if (validators == null)
validators = new ArrayList();
validators.add(validator);
}
/**
* Remove a validator from this widget
*/
public void removeValidator(Validator validator) {
if (validators != null)
validators.remove(validator);
}
/**
* Validate this widget
* @throws ValidationException If the widget did not validate
*/
public void validate() throws ValidationException, GUIException {
if (validators != null) {
for (int i = 0; i < validators.size(); i++) {
((Validator) validators.get(i)).validate(this);
}
}
}
/**
* Recursively validate the widget tree
* @throws ValidationException If the widget tree did not validate
*/
public void recursiveValidate() throws ValidationException, GUIException {
validate();
for (int i = 0; i < children.size(); i++) {
((Widget) children.get(i)).recursiveValidate();
}
}
/**
* Set a new data model for the whole widget tree
*/
public void recursiveSetDataModel(MapDataModel model) throws GUIException {
setDataModel(model);
for (int i = 0; i < children.size(); i++) {
((Widget) children.get(i)).recursiveSetDataModel(model);
}
}
/**
* Return whether this widget has any validators
* associated with it
*/
public boolean hasValidators() {
return (validators != null && validators.size() > 0);
}
/**
* Return the underlying swing component
*/
public abstract Component getWidget();
/**
* Return information about the widget's available
* properties. This is mostly used by the Builder
*/
public WidgetInfo getWidgetInfo() {
return widgetInfo;
}
/**
* Sometimes, a widget needs to be encapsulated - for example
* inside a JPanel. This function then returns the actual widget
*/
public Component getRealWidget() {
return getWidget();
}
/**
* This is called after a component has been completely constructed
* from an XML description file. Can be overwritten by subclasses if
* such funtionality is required.
*/
public void finalizeConstruction() throws GUIException {
/* Do nothing by default */
}
/**
* Return an ASCII-Art representation of the tree
*/
public String dumpStructure() {
return dumpStructure(new Stack());
}
private String dumpStructure(Stack stack) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < stack.size(); i++) {
boolean last = ((Boolean) stack.get(i)).booleanValue();
if ((i == stack.size() - 1) && last) {
buf.append(" `");
} else {
if (last)
buf.append(" ");
else
buf.append(" |");
}
}
buf.append("-o ").append(toString()).append('\n');
for (int i = 0; i < children.size(); i++) {
stack.push(new Boolean(children.size() - 1 == i));
buf.append(((Widget) children.get(i)).dumpStructure(stack));
stack.pop();
}
return buf.toString();
}
/**
* Return a string description of this widget
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(getClass().getName());
if (name != null)
buf.append("[name='" + name + "']");
return buf.toString();
}
/* =============== JComponent Base funtions ================= */
/**
* Set a tooltip for this widget
*/
public void setTooltipText(String text) throws GUIException {
try {
((JComponent) getWidget()).setToolTipText(text);
} catch (ClassCastException e) {
throw new GUIException("Widget does not contain a JComponent : " + getWidget().getClass().toString(), e);
}
}
/**
* Request the focus
*/
public void requestFocus() {
getWidget().requestFocus();
}
/**
* Set whether this widget is enabled or disabled
*/
public void setEnabled(boolean enabled) throws GUIException {
try {
((JComponent) getRealWidget()).setEnabled(enabled);
} catch (ClassCastException e) {
throw new GUIException("Widget does not contain a JComponent : " + getWidget().getClass().toString(), e);
}
}
/**
* Revalidate the swing widget tree
*/
public void revalidate() throws GUIException {
try {
((JComponent) getWidget()).revalidate();
getWidget().repaint();
} catch (ClassCastException e) {
throw new GUIException("Widget does not contain a JComponent : " + getWidget().getClass().toString(), e);
}
}
/**
* Set the application's help set. Note that this
* help set is static to the whole application
*/
public static void setHelpSet(HelpSet helpSet) {
Widget.helpSet = helpSet;
helpBroker = helpSet.createHelpBroker();
}
/**
* Get the application's help set. Note that this
* help is static to the whole application
*/
public static HelpSet getHelpSet() {
return helpSet;
}
/**
* Get the application's help broker. Note that this
* help is static to the whole application
*/
public static HelpBroker getHelpBroker() {
return helpBroker;
}
}