package net.xoetrope.builder;
import java.io.Reader;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.ResourceBundle;
import java.util.Vector;
import java.awt.Component;
import java.awt.Container;
import java.awt.TextComponent;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xml.XmlSource;
import net.xoetrope.xui.XAppender;
import net.xoetrope.xui.XAttributedComponent;
import net.xoetrope.xui.XContentPane;
import net.xoetrope.xui.XLayoutHelper;
import net.xoetrope.xui.XModelHolder;
import net.xoetrope.xui.XPage;
import net.xoetrope.xui.XPageDisplay;
import net.xoetrope.xui.XPageLoader;
import net.xoetrope.xui.XProjectManager;
import net.xoetrope.xui.XRadioButtonGroup;
import net.xoetrope.xui.XResourceManager;
import net.xoetrope.xui.XTarget;
import net.xoetrope.xui.build.BuildProperties;
import net.xoetrope.xui.data.XDataBinding;
import net.xoetrope.xui.data.XDataBindingFactory;
import net.xoetrope.xui.data.XListBinding;
import net.xoetrope.xui.data.XModel;
import net.xoetrope.xui.data.XModelAdapter;
import net.xoetrope.xui.data.XRadioBinding;
import net.xoetrope.xui.data.XStateBinding;
import net.xoetrope.xui.data.XTextBinding;
import net.xoetrope.xui.style.XStyleFactory;
import net.xoetrope.xui.validation.XValidationFactory;
import java.util.PropertyResourceBundle;
import java.awt.Cursor;
import net.xoetrope.xui.events.XListenerHelper;
import net.xoetrope.xui.data.XCustomDataBinding;
import net.xoetrope.xui.XComponentFactory;
import net.xoetrope.xui.XTextHolder;
/**
* A builder of XUI pages from an XML source. An instance of the class is setup
* by XApplet as a secondary page loader so that if a class file for the
* page is not found then an attempt is made to load the page from an XML file
* with the same base name.
* <br>
* Components, Menus, Events, Validations and Data Binding can be configured via the
* XML declarations.
* <br>
* The loading process itself relies heavily on the component factories to do
* the bulk of the work involved in constructing and adding components. The XPage
* class also performs much of the work involved in adding events and binding data.
* <p>Copyright (c) Xoetrope Ltd., 2002-2003</p>
* <p>License: see license.txt</p>
* $Revision: 1.29 $
*/
public class XuiBuilder implements XPageLoader
{
protected String packageName;
protected XStyleFactory componentFactory;
protected Object checkBoxGroup;
protected XPage page;
protected XPage rootPage;
protected XAppender menuBar;
protected Hashtable validationFactories;
protected static Hashtable currentAttributes;
protected XLayoutHelper layoutHelper;
private static int nextChildId;
/**
* Construct an instance of the builder with the default package set to net.xoetrope.awt
*/
public XuiBuilder()
{
init( XPage.XUI_AWT_PACKAGE );
}
/**
* Construct a new builder and set the default package
* @param packageName the name of the default package e.g. net.xoetrope.awt
*/
public XuiBuilder( String packageName )
{
init( packageName );
}
/**
* Initialize the builder
* @param packageName the default componnet package or use AWT if none is specified
*/
protected void init( String packageName )
{
if ( packageName == null )
packageName = XPage.XUI_AWT_PACKAGE;
validationFactories = new Hashtable( 2 );
componentFactory = new XStyleFactory( packageName );
layoutHelper = componentFactory.getLayoutHelper();
}
/**
* Set the default package name. The default package name is used when
* creating widgets such that a button class like XButton is instatiated as
* <defPackageName>.XButton.class. By default this expands to
* net.xoetrope.awt.XButton.clas
* @param defPackageName the default package name
*/
public void setPackageName( String defPackageName )
{
if ( defPackageName == null )
packageName = XPage.XUI_AWT_PACKAGE;
else
packageName = defPackageName;
componentFactory = new XStyleFactory( packageName );
}
/**
* Loads an XPage via a reader obtained from the XResourceManager (searches
* the classpath). The pageName is assumed to be the name of an XML file. For
* example if the pageName is 'welcome' then the 'welcome.xml' file is read as
* a UTF8 encoded XML file (by default).
* @param defPackageName the package or path to the page
* @param pageName the page name or the name of the class implementing the page
* @param include true if the page to be loaded is being included in another
* page in which case any class attribute of the included page is ignored
* @return the page
*/
public XPage loadPage( String defPackageName, String pageName, boolean include )
{
packageName = defPackageName;
Reader r = null;
try {
r = XResourceManager.getBufferedReader( pageName + ".xml", null );
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "File NOT found: " + pageName );
}
if ( r == null )
return null;
return readPage( r, pageName, include );
}
/**
* Loads a frameset. If a frameset is used then the calls to the XPageDisplay
* interface should use the form that specifies the target areas. By default
* frames are specified in a file (if found) called frames.xml. The frames
* are added to a container laid out with a BorderLayout.
* <br>
* Frames can be used to control how parts of the screen are updated. The
* default page updated by the page transition/swapping methods is the 'content'
* frame. Other areas can be used as non swapping areas for toolbars,
* navigation controls, sidebars or status areas. Frames may also be hidden
* and shown as needed.
* @param defPackageName the package or path to the page
* @param frameSetName the page name or the name of the class implementing the page
* @param pageDisplay the object that will display the pages and frameset
*/
public void loadFrames( String defPackageName, String frameSetName, XPageDisplay pageDisplay )
{
packageName = defPackageName;
Reader r = null;
try {
r = XResourceManager.getBufferedReader( frameSetName + ".xml", null );
}
catch ( Exception ex ) {
}
if ( r == null )
return;
XmlElement model = XmlSource.read( r );
Vector componentNodes = model.getChildren();
int numChildren = componentNodes.size();
for ( int i = 0; i < numChildren; i++ ) {
XmlElement childNode = ( XmlElement )componentNodes.elementAt( i );
String typeStr = childNode.getName();
if ( typeStr.compareTo( "Frame" ) == 0 ) {
try {
String targetName = childNode.getAttribute( "name" );
XTarget target = new XTarget( targetName,
new Integer( childNode.getAttribute( "width" ) ).intValue(),
new Integer( childNode.getAttribute( "height" ) ).intValue() );
Object constraint = layoutHelper.getConstraint( childNode.getAttribute( "constraint" ));
target.setName( targetName );
pageDisplay.addTarget( target, constraint );
XPage targetPage = XProjectManager.getPageManager().loadPage( childNode.getAttribute( "content" ));
pageDisplay.displayPage( targetPage, targetName );
}
catch ( NumberFormatException ex1 ) {
}
}
}
}
/**
* Read an XML description of the page and construct a new XPage. An instance
* of the class specified by the class attribute is constructed or else an
* instance of XPage if no class attribute is specified. The new page is
* populated but is not yet added to its parent.
* <br>
* The startup file parameter 'DefaultClass' is used to obtain a default for
* each page's class if a class parameter is not specified in the page's XML
* <br>
* The startup file parameter 'Validations' is used to obtain a default for
* each page's set of validation rules
*
* @param reader a input stream from which to read the page
* @param pageName the name of the page
* @param include the page to be loaded is being included in another page
* @return the page
*/
public XPage readPage( Reader reader, String pageName, boolean include )
{
XmlElement model = XmlSource.read( reader );
setupPage( model, pageName, include );
XmlElement componentsNode = null;
XmlElement eventsNode = null;
XmlElement dataNode = null;
XmlElement menuNode = null;
XmlElement validationsNode = null;
Vector componentNodes = model.getChildren();
int numChildren = componentNodes.size();
for ( int i = 0; i < numChildren; i++ ) {
XmlElement childNode = ( XmlElement )componentNodes.elementAt( i );
String typeStr = childNode.getName().toLowerCase();
if ( typeStr.compareTo( "components" ) == 0 )
componentsNode = childNode;
else if ( typeStr.compareTo( "events" ) == 0 )
eventsNode = childNode;
else if ( typeStr.compareTo( "data" ) == 0 )
dataNode = childNode;
else if ( typeStr.compareTo( "validations" ) == 0 )
validationsNode = childNode;
else if ( typeStr.compareTo( "menubar" ) == 0 )
menuNode = childNode;
else if ( typeStr.compareTo( "include" ) == 0 ) {
loadPage( packageName, childNode.getAttribute( "file" ), true );
componentFactory.setParentComponent( (Container)page );
}
}
if ( menuNode != null )
addMenu( page, menuNode );
if ( componentsNode != null )
addComponents( (Container)( include ? componentFactory.getParentComponent() : page ), componentsNode );
if ( dataNode != null )
addBindings( page, dataNode );
if ( eventsNode != null )
addEvents( page, eventsNode );
if ( validationsNode != null )
addValidations( page, validationsNode );
// String styleValue = ( String )model.getAttribute( "style" );
// if ( styleValue != null )
// currentResource.setStyleName( page, styleValue );
page.validate();
return page;
}
/**
* Loads a class as the basis for a page
* @param className the full class name
* @return an instance of the XPage class or a derivative
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
protected XPage loadClass( String className ) throws ClassNotFoundException, IllegalAccessException, InstantiationException
{
return ( XPage )Class.forName( className ).newInstance();
}
/**
* Loads the page based on the contents of the page tag or by using default
* values.
*
* @param pageName the name of the page
* @param include the page to be loaded is being included in another page
* @return the page
*/
protected void setupPage( XmlElement model, String pageName, boolean include )
{
String className = model.getAttribute( "class" );
if (( className == null ) || ( className.length() == 0 )) {
// Try to get a default startup class name if none has been specified in the XML
try {
className = XProjectManager.getCurrentProject().getStartupParam( "DefaultClass" );
}
catch ( Exception ex ) {
}
if ( className == null )
className = "net.xoetrope.xui.XPage";
}
if ( !include ) {
if ( ( className.indexOf( '.' ) <= 0 ) && ( packageName.length() > 1 ) )
className = packageName + className;
try {
page = loadClass( className );
}
catch ( Exception e ) {
if ( BuildProperties.DEBUG )
DebugLogger.trace( "Unable to load the named class: " + className );
page = new XPage();
}
setPageName( pageName );
// Perhaps use classname by default
// or check for existance of the file within XPage ... might add delay in constructing non validated pages
try {
String validationName = model.getAttribute( "Validations" );
if ( validationName == null || validationName.length() == 0 )
validationName = XProjectManager.getCurrentProject().getStartupParam( "Validations" );
if ( validationName != null ) {
if ( BuildProperties.DEBUG )
DebugLogger.trace( "Reading validations: " + validationName );
page.setValidationFactory( getValidationFactory( validationName ) );
}
}
catch ( Exception ex ) {
ex.printStackTrace();
}
if ( page instanceof XContentPane )
componentFactory.setParentComponent( (( XContentPane )page).getContentPane() );
else
componentFactory.setParentComponent( ( Container )page );
String styleName = model.getAttribute( "style" );
if ( styleName != null )
componentFactory.applyStyle( page, styleName );
}
int width = 0, height = 0;
String layoutMgr = null;
Enumeration attribNamesEnum = model.enumerateAttributeNames();
while ( attribNamesEnum.hasMoreElements()) {
String attribName = ( String )attribNamesEnum.nextElement();
String attribValue = ( String )model.getAttribute( attribName );
if ( attribName.compareTo( "class" ) == 0 )
;
else if ( attribName.compareTo( "resource" ) == 0 ) {
String resName;
if (( attribValue != null ) && ( attribValue.length() > 0 ))
resName = page.evaluatePath( attribValue );
else
resName = XResourceManager.getStartupParam( "Language" );
componentFactory.setResourceBundle( resName );
page.getComponentFactory().setResourceBundle( resName );
}
else if ( attribName.compareTo( "layout" ) == 0 ) {
if ( attribValue != null )
layoutMgr = attribValue;
}
else
page.setAttribute( attribName, null, evaluateAttribute( page, attribValue ));
setPageAttribute( page, attribName, attribValue );
}
if ( layoutMgr != null )
componentFactory.addLayout( page, layoutHelper.getLayoutType( layoutMgr ), currentAttributes );
rootPage = (XPage)page;
}
/**
* Get a page attribute (this version does nothing)
* @param c
* @param name
* @param value
*/
public void setPageAttribute( Component c, String name, String value )
{
}
/**
* Get the value of an attribute.
* @param page the page being loaded
* @param attributeValue the raw value of the attribute
* @return the evaluated value of the attribute
*/
public Object evaluateAttribute( XPage page, String attributeValue )
{
return page.evaluateAttribute( attributeValue );
}
/**
* Adds the elements specified by the Components element and its children
* @param page the new page object
* @param model the Components XML element (and implicitly its children)
*/
protected void addComponents( Component page, XmlElement model )
{
// componentFactory.setParentComponent( (Container)page );
Vector componentNodes = model.getChildren();
int numChildren = componentNodes.size();
for ( int i = 0; i < numChildren; i++ ) {
XmlElement childNode = ( XmlElement )componentNodes.elementAt( i );
addComponent( childNode );
}
}
/**
* Adds the event handlers. Events are specified in the XML as having 'type'
* (e.g. MouseHandler, ActionHandler etc...), 'name' (the method name to be
* invoked) and 'target' (the name of the component to which the handler is
* attached) attributes
* @param page The page that contains the response methods
* @param model the 'events' XML element
*/
protected void addEvents( XPage page, XmlElement model )
{
String typeStr, methodStr, targetStr, cursorStr;
cursorStr = "";
Vector eventNodes = model.getChildren();
int numChildren = eventNodes.size();
for ( int i = 0; i < numChildren; i++ ) {
typeStr = methodStr = targetStr = null;
XmlElement childNode = ( XmlElement )eventNodes.elementAt( i );
try {
Enumeration attribNamesEnum = childNode.enumerateAttributeNames();
while ( attribNamesEnum.hasMoreElements()) {
String attribName = (String)attribNamesEnum.nextElement();
String attribValue = childNode.getAttribute( attribName );
if ( attribName.compareTo( "type" ) == 0 )
typeStr = attribValue;
else if ( attribName.compareTo( "method" ) == 0 )
methodStr = attribValue;
else if ( attribName.compareTo( "target" ) == 0 )
targetStr = attribValue;
else if ( attribName.compareTo( "cursor" ) == 0 )
cursorStr = attribValue;
else
page.setAttribute( attribName, targetStr, attribValue );
}
if ( typeStr.compareTo( "MenuHandler" ) == 0 )
page.addMenuHandler( getMenuItem( targetStr ), methodStr );
else {
Component targetComp = page.findComponent( targetStr );
if ( BuildProperties.DEBUG ) {
if ( targetComp == null )
DebugLogger.logError( "Unable to find the component '" + targetStr + "' named in the event element " + methodStr );
}
addHandler( page, targetComp, typeStr, methodStr );
if ( cursorStr.equals( "true" ))
targetComp.setCursor( Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ) );
}
}
catch ( Exception e )
{
if ( BuildProperties.DEBUG )
DebugLogger.logError( "While adding the event element: " + methodStr );
e.printStackTrace();
}
}
}
/**
* Adds an event handler.
* @param xpage The page that contains the response methods
* @param targetComp the component to which the event handler is added
* @param typeStr the type of handler
* @param name the name of the response method
*/
protected void addHandler( XPage xpage, Component targetComp, String typeStr, String name )
{
if ( typeStr.compareTo( "MouseHandler" ) == 0 )
page.addMouseHandler( targetComp, name );
else if ( typeStr.compareTo( "MouseMotionHandler" ) == 0 )
page.addMouseMotionHandler( targetComp, name );
else if ( typeStr.compareTo( "ActionHandler" ) == 0 )
page.addActionHandler( targetComp, name );
else if ( typeStr.compareTo( "FocusHandler" ) == 0 )
page.addFocusHandler( targetComp, name );
else if ( typeStr.compareTo( "ItemHandler" ) == 0 )
page.addItemHandler( targetComp, name );
else if ( typeStr.compareTo( "KeyHandler" ) == 0 )
page.addKeyHandler( targetComp, name );
else if ( typeStr.compareTo( "TextHandler" ) == 0 )
page.addTextHandler( targetComp, name );
else {
try {
( ( XListenerHelper )targetComp ).addHandler( page, typeStr, name );
}
catch ( NoSuchMethodException ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to add the event handler method: " + name );
}
}
}
/**
* Register a data binding factory. An add-on library or plug-in may wish to
* customize the data bindings, particularly if it provides extended model
* node types. Such extended nodes may need adapters to be useful for some or
* all widgets and other binding contexts. Registration of the binding factory
* allows the necessary adapters and bindings to be created on request.
* @param fact the new binding factory
*/
public static void registerBindingFactory( XDataBindingFactory fact )
{
XProjectManager.getCurrentProject().getBindingsFactories().addElement( fact );
}
/**
* Adds data bindings to the page.
* @param page the page to which the component/data bindings are added
* @param model the data model
*/
protected void addBindings( XPage page, XmlElement model )
{
if ( model == null )
return;
String nameStr = null, srcStr, adapterStr, typeStr, attribStr;
XModel rootNode = XProjectManager.getModel();
Vector eventNodes = model.getChildren();
int numChildren = eventNodes.size();
for ( int i = 0; i < numChildren; i++ ) {
XmlElement childNode = ( XmlElement )eventNodes.elementAt( i );
try {
nameStr = childNode.getAttribute( "target" );
srcStr = childNode.getAttribute( "source" );
typeStr = childNode.getAttribute( "type" );
attribStr = childNode.getAttribute( "attrib" );
XModel srcModel = ( XModel )rootNode.get( page.evaluatePath( srcStr ));
Component targetComp = page.findComponent( nameStr );
XDataBinding factoryBinding = getFactoryBinding( targetComp, srcModel, childNode );
if ( factoryBinding != null )
page.addBinding( factoryBinding );
else {
if ( typeStr != null && typeStr.compareTo( "custom" ) == 0 ) {
String className = childNode.getAttribute( "class" );
XDataBinding binding = ( XDataBinding )Class.forName( className ).newInstance();
((XCustomDataBinding)binding).setup( targetComp, childNode );
page.addBinding( binding );
}
else {
String className = targetComp.getClass().getName();
if ( className.indexOf( "XComboBox" ) >= 0 ) {
XListBinding binding;
// Check for a specific binding
adapterStr = childNode.getAttribute( "adapter" );
if ( adapterStr != null ) {
// Assume a table is bound to the node
XModelAdapter adapter = ( XModelAdapter ) Class.forName( adapterStr ).newInstance();
XModel tableModel = ( XModel )rootNode.get( srcStr );
adapter.setModel( tableModel );
binding = new XListBinding( targetComp, adapter );
}
else { // Otherwise static data is being bound
String destStr = childNode.getAttribute( "output" );
String saveStr = childNode.getAttribute( "saveToSource" );
if ( destStr == null )
destStr = srcStr;
XModel dstModel = (XModel)rootNode.get( page.evaluatePath( destStr ) );
binding = new XListBinding( targetComp, srcStr, destStr, srcModel, dstModel, ( saveStr != null ? saveStr.equals( "true" ) : false ));
}
// Check and set the unique rows property
String attrib = childNode.getAttribute( "unique" );
if ( ( attrib != null ) && ( attrib.compareTo( "true" ) == 0 ) )
binding.setUseUnique(true);
page.addBinding(binding);
}
else if ( className.indexOf( "XEdit" ) >= 0 )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel, attribStr ) );
else if ( className.indexOf( "XTextArea" ) >= 0 )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel, attribStr ) );
else if ( className.indexOf( "XButton" ) >= 0 )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel ) );
else if ( className.indexOf( "XLabel" ) >= 0 )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel, attribStr ) );
else if ( className.indexOf( "XRadioButton" ) >= 0 ) {
if ( typeStr != null ) {
if ( typeStr.equals( "text" ) )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel, attribStr ) );
else if ( typeStr.equals( "state" ) ) {
XStateBinding sb = new XStateBinding( targetComp, srcStr, srcModel, childNode.getAttribute( "leaf" ) );
String destStr = childNode.getAttribute( "output" );
if ( destStr != null )
sb.setOutputPath( destStr );
page.addBinding( sb );
}
}
else
page.addBinding( new XRadioBinding( targetComp, srcStr, srcModel ) );
}
else if ( className.indexOf( "XCheckbox" ) >= 0 ) {
if ( ( typeStr != null ) && ( typeStr.equals( "text" ) ) )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel, attribStr ) );
else {
XStateBinding sb = new XStateBinding( targetComp, srcStr, srcModel, childNode.getAttribute( "leaf" ) );
String destStr = childNode.getAttribute( "output" );
if ( destStr != null )
sb.setOutputPath( destStr );
page.addBinding( sb );
}
}
else if ( className.indexOf( "XTable" ) >= 0 ) {
XModel tableModel = ( XModel )rootNode.get( srcStr );
( ( XModelHolder )targetComp ).setModel( tableModel );
}
else if ( targetComp instanceof TextComponent )
page.addBinding( new XTextBinding( targetComp, srcStr, srcModel, attribStr ) );
}
}
}
catch ( Exception e )
{
if ( BuildProperties.DEBUG )
DebugLogger.logError( "While adding the data binding element: " + nameStr );
e.printStackTrace();
}
}
}
/**
* Try to get a binding factory to construct the binding
* @param compType the component type
* @param model the source data model
* @param bindingNode the XML element defining the binding
* @return the new binding if one could be constructed
*/
protected XDataBinding getFactoryBinding( Component compType, XModel model, XmlElement bindingNode )
{
XDataBinding binding = null;
Vector bindingFactories = XProjectManager.getCurrentProject().getBindingsFactories();
for ( int i = 0; i < bindingFactories.size(); i++ ) {
binding = ( ( XDataBindingFactory )bindingFactories.elementAt( i ) ).getBinding( compType, model, bindingNode );
if ( binding != null )
return binding;
}
return null;
}
/**
* Set the name of the page
* @param pageName the new page name.
*/
protected void setPageName( String pageName )
{
page.setName( pageName );
}
/**
* Adds an individual component element to the page (this method may be called
* recursively for nested elements). Several methods will be attempted until a
* component is successfully created. Firstly the built-in component types are
* checked, then any additional registered component constructors. The types
* can be specified by type ID, type name or class name.
* @param childName the name of the child element
* @param childNode the XML element containing the component specification.
* @return the new component
*/
protected Component addComponent( XmlElement childNode )
{
String nameStr = null, typeStr, xStr, yStr, wStr, hStr, contentStr, styleStr, classStr,
layoutMgr, constraintStr, opaqueStr;
Component comp = null;
String childName = childNode.getName();
try {
int x = 0, y = 0, w = 30, h = 20;
nameStr = typeStr = wStr = hStr = contentStr = styleStr = classStr = layoutMgr =
constraintStr = opaqueStr = null;
xStr = yStr = "0";
wStr = hStr = "-1";
Hashtable componentAttributes = new Hashtable(); // Local copy of the component attributes
currentAttributes = componentAttributes;
Enumeration attribNamesEnum = childNode.enumerateAttributeNames();
while ( attribNamesEnum.hasMoreElements() ) {
String attribName = ( String ) attribNamesEnum.nextElement();
String attribValue = ( String ) childNode.getAttribute( attribName );
if ( attribName.compareTo( "type" ) == 0 )
typeStr = attribValue;
else if ( attribName.compareTo( "x" ) == 0 )
xStr = attribValue;
else if ( attribName.compareTo( "y" ) == 0 )
yStr = attribValue;
else if ( attribName.compareTo( "w" ) == 0 )
wStr = attribValue;
else if ( attribName.compareTo( "h" ) == 0 )
hStr = attribValue;
else {
Object retObj = evaluateAttribute( rootPage, attribValue );
attribValue = retObj.toString();
if ( attribName.compareTo( "name" ) == 0 )
nameStr = attribValue;
else if ( attribName.compareTo( "class" ) == 0 )
classStr = attribValue;
else if ( attribName.compareTo( "layout" ) == 0 )
layoutMgr = attribValue;
else if ( attribName.compareTo( "constraint" ) == 0 )
constraintStr = attribValue;
else {
if ( attribName.compareTo( "content" ) == 0 )
contentStr = attribValue;
else if ( attribName.compareTo( "key" ) == 0 )
contentStr = attribValue;
else if ( attribName.compareTo( "style" ) == 0 )
styleStr = attribValue;
else if ( attribName.compareTo( "opaque" ) == 0 )
opaqueStr = attribValue;
else {
if ( nameStr == null ) {
nameStr = childNode.getAttribute( "name" );
// Synthesise a name to distinguish this component
if ( nameStr == null )
nameStr = "child" + new Integer( ++nextChildId ).toString();
}
// Save a copy of the attributes in the page for post creation usage
( ( XPage )rootPage ).setAttribute( attribName, nameStr, attribValue );
}
// Save a local copy for use during the construction process
componentAttributes.put( attribName, attribValue );
}
}
}
x = y = w = h = 0;
x = getInt( xStr, x );
y = getInt( yStr, y );
w = getInt( wStr, w );
h = getInt( hStr, h );
if ( typeStr != null ) // Load an enumerated type
childName = typeStr;
if ( classStr != null ) // Load a specified type class
comp = componentFactory.addClass( classStr,
x, y, w, h,
styleStr
);
else { // Load by name
if ( childName.compareTo( "Include" ) == 0 ) {
Component parentObject = componentFactory.getParentComponent();
loadPage( packageName, childNode.getAttribute( "file" ), true );
componentFactory.setParentComponent( parentObject );
return null;
}
// Load with a name like 'Button'
try {
if ( constraintStr == null )
comp = componentFactory.addComponent( childName,
x, y, w, h,
contentStr,
styleStr
);
else {
comp = componentFactory.addComponent( childName,
layoutHelper.getConstraint( constraintStr ),
contentStr,
styleStr
);
if ( w > 0 )
comp.setSize( w, h );
}
}
catch ( Exception e ) {
comp = null;
}
finally {
if ( comp == null ) {
componentAttributes.put( "x", xStr );
componentAttributes.put( "y", yStr );
componentAttributes.put( "w", wStr );
componentAttributes.put( "h", hStr );
Object obj = componentFactory.addElement( childName, nameStr, contentStr, componentAttributes );
if ( obj == null ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to add component: " + nameStr );
// Construct a dummy component
comp = componentFactory.addComponent( XPage.UNKNOWN, x, y, w, h, contentStr, styleStr );
componentAttributes.put( "type", childName );
}
}
}
}
if ( comp == null )
return null;
// Add the layout manager if one has been specified
if (( comp instanceof Container ) && ( layoutMgr != null ))
componentFactory.addLayout( ( Container )comp, layoutHelper.getLayoutType( layoutMgr ), componentAttributes );
// Set the component name
comp.setName( nameStr );
// If a panel is found set it to the parent and recursively add its children
if ( comp instanceof Container ) {
Component parentObject = componentFactory.getParentComponent();
componentFactory.setParentComponent( comp );
addComponents( comp, childNode );
currentAttributes = componentAttributes;
componentFactory.setParentComponent( parentObject );
comp.doLayout();
}
// Special handling for radio buttons
// For the first rb create a group
// For subsequent rbs add them to the group
// If another type of component is found reset the groups
if ( comp instanceof XRadioButtonGroup ) {
XRadioButtonGroup rb = ( ( XRadioButtonGroup ) comp );
if ( checkBoxGroup == null ) {
// The radio button may already have a group
if ( rb.getRadioButtonGroup() == null )
checkBoxGroup = rb.createGroup();
else
checkBoxGroup = rb.getRadioButtonGroup();
}
else
rb.setRadioButtonGroup( checkBoxGroup );
}
else
checkBoxGroup = null;
// Setup any extra attributes specified in the XML
setComponentAttributes( comp, componentAttributes );
}
catch ( Exception e ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "While adding the component element: " + nameStr );
e.printStackTrace();
}
return comp;
}
/**
* Adds a menu to the application. Although specified in the XML as part of the
* page the menu is actually added to the application frame.
* @param page the page
* @param model the Menu XML element
*/
protected void addMenu( XPage page, XmlElement model )
{
Component parent = componentFactory.getParentComponent();
componentFactory.setParentComponent( (Container)page );
String basePackageName = XResourceManager.getPackageName() + ".X";
try {
menuBar = ( XAppender )Class.forName( basePackageName + XPage.MENUBAR ).newInstance();
Vector menuNodes = model.getChildren();
int numMenus = menuNodes.size();
for ( int i = 0; i < numMenus; i++ ) {
XmlElement childNode = ( XmlElement )menuNodes.elementAt( i );
XAppender menu = ( XAppender )Class.forName( basePackageName + XPage.MENU ).newInstance();
((XTextHolder)menu).setText( componentFactory.translate( childNode.getAttribute( "content" ) ));
int numMenuItems = childNode.getChildren().size();
for ( int j = 0; j < numMenuItems; j++ ) {
XmlElement itemNode = ( XmlElement )childNode.elementAt( j );
Object menuItem = Class.forName( basePackageName + XPage.MENUITEM ).newInstance();
((XTextHolder)menuItem).setText( componentFactory.translate( itemNode.getAttribute( "content" ) ));
menu.append( menuItem, itemNode.getAttribute( "name" ) );
}
menuBar.append( menu, childNode.getAttribute( "name" ) );
}
menuBar.setup();
}
catch ( Exception ex ) {
}
componentFactory.setParentComponent( (Container)parent );
}
/**
* Adds validation rules to the components
* @param page the page
* @param model the Validations XML element
*/
protected void addValidations( XPage page, XmlElement model )
{
Vector validations = model.getChildren();
int numValidations = validations.size();
for ( int i = 0; i < numValidations; i++ ) {
XmlElement childNode = ( XmlElement )validations.elementAt( i );
Component target = page.findComponent( childNode.getAttribute( "target" ));
String whenAttrib = childNode.getAttribute( "when" );
int whenAttribValue = FocusEvent.FOCUS_LOST;
if ( whenAttrib != null ) {
if ( whenAttrib.toLowerCase().compareTo( "mouseclicked" ) == 0 )
whenAttribValue = MouseEvent.MOUSE_CLICKED;
}
page.addValidation( target, childNode.getAttribute( "rule" ), childNode.getAttribute( "method" ), whenAttribValue );
}
}
/**
* Iterate through the attributes and set the attributes for a component
* @param comp the component
* @param attribs the attributes.
*/
protected void setComponentAttributes( Component comp, Hashtable attribs )
{
String compName = page.getComponentName( comp );
Enumeration enum = attribs.keys();
while ( enum.hasMoreElements() ) {
try {
String key = ( String )enum.nextElement();
String value = ( String )attribs.get( key );
page.setAttribute( key, compName, value );
// Set a couple of key attributes
if ( key.compareTo( "visible" ) == 0 )
comp.setVisible( value.equals( "true" ) );
else if ( key.compareTo( "enabled" ) == 0 )
comp.setEnabled( value.equals( "true" ) );
else if ( comp instanceof XAttributedComponent )
( ( XAttributedComponent )comp ).setAttribute( key, value );
}
catch ( Exception ex ) {
}
}
}
/**
* Gets a reference to a menu object
* @param name the name of the menu item
* @return
*/
protected Object getMenuItem( String name )
{
return menuBar.getObject( name );
}
/**
* Construct a validation factory appropriate to this builder
* @param validationFileName the validations file to read
* @return the validation factory
*/
protected XValidationFactory getValidationFactory( String validationFileName )
{
if (( validationFileName != null ) && ( validationFileName.length() > 0 )) {
try {
XValidationFactory vf = ( XValidationFactory )validationFactories.get( validationFileName );
if ( vf == null ) {
String validationFactName = XProjectManager.getCurrentProject().getStartupParam( "ValidationFactory" );
if ( validationFactName != null ) {
vf = ( XValidationFactory ) Class.forName(validationFactName).newInstance();
vf.read( XResourceManager.getBufferedReader( validationFileName, null ) );
}
else {
vf = new XValidationFactory( XResourceManager.getBufferedReader( validationFileName, null ) );
}
validationFactories.put( validationFileName, vf );
}
return vf;
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to read the validations file: " + ex.getMessage() + ", " + ex.getMessage());
}
}
return null;
}
/**
* Convert a string to an int or else return the default value
* @param s the number as a string
* @param def the default value
* @return the converted value
*/
protected int getInt( String s, int def )
{
try {
return new Integer( s ).intValue();
}
catch ( NumberFormatException ex ) {
}
return def;
}
/**
* Get the table of component attributes
* @return the attributes hash table.
*/
public static Hashtable getCurrentAttributes()
{
return currentAttributes;
}
}
|