All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.guise.framework.component.layout.AbstractValueLayout Maven / Gradle / Ivy

There is a newer version: 0.5.3
Show newest version
/*
 * Copyright © 2005-2008 GlobalMentor, Inc. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.guise.framework.component.layout;

import static io.guise.framework.Resources.*;
import static io.guise.framework.model.AbstractValueModel.createPropertyVetoException;
import static java.text.MessageFormat.*;

import java.beans.*;

import com.globalmentor.java.Objects;

import io.guise.framework.component.*;
import io.guise.framework.model.*;
import io.guise.framework.validator.*;

/**
 * A layout that manages the selection of child components, only one of which can be selected at a time. A value layout can only be used with a
 * {@link Container} as its owner. The layout maintains its own value model that maintains the current selected component. If a child component implements
 * {@link Activeable} the child component is set as active when selected and set as inactive when the child component is unselected.
 * @param  The type of layout constraints associated with each component.
 * @author Garret Wilson
 */
public abstract class AbstractValueLayout extends AbstractLayout implements ValueModel {

	/** The value model used by this component. */
	private final ValueModel valueModel;

	/** @return The value model used by this component. */
	protected ValueModel getValueModel() {
		return valueModel;
	}

	/** The lazily-created listener of constraint property changes. */
	//TODO del if not needed	private CardConstraintsPropertyChangeListener cardConstraintsPropertyChangeListener=null;

	/** @return The lazily-created listener of card constraint property changes. */
	/*TODO del if not needed
			protected CardConstraintsPropertyChangeListener getConstraintsPropertyChangeListener()
			{
				if(cardConstraintsPropertyChangeListener==null) {	//if we haven't yet created a property change listener for constraints
					cardConstraintsPropertyChangeListener=new CardConstraintsPropertyChangeListener();	//create a new constraints property change listener
				}
				return cardConstraintsPropertyChangeListener;	//return the listener of constraints properties
			}
	*/

	/** The index of the selected component, or -1 if the index is not known and should be recalculated. */
	private int selectedIndex = -1;

	/** @return The index of the selected component, or -1 if no component is selected. */
	public int getSelectedIndex() {
		if(selectedIndex < 0) { //if there is no valid selected index, make sure the index is up-to-date
			final Component selectedComponent = getValue(); //get the selected component
			if(selectedComponent != null) { //if a component is selected, we'll need to update the selected index
				final int newSelectedIndex = getOwner().indexOf(selectedComponent); //update the selected index with the index in the container of the selected component
				assert newSelectedIndex >= 0 : "Selected component " + selectedComponent + " is not in the container.";
				if(isSettingValue) { //we're setting the value, return the selected index without updating the variable, as a VetoablePropertyListener might be calling this method before the value is actually changed, so we want to leave the selected index uncached until after the value is actually changed
					return newSelectedIndex; //return the new selected index
				} else { //if we're not in the middle of setting the value
					selectedIndex = newSelectedIndex; //update the cached selected index; we'll return it later
				}
			}
		}
		return selectedIndex; //return the selected index, which we've verified is up-to-date
	}

	/**
	 * Sets the index of the selected component. If the value change is vetoed by the installed validator, the validation exception will be accessible via
	 * {@link PropertyVetoException#getCause()}.
	 * @param newIndex The index of the selected component, or -1 if no component is selected.
	 * @throws IllegalStateException if this layout has not yet been installed into a container.
	 * @throws IndexOutOfBoundsException if the index is out of range.
	 * @throws PropertyVetoException if the component at the given index is not a valid compoment to select or the change has otherwise been vetoed.
	 */
	public void setSelectedIndex(final int newIndex) throws PropertyVetoException {
		final Container container = getOwner(); //get the layout's container
		if(container == null) { //if we haven't been installed into a container
			throw new IllegalStateException("Layout does not have container.");
		}
		final Component component = newIndex >= 0 ? container.get(newIndex) : null; //get the component at the given index, if a valid index was given
		if(newIndex != getSelectedIndex() && component != getValue()) { //if we're really changing either the selected index or the component
			selectedIndex = -1; //uncache the selected index (don't actually change it yet---we want to make sure the value model allows the value to be changed)
			setValue(component); //update the component value, throwing a validation exception if this index can't be selected
		}
	}

