001: /*
002: * Beryl - A web platform based on XML, XSLT and Java
003: * This file is part of the Beryl XML GUI
004: *
005: * Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011:
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-3107 USA
020: */
021:
022: package org.beryl.gui;
023:
024: import java.awt.Component;
025: import java.awt.Font;
026: import java.awt.datatransfer.DataFlavor;
027: import java.awt.dnd.DnDConstants;
028: import java.awt.dnd.DragSource;
029: import java.awt.dnd.DropTarget;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032: import java.util.ArrayList;
033: import java.util.HashMap;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Stack;
037: import java.util.StringTokenizer;
038:
039: import javax.help.HelpBroker;
040: import javax.help.HelpSet;
041: import javax.swing.JComponent;
042: import javax.swing.UIManager;
043:
044: import org.beryl.gui.model.MapDataModel;
045: import org.beryl.gui.model.ModelChangeEvent;
046: import org.beryl.gui.validators.ValidationException;
047: import org.beryl.gui.validators.Validator;
048:
049: import cz.autel.dmi.HIGConstraints;
050:
051: /**
052: * <tt>Widget</tt> is the base class of all components using the
053: * Beryl XML GUI framework.
054: */
055:
056: public abstract class Widget extends AbstractView implements
057: LFConstants {
058: /* Method cache used to accelerate swing widget introspection */
059: private static HashMap methodCache = new HashMap();
060: /* Commonly used inside widget sublcasses */
061: protected static HIGConstraints constraints = new HIGConstraints();
062: /* Widget property information */
063: protected static WidgetInfo widgetInfo = null;
064: /* Used to invoke getters via reflection */
065: private static final Object[] EMPTY_ARRAY = new Object[] {};
066: /* JavaHelp support */
067: protected static HelpBroker helpBroker = null;
068: protected static HelpSet helpSet = null;
069:
070: /* Members */
071: private ArrayList children = null;
072: private String name = null;
073: private Widget parent = null;
074: private HashMap widgetMap = null;
075: private ArrayList validators = null;
076:
077: static {
078: /* Add the basic properties which should be available to most widgets */
079: widgetInfo = new WidgetInfo(Widget.class);
080: widgetInfo.addProperty("anchor", "anchor", null);
081: widgetInfo.addProperty("background", "color", UIManager
082: .getDefaults().getColor("Label.background"));
083: widgetInfo.addProperty("foreground", "color", UIManager
084: .getDefaults().getColor("Label.foreground"));
085: widgetInfo.addProperty("font", "font", new Font("sans serif",
086: Font.PLAIN, 12));
087: widgetInfo.addProperty("enabled", "bool", Boolean.TRUE);
088: widgetInfo.addProperty("opaque", "bool", Boolean.TRUE);
089: widgetInfo.addProperty("helpid", "string", null);
090: }
091:
092: /**
093: * Create a widget with a predefined type
094: * @param parent The widget's parent
095: * @param name The name of the widget
096: * @param preset The preset to use
097: * @throws GUIException If something goes wrong during the construction
098: */
099: public Widget(Widget parent, String name, String preset)
100: throws GUIException {
101: throw new GUIException("Could not create the preset [" + preset
102: + "] because there is no implementation");
103: }
104:
105: /**
106: * Create a widget
107: * @param parent The widget's parent
108: * @param name The name of the widget
109: * @throws GUIException If something goes wrong during the construction
110: */
111: public Widget(Widget parent, String name) throws GUIException {
112: children = new ArrayList();
113: this .name = name;
114: this .parent = parent;
115: if (parent != null) {
116: widgetMap = parent.widgetMap;
117: } else {
118: widgetMap = new HashMap();
119: }
120: }
121:
122: /**
123: * Return the widget's name
124: */
125:
126: public String getName() {
127: return name;
128: }
129:
130: /**
131: * Change the widget's name. This should
132: * only be used by the GUI Builder
133: */
134: public void setName(String newName) {
135: if (name != null) {
136: widgetMap.remove(name);
137: }
138: if (newName != null) {
139: widgetMap.put(newName, this );
140: }
141:
142: name = newName;
143: }
144:
145: /**
146: * Recursively search the widget tree for
147: * a child widget
148: */
149:
150: public Widget getChildWidgetByName(String name) {
151: for (int i = 0; i < children.size(); i++) {
152: Widget widget = (Widget) children.get(i);
153: if (name.equals(widget.getName())) {
154: return widget;
155: } else {
156: Widget result = widget.getChildWidgetByName(name);
157: if (result != null)
158: return result;
159: }
160: }
161: return null;
162: }
163:
164: /**
165: * Lookup a widget in the widget map. Note that
166: * this could be below or above the current widget.
167: * Search will, however, be restricted to one widget tree
168: */
169: public Widget getWidget(String name) {
170: return (Widget) widgetMap.get(name);
171: }
172:
173: /**
174: * Return the parent widget
175: */
176: public Widget getParentWidget() {
177: return parent;
178: }
179:
180: /**
181: * Return a parent widget with a given name
182: */
183: public Widget getParentWidgetByName(String name) {
184: if (parent == null)
185: return null;
186: if (name.equals(parent.getName()))
187: return parent;
188: else
189: return parent.getParentWidgetByName(name);
190: }
191:
192: /**
193: * Return the first parent widget with is an
194: * instance of the given class
195: */
196:
197: public Widget getParentWidgetByClass(Class type) {
198: if (parent == null)
199: return null;
200: if (type.equals(parent.getClass()))
201: return parent;
202: else
203: return parent.getParentWidgetByClass(type);
204: }
205:
206: /**
207: * Return the amount of child widgets
208: */
209:
210: public int getChildCount() {
211: return children.size();
212: }
213:
214: /**
215: * Return the child widget at the given index
216: */
217: public Widget getChild(int index) {
218: return (Widget) children.get(index);
219: }
220:
221: /**
222: * Return the index of a child
223: */
224: public int getChildIndex(Widget child) {
225: return children.indexOf(child);
226: }
227:
228: /**
229: * Set a property using the reflection api
230: */
231: public void setProperty(String name, Object value)
232: throws GUIException {
233: if (name.equals("helpid")) {
234: if (helpBroker == null)
235: throw new GUIException(
236: "JavaHelp has not been activated");
237: if (value != null)
238: helpBroker.enableHelp(getRealWidget(), (String) value,
239: helpSet);
240: } else if (name.equals("helpid.popup")) {
241: if (helpBroker == null)
242: throw new GUIException(
243: "JavaHelp has not been activated");
244: if (value != null)
245: helpBroker.enableHelpKey(getRealWidget(),
246: (String) value, helpSet, "javax.help.Popup",
247: null);
248: } else {
249: String cacheName = this .getClass().getName() + ":" + name
250: + ":" + value.getClass().getName() + "S";
251: try {
252: Method setter = (Method) methodCache.get(cacheName);
253:
254: if (setter == null) {
255: setter = findSetter(name, value);
256: methodCache.put(cacheName, setter);
257: }
258: setter.invoke(getRealWidget(), new Object[] { value });
259: } catch (InvocationTargetException e) {
260: throw new GUIException(
261: "Property setter threw an exception", e);
262: } catch (IllegalAccessException e) {
263: throw new GUIException(
264: "Property setter method is inaccessible", e);
265: }
266: }
267: }
268:
269: /**
270: * Get a property using the reflection API
271: */
272: public Object getProperty(String name) throws GUIException {
273: String cacheName = this .getClass().getName() + ":" + name + "G";
274: try {
275: Method getter = (Method) methodCache.get(cacheName);
276:
277: if (getter == null) {
278: getter = findGetter(name);
279: methodCache.put(cacheName, getter);
280: }
281: return getter.invoke(getRealWidget(), EMPTY_ARRAY);
282: } catch (InvocationTargetException e) {
283: throw new GUIException(
284: "Property getter threw an exception", e);
285: } catch (IllegalAccessException e) {
286: throw new GUIException(
287: "Property getter method is inaccessible", e);
288: }
289: }
290:
291: private Method findGetter(String name) throws GUIException {
292: Object widget = getRealWidget();
293: String setterName = "get" + name.substring(0, 1).toUpperCase()
294: + name.substring(1);
295: try {
296: return widget.getClass().getMethod(setterName,
297: new Class[] {});
298: } catch (Exception e) {
299: throw new GUIException(
300: "Cannot acquire getter method in widget ["
301: + this .getClass().getName()
302: + "] for property [" + name + "]", e);
303: }
304: }
305:
306: private Method findSetter(String name, Object value)
307: throws GUIException {
308: Object widget = getRealWidget();
309: Class valueClass = value.getClass();
310: String setterName = "set" + name.substring(0, 1).toUpperCase()
311: + name.substring(1);
312:
313: /* Prefer builtin types to their boxed counterparts */
314: if (valueClass.equals(Boolean.class))
315: valueClass = boolean.class;
316: else if (valueClass.equals(Integer.class))
317: valueClass = int.class;
318: else if (valueClass.equals(Long.class))
319: valueClass = long.class;
320: else if (valueClass.equals(Double.class))
321: valueClass = double.class;
322: else if (valueClass.equals(Float.class))
323: valueClass = float.class;
324: else if (valueClass.equals(Character.class))
325: valueClass = char.class;
326: else if (valueClass.equals(Short.class))
327: valueClass = short.class;
328: else if (valueClass.equals(Byte.class))
329: valueClass = byte.class;
330:
331: try {
332: /* 1. Simple method */
333: return widget.getClass().getMethod(setterName,
334: new Class[] { valueClass });
335: } catch (NoSuchMethodException e) {
336: /* 2. Tricky method */
337: Method methods[] = widget.getClass().getMethods();
338: for (int i = 0; i < methods.length; i++) {
339: Method method = methods[i];
340: if (method.getName().equals(setterName)
341: && method.getParameterTypes().length == 1
342: && method.getParameterTypes()[0]
343: .isAssignableFrom(valueClass)) {
344: return method;
345: }
346: }
347: }
348: throw new GUIException(
349: "Cannot acquire setter method in widget ["
350: + this .getClass().getName()
351: + "] for property [" + name
352: + "], value class [" + valueClass.getName()
353: + "] and value [" + value + "]");
354: }
355:
356: /**
357: * Notify the widget about a data model event
358: */
359:
360: public void modelChanged(ModelChangeEvent e) throws GUIException {
361: /* Do nothing, to be overwritten by subclasses */
362: }
363:
364: /**
365: * Add a child widget with a constraint
366: */
367:
368: public void addChild(Widget widget, Object constraint)
369: throws GUIException {
370: throw new GUIException(
371: "Child widget functionality not implemented for widget ["
372: + this .getClass().getName() + "]");
373: }
374:
375: /**
376: * Add a child widget to the widget tree
377: */
378: public void addChild(Widget widget) throws GUIException {
379: children.add(widget);
380: String name = widget.getName();
381: if (name != null) {
382: if (!widgetMap.containsKey(name))
383: widgetMap.put(name, widget);
384: else
385: throw new GUIException(
386: "There is already a widget named [" + name
387: + "]");
388: }
389: }
390:
391: /**
392: * Return a structure containing the widget's children
393: */
394:
395: protected final List getChildren() {
396: return children;
397: }
398:
399: /**
400: * Remove all of this widget's children from the
401: * widget tree
402: */
403:
404: public void removeAllChildWidgets() {
405: for (Iterator i = children.iterator(); i.hasNext();) {
406: Widget child = (Widget) i.next();
407: widgetMap.remove(child.getName());
408: i.remove();
409: }
410: }
411:
412: /**
413: * Remove a child widget from the widget tree
414: */
415:
416: public void removeChildWidget(Widget widget) throws GUIException {
417: widgetMap.remove(widget.getName());
418: children.remove(widget);
419: }
420:
421: /**
422: * Add a listener for a given event
423: *
424: * Every widget can support DnD, to enable it add a "drag"
425: * event or an event in the form "drop[mimeType1,mimeType2]"
426: */
427: public void addListener(String event, String name,
428: GUIEventListener listener) throws GUIException {
429: if (event.equals("drag")) {
430: DragHandler handler = new DragHandler(this , listener, name);
431: DragSource dragSource = DragSource.getDefaultDragSource();
432: dragSource.createDefaultDragGestureRecognizer(
433: getRealWidget(), DnDConstants.ACTION_COPY_OR_MOVE,
434: handler);
435: } else if (event.startsWith("drop[") && event.endsWith("]")) {
436: try {
437: StringTokenizer tokenizer = new StringTokenizer(event
438: .substring(5, event.length() - 1), ", ");
439: DataFlavor flavors[] = new DataFlavor[tokenizer
440: .countTokens()];
441:
442: int counter = 0;
443: while (tokenizer.hasMoreTokens()) {
444: flavors[counter++] = new DataFlavor(tokenizer
445: .nextToken());
446: }
447:
448: DropHandler handler = new DropHandler(this , listener,
449: name, flavors);
450: new DropTarget(getRealWidget(), handler);
451: } catch (Exception e) {
452: throw new GUIException(
453: "Error while creating drop target support", e);
454: }
455: } else {
456: throw new GUIException("Event [" + name
457: + "] not supported by widget ["
458: + this .getClass().getName() + "]");
459: }
460: }
461:
462: /**
463: * Add a validator to this widget
464: */
465:
466: public void addValidator(Validator validator) {
467: if (validators == null)
468: validators = new ArrayList();
469: validators.add(validator);
470: }
471:
472: /**
473: * Remove a validator from this widget
474: */
475:
476: public void removeValidator(Validator validator) {
477: if (validators != null)
478: validators.remove(validator);
479: }
480:
481: /**
482: * Validate this widget
483: * @throws ValidationException If the widget did not validate
484: */
485:
486: public void validate() throws ValidationException, GUIException {
487: if (validators != null) {
488: for (int i = 0; i < validators.size(); i++) {
489: ((Validator) validators.get(i)).validate(this );
490: }
491: }
492: }
493:
494: /**
495: * Recursively validate the widget tree
496: * @throws ValidationException If the widget tree did not validate
497: */
498:
499: public void recursiveValidate() throws ValidationException,
500: GUIException {
501: validate();
502: for (int i = 0; i < children.size(); i++) {
503: ((Widget) children.get(i)).recursiveValidate();
504: }
505: }
506:
507: /**
508: * Set a new data model for the whole widget tree
509: */
510:
511: public void recursiveSetDataModel(MapDataModel model)
512: throws GUIException {
513: setDataModel(model);
514: for (int i = 0; i < children.size(); i++) {
515: ((Widget) children.get(i)).recursiveSetDataModel(model);
516: }
517: }
518:
519: /**
520: * Return whether this widget has any validators
521: * associated with it
522: */
523:
524: public boolean hasValidators() {
525: return (validators != null && validators.size() > 0);
526: }
527:
528: /**
529: * Return the underlying swing component
530: */
531: public abstract Component getWidget();
532:
533: /**
534: * Return information about the widget's available
535: * properties. This is mostly used by the Builder
536: */
537: public WidgetInfo getWidgetInfo() {
538: return widgetInfo;
539: }
540:
541: /**
542: * Sometimes, a widget needs to be encapsulated - for example
543: * inside a JPanel. This function then returns the actual widget
544: */
545: public Component getRealWidget() {
546: return getWidget();
547: }
548:
549: /**
550: * This is called after a component has been completely constructed
551: * from an XML description file. Can be overwritten by subclasses if
552: * such funtionality is required.
553: */
554: public void finalizeConstruction() throws GUIException {
555: /* Do nothing by default */
556: }
557:
558: /**
559: * Return an ASCII-Art representation of the tree
560: */
561: public String dumpStructure() {
562: return dumpStructure(new Stack());
563: }
564:
565: private String dumpStructure(Stack stack) {
566: StringBuffer buf = new StringBuffer();
567: for (int i = 0; i < stack.size(); i++) {
568: boolean last = ((Boolean) stack.get(i)).booleanValue();
569: if ((i == stack.size() - 1) && last) {
570: buf.append(" `");
571: } else {
572: if (last)
573: buf.append(" ");
574: else
575: buf.append(" |");
576: }
577: }
578: buf.append("-o ").append(toString()).append('\n');
579: for (int i = 0; i < children.size(); i++) {
580: stack.push(new Boolean(children.size() - 1 == i));
581: buf.append(((Widget) children.get(i)).dumpStructure(stack));
582: stack.pop();
583: }
584: return buf.toString();
585: }
586:
587: /**
588: * Return a string description of this widget
589: */
590:
591: public String toString() {
592: StringBuffer buf = new StringBuffer();
593: buf.append(getClass().getName());
594:
595: if (name != null)
596: buf.append("[name='" + name + "']");
597: return buf.toString();
598: }
599:
600: /* =============== JComponent Base funtions ================= */
601:
602: /**
603: * Set a tooltip for this widget
604: */
605: public void setTooltipText(String text) throws GUIException {
606: try {
607: ((JComponent) getWidget()).setToolTipText(text);
608: } catch (ClassCastException e) {
609: throw new GUIException(
610: "Widget does not contain a JComponent : "
611: + getWidget().getClass().toString(), e);
612: }
613: }
614:
615: /**
616: * Request the focus
617: */
618: public void requestFocus() {
619: getWidget().requestFocus();
620: }
621:
622: /**
623: * Set whether this widget is enabled or disabled
624: */
625: public void setEnabled(boolean enabled) throws GUIException {
626: try {
627: getRealWidget().setEnabled(enabled);
628: } catch (ClassCastException e) {
629: throw new GUIException(
630: "Widget does not contain a JComponent : "
631: + getWidget().getClass().toString(), e);
632: }
633: }
634:
635: /**
636: * Revalidate the swing widget tree
637: */
638: public void revalidate() throws GUIException {
639: try {
640: ((JComponent) getWidget()).revalidate();
641: getWidget().repaint();
642: } catch (ClassCastException e) {
643: throw new GUIException(
644: "Widget does not contain a JComponent : "
645: + getWidget().getClass().toString(), e);
646: }
647: }
648:
649: /**
650: * Set the application's help set. Note that this
651: * help set is static to the whole application
652: */
653: public static void setHelpSet(HelpSet helpSet) {
654: Widget.helpSet = helpSet;
655: helpBroker = helpSet.createHelpBroker();
656: }
657:
658: /**
659: * Get the application's help set. Note that this
660: * help is static to the whole application
661: */
662: public static HelpSet getHelpSet() {
663: return helpSet;
664: }
665:
666: /**
667: * Get the application's help broker. Note that this
668: * help is static to the whole application
669: */
670: public static HelpBroker getHelpBroker() {
671: return helpBroker;
672: }
673: }
|