package net.xoetrope.xui;
import java.io.StringReader;
import java.util.Hashtable;
import java.util.Enumeration;
import java.lang.reflect.Constructor;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xml.XmlSource;
import net.xoetrope.xui.data.XModel;
import net.xoetrope.xui.helper.XTranslator;
import net.xoetrope.registry.ComponentAdapter;
import net.xoetrope.xui.build.BuildProperties;
import net.xoetrope.xui.helper.XLayoutHelper;
/**
* <p>A component factory. The factory is designed to create components
* for a null layout. The factory will use an incrementing id to name each component.
* When an XPanel is added it will automatically become the parent for subsequent
* components added using the factory. If another parent component is needed then
* the parent can be explicitly set.
* </p>
* <p>
* When components are added their size is checked against that of the parent and
* reduced if they extend beyond the bounds of the parent.
* </p>
* <p>
* The component factory can be extended by registering new XComponentConstructors.
* These constructors are invoked if the build in constructors cannot build the
* specified type.
* </p>
* <p>
* Components can be specified by a type name, an type constant or by a class name.
* The type constants for the built-in components are specified in XPage so as
* to make referencing the constants easier in the client code (subclasses of XPage).
* The package name is set as an attribute of the factory so that various versions
* of the widgets can be create for say Swing and AWT without needing to create
* distinct factories and without need to include such implementation details in
* the client code.
* </p>
* <p>Copyright (c) Xoetrope Ltd., 2002-2003</p>
* <p>License: see license.txt</p>
* $Revision: 2.21 $
*/
public class XComponentFactory
{
// Built-in component types
/**
* A component of a type that is unknown to XUI (i.e. not one of the built in types)
*/
public final static int XUNKNOWN = -1;
/**
* A panel / container
*/
public final static int XPANEL = 0;
/**
* A static text
*/
public final static int XLABEL = 1;
/**
* A constant used internally to identify a RadioButton.
*/
public final static int XRADIO = 2;
/**
* A constant used internally to identify a Checkbox.
*/
public final static int XCHECK = 3;
/**
* A constant used internally to identify a Combo Box.
*/
public final static int XCOMBO = 4;
/**
* A constant used internally to identify a List.
*/
public final static int XLIST = 5;
/**
* A constant used internally to identify an image component.
*/
public final static int XIMAGE = 6;
/**
* A constant used internally to identify a Edit field.
*/
public final static int XEDIT = 7;
/**
* A constant used internally to identify a push buttob.
*/
public final static int XBUTTON = 8;
/**
* A constant used internally to identify a container for tagged content.
*/
public final static int XMETACONTENT = 9;
/**
* A constant used internally to identify a RadioButton group.
*/
public final static int XGROUP = 10;
/**
* A constant used internally to identify a scroll panel.
*/
public final static int XSCROLLPANE = 11;
/**
* A constant used internally to identify a scrollable meta content.
*/
public final static int XSCROLLABLEMETACONTENT = 12;
/**
* A constant used internally to identify a hotspot image.
*/
public final static int XHOTSPOTIMAGE = 13;
/**
* A constant used internally to identify a table component.
*/
public final static int XTABLE = 14;
/**
* A constant used internally to identify a vector image component.
*/
public final static int XWMF = 15;
/**
* A constant used internally to identify an annotated image component.
*/
public final static int XANNOTATEDIMAGE = 16; // DROPPED -merged with imagemap
/**
* A constant used internally to identify a MenuBar.
*/
public final static int XMENUBAR = 17;
/**
* A constant used internally to identify a Menu.
*/
public final static int XMENU = 18;
/**
* A constant used internally to identify a Menu item.
*/
public final static int XMENUITEM = 19;
/**
* A constant used internally to identify a multiline text edit component.
*/
public final static int XTEXTAREA = 20;
/**
* A constant used internally to identify a password field.
*/
public final static int XPASSWORD = 21;
/**
* A constant used internally to identify an image map.
*/
public final static int XIMAGEMAP = 22;
/**
* A constant used internally to identify a tab panel.
*/
public final static int XTABPANEL = 23;
/**
* A constant used internally to identify a splitter.
*/
public final static int XSPLITPANE = 24;
/**
* A collection of component factories
*/
protected static Hashtable componentFactories = new Hashtable();
/**
* A collection of component type names and ids used to aid construction of components
*/
protected static Hashtable typeNames;
/**
* A helper to construct various layout managers
*/
protected static LayoutHelper layoutHelper = new XLayoutHelper();
/**
* A flag indicating is the components require the parent object to be passed
* as an argumnet to the constructor.
*/
protected static boolean requiresParent = false;
/**
* The package to which the widgets belong
*/
protected String basePackageName;
/**
* The translator used for translation
*/
protected XTranslator translator;
/**
* The project that owns this factory
*/
protected XProject currentProject;
/**
* The adapter used for the current package's widgets
*/
protected WidgetAdapter adapter;
/**
* The application's menu bar
*/
protected Object currentMenuBar;
/**
* The current menu. Menu construction is slightly abnormal for the factory and
* requires additional parameters
*/
protected Object currentMenu;
/**
* The parent panel to which components are added
*/
protected Object parentPanel;
protected int parentW, parentH;
/**
* Constructs a component factory
* @param proj The project to which the fctory belongs
* @param packageName the package name for the components
*/
public XComponentFactory( XProject proj, String packageName )
{
currentProject = proj;
if ( packageName == null )
packageName = XPage.XUI_AWT_PACKAGE;
adapter = WidgetAdapter.getInstance();
basePackageName = packageName + "." + "X";
setupTypeNames();
translator = currentProject.getTranslator();
}
/**
* Set the resource bundle for this component factory. The resource bundle is used
* for translation of text and other content.
* @param resourceBundleName the resource bundle name
*/
public void setResourceBundle( String resourceBundleName )
{
if ( translator == null )
translator = currentProject.getTranslator( resourceBundleName );
if ( translator != null )
translator.setResourceBundle( currentProject.getResourceBundle( resourceBundleName ));
}
/**
* A generic factory for constructing XComponents.
* @param type a name identifying the type of component to be created
* @param content the component text/content
* @return the new component
*/
public Object constructComponent( String type, String content )
{
int id = getTypeCode( type );
if ( id < 0 )
return buildRegisteredComponent( type, content );
Object comp = null;
try {
switch ( id ) {
case XPANEL:
comp = instantiate( basePackageName + XPage.PANEL );
if ( parentPanel == null )
parentPanel = comp;
break;
case XLABEL:
comp = instantiate( basePackageName + XPage.LABEL );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XRADIO:
comp = instantiate( basePackageName + XPage.RADIO );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XCHECK:
comp = instantiate( basePackageName + XPage.CHECK );
( ( XTextHolder )comp ).setText( translate( content ) );
break;
case XCOMBO:
comp = instantiate( basePackageName + XPage.COMBO );
break;
case XLIST:
comp = instantiate( basePackageName + XPage.LIST );
break;
case XIMAGE:
comp = instantiate( basePackageName + XPage.IMAGE );
currentProject.getImage( (XImageHolder)comp, content );
break;
case XIMAGEMAP:
comp = instantiate( basePackageName + XPage.IMAGEMAP );
( ( XImageHolder )comp ).setImage( currentProject.getImage( content ) );
break;
case XEDIT:
comp = instantiate( basePackageName + XPage.EDIT );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XTEXTAREA:
comp = instantiate( basePackageName + XPage.TEXTAREA );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XPASSWORD:
comp = instantiate( basePackageName + XPage.PASSWORD );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XBUTTON:
comp = instantiate( basePackageName + XPage.BUTTON );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XMETACONTENT:
comp = instantiate( basePackageName + XPage.METACONTENT );
if ( content != null )
setMetaContent( comp, content );
break;
case XSCROLLPANE:
comp = instantiate( basePackageName + XPage.SCROLLPANE );
break;
case XSCROLLABLEMETACONTENT:
comp = instantiate( basePackageName + XPage.SCROLLABLEMETACONTENT );
setMetaContent( comp, content );
break;
case XHOTSPOTIMAGE:
comp = instantiate( basePackageName + XPage.HOTSPOTIMAGE );
( ( XImageHolder )comp ).setImage( currentProject.getImage( content ) );
break;
case XTABLE:
comp = instantiate( basePackageName + XPage.TABLE );
setTableContent( comp, content );
break;
case XWMF:
comp = instantiate( "net.xoetrope.xui.wmf.XWmf" );
if ( content != null )
((XTextHolder)comp).setText( translate( content ));
break;
case XTABPANEL:
comp = instantiate( basePackageName + XPage.TABPANEL );
break;
case XSPLITPANE:
comp = instantiate( basePackageName + XPage.SPLITPANE );
break;
default:
break;
}
}
catch ( Exception e ) {
e.printStackTrace();
}
return comp;
}
/**
* Instantiate a component using reflection to locate the constructor. This
* method of creating a component is used where the parent object or some
* other constructor argument is required to create the component.
* @param className the class to instantiate
* @return the new component
*/
protected Object instantiate( String className )
{
try {
Class clazz = Class.forName( className.trim());
if ( !requiresParent )
return clazz.newInstance();
else {
Class[] parameterTypes = new Class[ 1 ];
parameterTypes[ 0 ] = Object.class;
Constructor ctor = clazz.getConstructor( parameterTypes );
Object[] args = new Object[ 1 ];
args[ 0 ] = parentPanel;
return ctor.newInstance( args );
}
}
catch ( Exception e ) {
e.printStackTrace();
}
return null;
}
/**
* Look up the translation of a key using the current language resource
* @param key the key string
* @return the translation
*/
public String translate( String key )
{
if ( translator != null )
return translator.translate( key );
else
return key;
}
/**
* A generic factory for adding XComponents. The component is constructed,
* positioned and added to the parent panel if one exists. This method
* delegates to the registered component factories if any. All built in
* components are constructed with an ID.
* When a ScrollPane is addd it becomes the parent.
* @param type a name identifying the type of component to be created
* @param x the left coordinate
* @param y the top coordinate
* @param w the width
* @param h the height
* @param content the component text/content
* @return the new component
*/
public Object addComponent( String type, int x, int y, int w, int h, String content )
{
Object comp = null;
try {
comp = constructComponent( type, content );
if ( comp == null )
comp = buildRegisteredComponent( type, content );
if ( comp == null ) {
String typeName = type.trim();
if ( type.indexOf( "." ) <= 0 )
typeName = basePackageName + typeName;
comp = Class.forName( typeName ).newInstance();
if ( comp instanceof XTextHolder )
((XTextHolder)comp ).setText( content );
}
if ( comp == null )
return null;
if (( parentW > 0 ) && ( w > 0 )) {
/**
* @todo consider using an SWT Component factory so as to remove these
* nuances of SWT to an SWT specific class.
*/
String className = parentPanel.getClass().getName();
if ( className.indexOf( "ScrollPane" ) < 0 )
adapter.setBounds( comp, x, y, Math.min( w, parentW - x ), Math.min( h, parentH ) );
else if ( className.indexOf( ".swt." ) > 0 )
adapter.setBounds( comp, x, y, w, h );
}
else if ( w > 0 )
adapter.setBounds( comp, x, y, w, h );
if ( comp != null ) {
addComponent( comp );
return comp;
}
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG ) {
DebugLogger.logWarning( "Could not instantiate type: " + type );
ex.printStackTrace();
Throwable t = ex.getCause();
if( t != null )
t.printStackTrace();
}
}
return comp;
}
/**
* A generic factory for adding XComponents. The component is constructed,
* positioned and added to the parent panel if one exists. This method
* delegates to the registered component factories if any. All built in
* components are constructed with an ID.
* When a ScrollPane is addd it becomes the parent.
* @param type a name identifying the type of component to be created
* @param pos the constraint
* @param content the component text/content
* @return the new component
*/
public Object addComponent( String type, Object pos, String content )
{
Object comp = null;
try {
comp = constructComponent( type, content );
//-------------------------------------------------
/**
* @todo check if this block of code is reachable or has an effect as the
* construct component method calls buildRegisteredComponent.
*/
if ( comp == null ) {
comp = buildRegisteredComponent( type, content );
}
//-------------------------------------------------
if ( comp != null ) {
if ( pos != null )
addComponent( comp, pos );
else
addComponent( comp );
return comp;
}
}
catch ( Exception e ) {
e.printStackTrace();
}
return comp;
}
/**
* A generic factory for adding registered components via the
* XComponentConstructor interface or component factories.
* @param type a name identifying the type of component to be created
* @param content the component text/content
* @return the new component
*/
protected Object buildRegisteredComponent( String type, String content )
{
Enumeration enumeration = componentFactories.keys();
while ( enumeration.hasMoreElements() ) {
XComponentConstructor factory = ( XComponentConstructor )componentFactories.get( enumeration.nextElement() );
Object comp = factory.constructComponent( this, type, content );
if ( comp != null )
return comp;
}
return null;
}
/**
* Lookup the component adapter for the named type
* @param type a name identifying the type of component to be created
* @return the new component adapter for the type
*/
public ComponentAdapter getComponentAdapter( String type )
{
Enumeration enumeration = componentFactories.keys();
while ( enumeration.hasMoreElements() ) {
XComponentConstructor factory = ( XComponentConstructor )componentFactories.get( enumeration.nextElement() );
ComponentAdapter ca = factory.getComponentAdapter( type );
if ( ca != null )
return ca;
}
return null;
}
/**
* Add a componentFactory to the static register of component constructors
* @param name the name by which this factory will be known.
* @param factory the new componentFactory
*/
public static void registerComponentFactory( String name, XComponentConstructor factory )
{
if ( componentFactories.get( name ) == null )
componentFactories.put( name, factory );
}
/**
* Notify the component factories that some of their settings may have changed
*/
public static void updateComponentFactories()
{
Enumeration enumeration = componentFactories.keys();
while ( enumeration.hasMoreElements() ) {
XComponentConstructor factory = ( XComponentConstructor )componentFactories.get( enumeration.nextElement() );
factory.update();
}
}
/**
* Get the component factories
* @return a the factor store.
*/
public static Hashtable getFactories()
{
return componentFactories;
}
/**
* Add a non-component object to the panel or an element of the panel. This method
* is invoked is the XuiBuilder fails to construct a component for an XML element
* @param type the object type
* @param name a name identifying the element to be created
* @param content the component text/content
* @param attribs the element attributes if any
* @return the new component
*/
public Object addElement( String type, String name, String content, Hashtable attribs )
{
Enumeration enumeration = componentFactories.keys();
while ( enumeration.hasMoreElements() ) {
XComponentConstructor factory = ( XComponentConstructor )componentFactories.get( enumeration.nextElement() );
Object obj = factory.addElement( this, type, name, content, attribs );
if ( obj != null )
return obj;
}
return null;
}
/**
* Add a component to the panel.
* @param c the component to add
*/
public void addComponent( Object c )
{
if (( c != parentPanel ) && ( parentPanel != null ))
adapter.add( parentPanel, c );
}
/**
* Add a component to the panel.
* @param c the component to add
* @param constraint the layout manager constraint
*/
public void addComponent( Object c, Object constraint )
{
if ( c != parentPanel )
adapter.add( parentPanel, c, constraint );
}
/**
* Sets a LayoutManager for the panel
* @param cont the container whose layout manager is being set or null to set the parent panel's layout manager
* @param type the layout manager as defined in the XLayoutHelper class
* @return the new layout manager instance
*/
public Object addLayout( Object cont, int type )
{
if ( cont == null )
cont = parentPanel;
return layoutHelper.addLayout( cont, type );
}
/**
* Change the parent for new components. This method will affect the next component added.
* @param c the new parent, this should be an instance of java.awt.Container
*/
public void setParentComponent( Object c )
{
parentPanel = c;
if ( parentPanel != null ) {
int w = adapter.getWidth( parentPanel );
int h = adapter.getHeight( parentPanel );
parentW = w;
parentH = h;
}
}
/**
* Get the current parent component
* @return the parent component.
*/
public Object getParentComponent()
{
return parentPanel;
}
/**
* Get the layout helper
* @return the layout helper
*/
public static LayoutHelper getLayoutHelper()
{
return layoutHelper;
}
/**
* Set the layout helper
* @param newHelper the new layout helper
*/
public static void setLayoutHelper( LayoutHelper newHelper )
{
layoutHelper = newHelper;
}
/**
* Set the content for XMetaContentHolder objects
* @param comp
* @param content
*/
private void setMetaContent( Object comp, String content )
{
if ( content == null )
return;
boolean useContent = false;
if ( content.indexOf( "<?xml") != 0 ) {
String res = translate( content );
if ( res.compareTo( content ) != 0 ) {
content = res;
useContent = true;
}
}
else
useContent = true;
MetaContentReader cr = new MetaContentReader( currentProject, comp, content, useContent );
cr.start();
}
/**
* Set the content for XTable objects
* @param comp
* @param content
*/
private void setTableContent( Object comp, String content )
{
if ( content != null ) {
XModel xm = ( XModel )currentProject.getModel().get( content );
( ( XModelHolder )comp ).setModel( xm );
}
}
/**
* Get the type constant associated with a type name
* @param typeName the type name
* @return the type constant
*/
protected static int getTypeCode( String typeName )
{
String key;
setupTypeNames();
if ( typeName.indexOf( '.' ) > 0 )
typeName = typeName.substring( typeName.lastIndexOf( '.' ) + 1 );
if ( typeName.charAt( 0 ) == 'X' )
key = typeName.substring( 1, typeName.length() );
else
key = typeName;
Object retObj = typeNames.get( key.toUpperCase() );
if ( retObj == null )
return -1;
return ( ( Integer )retObj ).intValue();
}
/**
* Setup a hashtable of type names. This will be moved to a helper class at some stage
*/
protected static void setupTypeNames()
{
if ( typeNames == null ) {
typeNames = new Hashtable( 34 );
addTypeName( XPage.PANEL, XPANEL );
addTypeName( XPage.LABEL, XLABEL );
addTypeName( XPage.RADIO, XRADIO );
addTypeName( "RADIOBUTTON", XRADIO );
addTypeName( XPage.CHECK, XCHECK );
addTypeName( "CHECK", XCHECK );
addTypeName( XPage.COMBO, XCOMBO );
addTypeName( "COMBO", XCOMBO );
addTypeName( XPage.LIST, XLIST );
addTypeName( XPage.IMAGE, XIMAGE );
addTypeName( XPage.IMAGEMAP, XIMAGEMAP );
addTypeName( XPage.EDIT, XEDIT );
addTypeName( XPage.TEXTAREA, XTEXTAREA );
addTypeName( XPage.BUTTON, XBUTTON );
addTypeName( XPage.METACONTENT, XMETACONTENT );
addTypeName( XPage.GROUP, XGROUP );
addTypeName( XPage.SCROLLPANE, XSCROLLPANE );
addTypeName( XPage.SCROLLABLEMETACONTENT, XSCROLLABLEMETACONTENT );
addTypeName( XPage.HOTSPOTIMAGE, XHOTSPOTIMAGE );
addTypeName( "HOTSPOT", XHOTSPOTIMAGE );
addTypeName( XPage.TABLE, XTABLE );
addTypeName( XPage.WMF, XWMF );
addTypeName( XPage.MENUBAR, XMENUBAR );
addTypeName( XPage.MENU, XMENU );
addTypeName( XPage.MENUITEM, XMENUITEM );
addTypeName( XPage.PASSWORD, XPASSWORD );
addTypeName( XPage.UNKNOWN, XUNKNOWN );
addTypeName( XPage.TABPANEL, XTABPANEL );
addTypeName( XPage.SPLITPANE, XSPLITPANE );
}
}
/**
* Add a type name to the table of types. The type names are used to recognize
* the xml elements in the page description
* @param name the type name
* @param value the corresponding id
*/
private static void addTypeName( String name, int value )
{
typeNames.put( name.toUpperCase(), new Integer( value ) );
}
/**
* Flags whether or not the component constructors require the parent as an
* argument. Set to false by default.
* @param b true to pass the parent to the constructor
*/
public static void setRequiresParent( boolean b )
{
requiresParent = b;
}
}
/**
* Description: Loads the meta content in a background thread</p>
*/
class MetaContentReader extends Thread
{
private String content;
private Object comp;
private boolean useContent;
private XProject currentProject;
/**
* Construct and initialize a new MetaContentReader
* @param proj the current project
* @param c the MetaContent component
* @param source the resource name of the content file.
* @param useCont true to use the source argument as the content, false to treat it as the name of an external file.
*/
MetaContentReader( XProject proj, Object c, String source, boolean useCont )
{
currentProject = proj;
useContent = useCont;
comp = c;
content = source;
}
/**
* Read and setthe content on a background thread
*/
public void run()
{
try {
if ( content != null ) {
XmlElement src;
if ( !useContent ) {
src = XmlSource.read( currentProject.getBufferedReader( content, null ) );
( ( XMetaContentHolder ) comp ).setContent( content, src );
}
else {
StringReader reader = new StringReader( content );
src = XmlSource.read( reader );
( ( XMetaContentHolder ) comp ).setContent( content, src );
}
}
}
catch ( Exception ex ) {
DebugLogger.logWarning( "Unable to load content: " + content );
DebugLogger.logWarning( "Please check the case of the file name" );
ex.printStackTrace();
}
}
}