Revision: 460 http://skycastle.svn.sourceforge.net/skycastle/?rev=460&view=rev Author: zzorn Date: 2008-04-14 11:37:59 -0700 (Mon, 14 Apr 2008) Log Message: ----------- Implemented ListUi, the demo application shows a GameObject with hp, mana, and an inventory. Modified Paths: -------------- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacade.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacadeImpl.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TextFieldUi.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/example/UiDemo.java Added Paths: ----------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/AbstractPropertyUi.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/ListUi.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TableUi.java Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacade.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacade.java 2008-04-14 17:59:09 UTC (rev 459) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacade.java 2008-04-14 18:37:59 UTC (rev 460) @@ -19,7 +19,9 @@ // and move to a list based model where insertion and change and removal positions are indicated with indexes. // Leave a set based collection for later if needed. -// TODO: Allow properties have as type some kind of GameObject interfaces / templates? +// TODO: Allow properties have as type some kind of GameObject interfaces / templates? + +// TODO: Create specialized methods for adding list and map type parameters.. { /** Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacadeImpl.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacadeImpl.java 2008-04-14 17:59:09 UTC (rev 459) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/property/PropertyFacadeImpl.java 2008-04-14 18:37:59 UTC (rev 460) @@ -144,7 +144,7 @@ if ( !StringUtilities.isIdentifier( propertyIdentifier ) ) { throw new ParameterValidationException( PROBLEM_WHEN_ADDING_PROPERTY, - new ValidationError( "", + new ValidationError( propertyIdentifier, "The name for the new property is not following the " + "identifier syntax (letter + letter, numbers, and underscores), " + "instead it was '" + propertyIdentifier + "' " ) ); @@ -154,11 +154,28 @@ if ( myPropertyMetadata.containsKey( propertyIdentifier ) ) { throw new ParameterValidationException( PROBLEM_WHEN_ADDING_PROPERTY, - new ValidationError( "", + new ValidationError( propertyIdentifier, "There is already a property called '" + propertyIdentifier + "' " + "registered, can not add a new one with the same name" ) ); } + // Check that collection and map types have full metadata + if ( Collection.class.isAssignableFrom( propertyMetadata.getType() ) + && propertyMetadata.getValueMetadata() == null ) + { + throw new ParameterValidationException( PROBLEM_WHEN_ADDING_PROPERTY, + new ValidationError( propertyIdentifier, + "The property is a collection type, but lacks the metadata for the values." ) ); + } + if ( Map.class.isAssignableFrom( propertyMetadata.getType() ) + && ( propertyMetadata.getValueMetadata() == null || + propertyMetadata.getKeyMetadata() == null ) ) + { + throw new ParameterValidationException( PROBLEM_WHEN_ADDING_PROPERTY, + new ValidationError( propertyIdentifier, + "The property is a map type, but lacks the metadata for the values and/or keys." ) ); + } + // Check that the initial vaue is acceptable final ValidationError validationError = propertyMetadata.validateParameter( initialValue, propertyIdentifier ); @@ -208,7 +225,8 @@ final ParameterMetadataImpl propertyMetadata = new ParameterMetadataImpl( type, false, - new UiMetadataImpl( StringUtilities.camelCaseToUserReadableString( propertyIdentifier ), description ) ); + new UiMetadataImpl( StringUtilities.camelCaseToUserReadableString( + propertyIdentifier ), description ) ); // Add validators to metadata for ( final ParameterValidator validator : validators ) @@ -237,7 +255,9 @@ propertyIdentifier ) ); } - public void addPropertyElement( final String propertyIdentifier, final Serializable collectionPropertyItem ) + + public void addPropertyElement( final String propertyIdentifier, + final Serializable collectionPropertyItem ) throws ParameterValidationException { // Check that the property exists @@ -254,10 +274,13 @@ } // Check that the vaue is acceptable - final ValidationError validationError = propertyMetadata.getValueMetadata().validateParameter( collectionPropertyItem, propertyIdentifier ); + final ValidationError validationError = propertyMetadata.getValueMetadata().validateParameter( + collectionPropertyItem, + propertyIdentifier ); if ( validationError != null ) { - throw new ParameterValidationException( PROBLEM_WHEN_ADDING_COLLECTION_PROPERTY_ITEM, validationError ); + throw new ParameterValidationException( PROBLEM_WHEN_ADDING_COLLECTION_PROPERTY_ITEM, + validationError ); } // Add property @@ -287,10 +310,14 @@ collection.add( collectionPropertyItem ); // Notify about addition - getGameObject().sendUpdateToObservers( new CollectionPropertyElementAdded( getId(), propertyIdentifier, collectionPropertyItem ) ); + getGameObject().sendUpdateToObservers( new CollectionPropertyElementAdded( getId(), + propertyIdentifier, + collectionPropertyItem ) ); } - public void removePropertyElement( final String propertyIdentifier, final Serializable collectionPropertyItem ) + + public void removePropertyElement( final String propertyIdentifier, + final Serializable collectionPropertyItem ) throws ParameterValidationException { // Check that the property exists @@ -306,15 +333,21 @@ } // Notify about removal - getGameObject().sendUpdateToObservers( new CollectionPropertyElementRemoved( getId(), propertyIdentifier, collectionPropertyItem ) ); + getGameObject().sendUpdateToObservers( new CollectionPropertyElementRemoved( getId(), + propertyIdentifier, + collectionPropertyItem ) ); } - public void addPropertyMapping( final String propertyIdentifier, final Serializable key, final Serializable value ) + + public void addPropertyMapping( final String propertyIdentifier, + final Serializable key, + final Serializable value ) throws ParameterValidationException { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT } + public void removePropertyMapping( final String propertyIdentifier, final Serializable key ) throws ParameterValidationException { Copied: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/AbstractPropertyUi.java (from rev 459, trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/AbstractPropertyUiObject.java) =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/AbstractPropertyUi.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/AbstractPropertyUi.java 2008-04-14 18:37:59 UTC (rev 460) @@ -0,0 +1,266 @@ +package org.skycastle.ui; + +import org.skycastle.core.GameObject; +import org.skycastle.core.GameObjectId; +import org.skycastle.util.parameters.ParameterMetadata; + +import javax.swing.*; +import java.io.Serializable; + +/** + * A base class for {@link UiObject}s that are bound to a specific property, and require updates when it + * changes. + * <p/> + * Calls the abstract update functions when the UI needs to update as a result of changes in the property. + * <p/> + * Automatically disables the UI component when the referenced property is not available. + * + * @author Hans Häggström + */ +public abstract class AbstractPropertyUi + extends UiObject +{ + + //====================================================================== + // Private Fields + + private final PropertyReference myPropertyReference = new PropertyReferenceImpl(); + + private JComponent myPropertyUi; + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Protected Methods + + //---------------------------------------------------------------------- + // Protected Constructors + + /** + * Creates a new AbstractPropertyUiObject with a {@link PropertyReference} not bound to any property. + */ + protected AbstractPropertyUi() + { + } + + + /** + * Creates a new AbstractPropertyUiObject with a {@link PropertyReference} bound to the specified + * property. + * + * @param gameObjectId the id of the {@link GameObject} where the property is. + * @param propertyIdentifier the identifier of the property to bind to. + */ + protected AbstractPropertyUi( final GameObjectId gameObjectId, final String propertyIdentifier ) + { + myPropertyReference.setReference( gameObjectId, propertyIdentifier ); + } + + //---------------------------------------------------------------------- + // Abstract Protected Methods + + /** + * Does not need to register as listener to the {@link PropertyReference}, the calling code does that. + * + * @return the UI for the {@link UiObject}. + */ + protected abstract JComponent createUiInDerivedClass(); + + /** + * Called when there is an update in the editability or availability of the property. + * + * @param available true if the property is available, false if it is not (it was removed, or no access + * rights, or connection terminated, etc) + * @param editable true if the property is editable, false if not. + */ + protected abstract void updateStatus( final boolean available, final boolean editable ); + + + /** + * @param description the new user readable description for the property. + */ + protected abstract void updateDescription( final String description ); + + + /** + * @param propertyValue the new value of the property. + */ + protected abstract void updateValue( final Object propertyValue ); + + //---------------------------------------------------------------------- + // Other Protected Methods + + /** + * Override if collection properties are handled specially. + * + * @param addedElement an element that was added to the collection property. + */ + protected void updateAddedElement( final Serializable addedElement ) + { + updateValueFromModel(); + } + + + /** + * Override if collection properties are handled specially. + * + * @param removedElement an element that was removed from the collection property. + */ + protected void updateRemovedElement( final Serializable removedElement ) + { + updateValueFromModel(); + } + + + /** + * Called when an entry is added to or changed in a map property. + * <p/> + * Override if map properties are handled specially. + * + * @param key the key of the added entry. + * @param value the value for the key. + */ + protected void updatePutEntry( final Serializable key, final Serializable value ) + { + updateValueFromModel(); + } + + + /** + * Called when an entry is removed from a map property. + * <p/> + * Override if map properties are handled specially. + * + * @param key the key of the removed entry. + */ + protected void updateRemovedEntry( final Serializable key ) + { + updateValueFromModel(); + } + + + /** + * @return the {@link PropertyReference} to the property that this {@link AbstractPropertyUi} is bound + * to. + */ + protected final PropertyReference getPropertyReference() + { + return myPropertyReference; + } + + + @Override + protected final JComponent createUi() + { + myPropertyUi = createUiInDerivedClass(); + + myPropertyReference.addChangeListener( createPropertyReferenceListener() ); + + updateUiFromModel(); + + return myPropertyUi; + } + + + /** + * @return user readable description of the property, or null if not available. + */ + protected final String getDescription() + { + final ParameterMetadata propertyMetadata = myPropertyReference.getPropertyMetadata(); + + String description = null; + if ( propertyMetadata != null ) + { + description = propertyMetadata.getUiMetadata().getDescription(); + } + + return description; + } + + + /** + * Updates the UI to reflect the current status of the model (the {@link PropertyReference}). + */ + protected final void updateUiFromModel() + { + updateUiEnabledStateFromModel(); + + updateStatus( myPropertyReference.isAvailable(), + myPropertyReference.isEditable() ); + + updateDescription( getDescription() ); + + updateValueFromModel(); + } + + //====================================================================== + // Private Methods + + private void updateUiEnabledStateFromModel() + { + if ( myPropertyUi != null ) + { + myPropertyUi.setEnabled( myPropertyReference.isAvailable() ); + } + } + + + private PropertyReferenceListener createPropertyReferenceListener() + { + return new PropertyReferenceListener() + { + + public void onPropertyValueChange( final PropertyReference propertyReference ) + { + updateValueFromModel(); + } + + + public void onPropertyChanged( final PropertyReference propertyReference ) + { + updateUiFromModel(); + } + + + public void onPropertyElementAdded( final PropertyReference propertyReference, + final Serializable addedElement ) + { + updateAddedElement( addedElement ); + } + + + public void onPropertyElementRemoved( final PropertyReference propertyReference, + final Serializable removedElement ) + { + updateRemovedElement( removedElement ); + } + + + public void onPropertyEntryPut( final PropertyReference propertyReference, + final Serializable key, + final Serializable value ) + { + updatePutEntry( key, value ); + } + + + public void onPropertyEntryRemoved( final PropertyReference propertyReference, + final Serializable key ) + { + updateRemovedEntry( key ); + } + + }; + } + + + private void updateValueFromModel() + { + updateValue( myPropertyReference.getProperty( getId(), Object.class, null ) ); + } + +} Added: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/ListUi.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/ListUi.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/ListUi.java 2008-04-14 18:37:59 UTC (rev 460) @@ -0,0 +1,110 @@ +package org.skycastle.ui; + +import org.skycastle.core.GameObject; +import org.skycastle.core.GameObjectId; + +import javax.swing.*; +import java.util.Collection; + +/** + * A {@link UiObject} for properties that are of list type. + * + * @author Hans Häggström + */ +// TODO: Allow single or multi-selection from the list, and reading the selection from a property of this object. +// TODO: Allow (in place?) editing of list entries? +// TODO: Some way to call an action on double click of an item? +public final class ListUi + extends AbstractPropertyUi +{ + + //====================================================================== + // Private Fields + + private JList myList; + private DefaultListModel myListModel; + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link org.skycastle.ui.ListUi}. + */ + public ListUi() + { + } + + + /** + * Creates a new {@link org.skycastle.ui.ListUi}. + * + * @param gameObjectId the id of the {@link GameObject} where the property is. + * @param propertyIdentifier the identifier of the property to bind to. + */ + public ListUi( final GameObjectId gameObjectId, final String propertyIdentifier ) + { + super( gameObjectId, propertyIdentifier ); + } + + //====================================================================== + // Protected Methods + + @Override + protected JComponent createUiInDerivedClass() + { + myListModel = new DefaultListModel(); + myList = new JList( myListModel ); + + // TODO: Listen to list selection + + final JScrollPane scrollPane = new JScrollPane( myList ); + scrollPane.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED ); + scrollPane.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED ); + return scrollPane; + } + + + @Override + protected void updateStatus( final boolean available, final boolean editable ) + { + // Nothing to do at the moment + } + + + @Override + protected void updateDescription( final String description ) + { + myList.setToolTipText( description ); + } + + + @Override + protected void updateValue( final Object propertyValue ) + { + if ( propertyValue instanceof Collection ) + { + final Collection values = (Collection) propertyValue; + + // Populate with new values + myListModel.clear(); + for ( final Object value : values ) + { + myListModel.addElement( value ); + } + } + else + { + // Non-list type property, or null + myListModel.clear(); + } + } + +} Copied: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TableUi.java (from rev 458, trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TableUiObject.java) =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TableUi.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TableUi.java 2008-04-14 18:37:59 UTC (rev 460) @@ -0,0 +1,106 @@ +package org.skycastle.ui; + +import org.skycastle.core.GameObjectId; + +import javax.swing.*; +import javax.swing.table.DefaultTableModel; +import java.io.Serializable; + +/** + * A table where each row is a GameObject and the columns specify which properties to show. + * <p/> + * Can be set to always show the last rows, allowing it to be used as a chat window. + * + * @author Hans Häggström + */ +// TODO: We need a concept of a collection property of game objects, and listener support for added and removed GameObjects. +// The server should send info on added and removed game objects. +public final class TableUi + extends AbstractPropertyUi +{ + + //====================================================================== + // Private Fields + + private DefaultTableModel myTableModel; + + private JTable myTable; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link TableUi}. + */ + public TableUi() + { + } + + + /** + * Creates a new {@link TableUi}. + */ + public TableUi( final GameObjectId gameObjectId, final String propertyIdentifier ) + { + super( gameObjectId, propertyIdentifier ); + } + + //====================================================================== + // Protected Methods + + protected JComponent createUiInDerivedClass() + { + myTableModel = new DefaultTableModel(); + myTable = new JTable( myTableModel ); + + // TODO: Add listeners that can listen to editing of entries, if they are editable. + + return myTable; + } + + + protected void updateStatus( final boolean available, final boolean editable ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + + + protected void updateDescription( final String description ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + + + protected void updateValue( final Object propertyValue ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + + + protected void updateAddedElement( final Serializable addedElement ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + + + protected void updateRemovedElement( final Serializable removedElement ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + + + protected void updatePutEntry( final Serializable key, final Serializable value ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + + + protected void updateRemovedEntry( final Serializable key ) + { + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + +} Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TextFieldUi.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TextFieldUi.java 2008-04-14 17:59:09 UTC (rev 459) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/TextFieldUi.java 2008-04-14 18:37:59 UTC (rev 460) @@ -15,8 +15,8 @@ * * @author Hans Haggstrom */ -public class TextFieldUi - extends AbstractPropertyUiObject +public final class TextFieldUi + extends AbstractPropertyUi { //====================================================================== Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/example/UiDemo.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/example/UiDemo.java 2008-04-14 17:59:09 UTC (rev 459) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/example/UiDemo.java 2008-04-14 18:37:59 UTC (rev 460) @@ -4,12 +4,13 @@ import org.skycastle.messaging.Message; import org.skycastle.messaging.MessageListener; import org.skycastle.messaging.updates.property.PropertyChangedMessage; -import org.skycastle.ui.ClientUi; -import org.skycastle.ui.PanelUi; -import org.skycastle.ui.SwingClientUi; -import org.skycastle.ui.TextFieldUi; +import org.skycastle.ui.*; +import org.skycastle.util.parameters.ParameterMetadataImpl; import org.skycastle.util.parameters.ParameterValidationException; +import org.skycastle.util.uiidentity.UiMetadataImpl; +import java.util.List; + /** * Builds up an UI from the UiObjects by instantiating them directly. * @@ -23,25 +24,75 @@ public static void main( String[] args ) { + // Initialize the game context. It keeps track of created GameObjects, and has different implementations + // for the server and client side. Will be automatically created in the future when we can differentiate + // between server-side, client side, and unit-tests. GameContext.setGameObjectContext( new ClientContext() ); - final GameObjectContext context = GameContext.getGameObjectContext(); - ClientUi swingClientUi = new SwingClientUi(); + // Create an example of a game object, in this case representing an avatar + final GameObject exampleGameObject = createExampleGameObject(); + // Create an UI for displaying properties of the example game object + final UiObject exampleUi = createExampleUi( exampleGameObject ); + // Create the root UI which also contains the main cient rendering loop, and start it. + // For now we use an implementation that simply uses Swing, instead of a real OpenGL UI with 3D views. + final ClientUi swingClientUi = new SwingClientUi(); + swingClientUi.setRootUi( exampleUi ); + swingClientUi.start(); + } + + //====================================================================== + // Private Methods + + private static PanelUi createExampleUi( final GameObject exampleGameObject ) + { + final TextFieldUi hp = new TextFieldUi( exampleGameObject.getId(), "hp" ); + final TextFieldUi mana = new TextFieldUi( exampleGameObject.getId(), "mana" ); + final ListUi inventory = new ListUi( exampleGameObject.getId(), "inventory" ); + + + final PanelUi panelUi = new PanelUi(); + panelUi.addUi( hp ); + panelUi.addUi( mana ); + panelUi.addUi( inventory ); + return panelUi; + } + + + private static GameObject createExampleGameObject() + { + final GameObjectContext context = GameContext.getGameObjectContext(); + final GameObject gameObject = context.createGameObject(); try { gameObject.addProperty( "hp", "10", "Hitpoints" ); gameObject.addProperty( "mana", "5", "Mana points" ); + gameObject.addProperty( "inventory", + null, + new ParameterMetadataImpl( List.class, + true, + new UiMetadataImpl( "Inventory", + "Current inventory of avatar" ), + new ParameterMetadataImpl( String.class ) ) ); + + gameObject.addPropertyElement( "inventory", "A torch that runs on kinetic energy" ); + gameObject.addPropertyElement( "inventory", + "A sealed package addressed to 'Mr Hackworth, Goblin street'" ); + gameObject.addPropertyElement( "inventory", "A handfull of sunflower seeds" ); + gameObject.addPropertyElement( "inventory", + "A strange looking device with a soft rubber button" ); + gameObject.addPropertyElement( "inventory", "Blueprints titled 'EXPERIMENTAL - Do not build!'" ); } catch ( ParameterValidationException e ) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } gameObject.addDirectUpdateListener( new AllMessagesFilter(), new MessageListener() { + public void onMessage( final Message message ) { if ( message instanceof PropertyChangedMessage ) @@ -51,17 +102,9 @@ System.out.println( "propertyChangedMessage.getValue() = " + propertyChangedMessage.getValue() ); } } + } ); - - - final TextFieldUi hp = new TextFieldUi( gameObject.getId(), "hp" ); - final TextFieldUi mana = new TextFieldUi( gameObject.getId(), "mana" ); - final PanelUi panelUi = new PanelUi(); - panelUi.addUi( hp ); - panelUi.addUi( mana ); - swingClientUi.setRootUi( panelUi ); - - swingClientUi.start(); + return gameObject; } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.