	/**
	 * {@inheritDoc}
	 * 

* This version updates the new component's active status if the component implements {@link Activeable}. *

*/ @Override public void addComponent(final Component component) { super.addComponent(component); //add the component normally if(getValue() == null) { //if there is no component selected try { setSelectedIndex(0); //select the first component } catch(final PropertyVetoException propertyVetoException) { //if we can't select the first component, don't do anything } } if(component instanceof Activeable) { //if the component is activable ((Activeable)component).setActive(getValue() == component); //if the card is not the selected card, tell it that it is not active } } /** * {@inheritDoc} *

* This implementation updates the selected component if necessary. *

*/ @Override public void removeComponent(final Component component) { super.removeComponent(component); //remove the component normally if(component == getValue()) { //if the selected component was removed final Container container = getOwner(); //get our container final int selectedIndex = container.indexOf(component); //find the current index of the component that is being removed final int containerSize = container.size(); //find out how many components are in the container final int newSelectedComponentIndex; //we'll determine the new selected index (that is, the index of the new selected component in this current state; it won't be the new selected index after removal) if(selectedIndex < containerSize - 1) { //if this component wasn't the last component newSelectedComponentIndex = selectedIndex + 1; //get the subsequent component } else { //if this was the last component tha twas removed newSelectedComponentIndex = containerSize - 2; //get the second-to last component } try { setValue(container.get(newSelectedComponentIndex)); //update the component value, throwing a validation exception if this index can't be selected } catch(final PropertyVetoException propertyVetoException) { //if we can't select the next component getValueModel().resetValue(); //reset the selected component value TODO is there something better we can do here? } } this.selectedIndex = -1; //always uncache the selected index, because the index of the selected component might have changed } @Override public Container getOwner() { return (Container)super.getOwner(); } @Override public void setOwner(final LayoutComponent newOwner) { super.setOwner((Container)newOwner); //make sure the new owner is a container } /** Default constructor. */ public AbstractValueLayout() { this.valueModel = new DefaultValueModel(Component.class); //create a new value model this.valueModel.addPropertyChangeListener(getRepeatPropertyChangeListener()); //listen and repeat all property changes of the value model this.valueModel.addVetoableChangeListener(getRepeatVetoableChangeListener()); //listen and repeat all vetoable changes of the value model } @Override public Component getDefaultValue() { return getValueModel().getDefaultValue(); } @Override public Component getValue() { return getValueModel().getValue(); } /** The flat that indicates whether we are in the middle of setting the value. */ private boolean isSettingValue = false; /** * {@inheritDoc} *

* This version makes sure that the given component is contained in the container, and resets the cached selected index so that it can be recalculated. This * version updates the active status of the old and new components if the implement {@link Activeable}. *

*/ @Override public void setValue(final Component newValue) throws PropertyVetoException { final Component oldValue = getValue(); //get the old value if(!Objects.equals(oldValue, newValue)) { //if a new component is given final Container container = getOwner(); //get the layout's container if(container == null) { //if we haven't been installed into a container throw new IllegalStateException("Layout does not have container."); } if(newValue != null && !container.contains(newValue)) { //if there is a new component that isn't contained in the container //create a custom validation exception final ValidationException validationException = new ValidationException( format(getSession().dereferenceString(VALIDATOR_INVALID_VALUE_MESSAGE_RESOURCE_REFERENCE), newValue.toString()), newValue); throw createPropertyVetoException(this, validationException, VALUE_PROPERTY, oldValue, newValue); //throw a property veto exception representing the validation error } if(oldValue instanceof Activeable) { //if the old value is activable ((Activeable)oldValue).setActive(false); //tell the old card it is no longer active } isSettingValue = true; //indicate that we're setting the value try { selectedIndex = -1; //uncache the selected index getValueModel().setValue(newValue); //set the new value normally } finally { isSettingValue = false; //indicate that we're no longer setting the value } if(newValue instanceof Activeable) { //if the new value is activable ((Activeable)newValue).setActive(true); //tell the new card it is active } } } /** * {@inheritDoc} *

* This version resets the cached selected index so that it can be recalculated. *

*/ @Override public void clearValue() { selectedIndex = -1; //uncache the selected index getValueModel().clearValue(); } /** * {@inheritDoc} *

* This version resets the cached selected index so that it can be recalculated. *

*/ @Override public void resetValue() { selectedIndex = -1; //uncache the selected index getValueModel().resetValue(); } @Override public Validator getValidator() { return getValueModel().getValidator(); } @Override public void setValidator(final Validator newValidator) { getValueModel().setValidator(newValidator); } @Override public boolean isValidValue() { return getValueModel().isValidValue(); } @Override public void validateValue() throws ValidationException { getValueModel().validateValue(); } @Override public Class getValueClass() { return getValueModel().getValueClass(); } /** * A property change listener that listens for changes in a constraint object's properties and fires a layout constraints property change event in response. * This version also fires model {@link ValuePropertyChangeEvent}s if appropriate. A {@link LayoutConstraintsPropertyChangeEvent} will be fired for each * component associated with the constraints for which a property changed * @author Garret Wilson * @see ValuePropertyChangeEvent */ /*TODO decide if we need this protected class CardConstraintsPropertyChangeListener extends ConstraintsPropertyChangeListener { */ /** * Refires a constraint property change event for the layout in the form of a {@link LayoutConstraintsPropertyChangeEvent}. This version also fires a model * {@link ValuePropertyChangeEvent} if appropriate to satisfy the list select model contract for value state changes. * @param component The component for which a constraint value changed. * @param constraints The constraints for which a value changed. * @param propertyName The name of the property being changed. * @param oldValue The old property value. * @param newValue The new property value. */ /*TODO decide if we need this protected void refirePropertyChange(final Component component, final Constraints constraints, final String propertyName, final V oldValue, final V newValue) { super.refirePropertyChange(component, constraints, propertyName, oldValue, newValue); //refire the event normally if(Constraints.ENABLED_PROPERTY.equals(propertyName)) { //if the enabled constraint changed listSelectModel.fireValuePropertyChange(component, propertyName, oldValue, newValue); //tell the model to fire its own event to satisfy the model's contract } } } */ }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy