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

io.guise.framework.component.AbstractListSelectControl 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;

import java.beans.PropertyVetoException;
import java.lang.reflect.*;
import java.util.*;

import static java.util.Objects.*;

import static com.globalmentor.java.Classes.*;

import com.globalmentor.event.EventListenerManager;

import io.guise.framework.converter.*;
import io.guise.framework.event.*;
import io.guise.framework.model.*;
import io.guise.framework.validator.*;

/**
 * Abstract implementation of a control to allow selection by the user of a value from a list. The component valid status is updated before a change in the
 * {@link #VALUE_PROPERTY} or the {@link #VALIDATOR_PROPERTY} is fired. This implementation does not yet fully support elements that appear more than once in
 * the model.
 * @param  The type of values to select.
 * @author Garret Wilson
 */
public abstract class AbstractListSelectControl extends AbstractCompositeStateControl
		implements ListSelectControl {

	/** The list select model used by this component. */
	private final ListSelectModel listSelectModel;

	/** @return The list select model used by this component. */
	protected ListSelectModel getListSelectModel() {
		return listSelectModel;
	}

	/** The strategy used to generate a component to represent each value in the model. */
	private ValueRepresentationStrategy valueRepresentationStrategy;

	@Override
	public ValueRepresentationStrategy getValueRepresentationStrategy() {
		return valueRepresentationStrategy;
	}

	@Override
	public void setValueRepresentationStrategy(final ValueRepresentationStrategy newValueRepresentationStrategy) {
		if(valueRepresentationStrategy != newValueRepresentationStrategy) { //if the value is really changing
			final ValueRepresentationStrategy oldValueRepresentationStrategy = valueRepresentationStrategy; //get the old value
			valueRepresentationStrategy = requireNonNull(newValueRepresentationStrategy, "Value representation strategy cannot be null."); //actually change the value
			firePropertyChange(VALUE_REPRESENTATION_STRATEGY_PROPERTY, oldValueRepresentationStrategy, newValueRepresentationStrategy); //indicate that the value changed
		}
	}

	/**
	 * {@inheritDoc}
	 * 

* This version is provided to allow public access. *

*/ @Override public Component getComponent(final V value) { return super.getComponent(value); //delegate to the parent version } @Override protected ValueComponentState createComponentState(final V value) { //TODO assert that there is a representation strategy, or otherwise check //TODO improve and/or change parameters final Component valueComponent = getValueRepresentationStrategy().createComponent(this, value, -1, false, false); //create a new component for the value return new ValueComponentState(valueComponent); //create a new component state for the value's component and metadata } /** * List select model and value representation strategy constructor. * @param listSelectModel The component list select model. * @param valueRepresentationStrategy The strategy to create controls to represent this model's values. * @throws NullPointerException if the given list select model and/or value representation strategy is null. */ public AbstractListSelectControl(final ListSelectModel listSelectModel, final ValueRepresentationStrategy valueRepresentationStrategy) { this.valueRepresentationStrategy = requireNonNull(valueRepresentationStrategy, "Value representation strategy cannot be null."); this.listSelectModel = requireNonNull(listSelectModel, "List select model cannot be null."); //save the list select model this.listSelectModel.addPropertyChangeListener(getRepeatPropertyChangeListener()); //listen and repeat all property changes of the list select model this.listSelectModel.addVetoableChangeListener(getRepeatVetoableChangeListener()); //listen and repeat all vetoable changes of the list select model this.listSelectModel.addListListener(new ListListener() { //install a repeater list listener to listen to the decorated model @Override public void listModified(final ListEvent listEvent) { //if the list is modified fireListModified(listEvent.getIndex(), listEvent.getAddedElement(), listEvent.getRemovedElement()); //repeat the event, indicating the component as the source of the event } }); this.listSelectModel.addListSelectionListener(new ListSelectionListener() { //install a repeater list selection listener to listen to the decorated model @Override public void listSelectionChanged(final ListSelectionEvent selectionEvent) { //if the list selection changes fireSelectionChanged(selectionEvent.getAddedElement(), selectionEvent.getRemovedElement()); //repeat the event, indicating the component as the source of the event } }); addListListener(new ListListener() { //listen for list changes @Override public void listModified(final ListEvent listEvent) { //if list is modified clearComponentStates(); //clear all the components and component states TODO probably do this on a component-by-component basis }; }); } /** * {@inheritDoc} *

* This version first updates the valid status if the value is reported as being changed. *

*/ @Override protected void firePropertyChange(final String propertyName, final VV oldValue, final VV newValue) { if(VALUE_PROPERTY.equals(propertyName) || VALIDATOR_PROPERTY.equals(propertyName)) { //if the value property or the validator property is being reported as changed updateValid(); //update the valid status based upon the new property, so that any listeners will know whether the new property is valid } super.firePropertyChange(propertyName, oldValue, newValue); //fire the property change event normally } /** * {@inheritDoc} *

* This version performs no additional checks if the control is disabled. *

*/ @Override protected boolean determineValid() { if(!super.determineValid()) { //if we don't pass the default validity checks return false; //the component isn't valid } return !isEnabled() || getListSelectModel().isValidValue(); //the component is valid if the list select model has a valid value (don't check the list select model if the control is not enabled) } /** * {@inheritDoc} *

* This version validates the associated list select model. This version performs no additional checks if the control is disabled. *

*/ @Override public boolean validate() { super.validate(); //validate the parent class if(isEnabled()) { //if the control is enabled try { getListSelectModel().validateValue(); //validate the value model } catch(final ValidationException validationException) { //if there is a validation error //TODO del componentException.setComponent(this); //make sure the exception knows to which component it relates setNotification(new Notification(validationException)); //add notification of this error to the component } } return isValid(); //return the current valid state } /** * {@inheritDoc} *

* This version resets the control value. *

* @see #resetValue() */ @Override public void reset() { super.reset(); //reset normally resetValue(); //reset the control value } //ValueModel delegations @Override public V getDefaultValue() { return getListSelectModel().getDefaultValue(); } @Override public V getValue() { return getListSelectModel().getValue(); } @Override public void setValue(final V newValue) throws PropertyVetoException { getListSelectModel().setValue(newValue); } @Override public void clearValue() { getListSelectModel().clearValue(); } @Override public void resetValue() { getListSelectModel().resetValue(); } @Override public Validator getValidator() { return getListSelectModel().getValidator(); } @Override public void setValidator(final Validator newValidator) { getListSelectModel().setValidator(newValidator); } @Override public boolean isValidValue() { return getListSelectModel().isValidValue(); } @Override public void validateValue() throws ValidationException { getListSelectModel().validateValue(); } @Override public Class getValueClass() { return getListSelectModel().getValueClass(); } //SelectModel delegations @Override public boolean replace(final V oldValue, final V newValue) { return getListSelectModel().replace(oldValue, newValue); } @Override public V getSelectedValue() { return getListSelectModel().getSelectedValue(); } @Override public V[] getSelectedValues() { return getListSelectModel().getSelectedValues(); } @Override public void setSelectedValues(final V... values) throws PropertyVetoException { getListSelectModel().setSelectedValues(values); } //ListSelectModel delegations @Override public ListSelectionPolicy getSelectionPolicy() { return getListSelectModel().getSelectionPolicy(); } @Override public int getSelectedIndex() { return getListSelectModel().getSelectedIndex(); } @Override public int[] getSelectedIndexes() { return getListSelectModel().getSelectedIndexes(); } @Override public void setSelectedIndexes(int... indexes) throws PropertyVetoException { getListSelectModel().setSelectedIndexes(indexes); } @Override public void addSelectedIndexes(int... indexes) throws PropertyVetoException { getListSelectModel().addSelectedIndexes(indexes); } @Override public void removeSelectedIndexes(int... indexes) throws PropertyVetoException { getListSelectModel().removeSelectedIndexes(indexes); } @Override public boolean isValueDisplayed(final V value) { return getListSelectModel().isValueDisplayed(value); } @Override public void setValueDisplayed(final V value, final boolean newDisplayed) { getListSelectModel().setValueDisplayed(value, newDisplayed); } //TODO fix property change event @Override public boolean isIndexDisplayed(final int index) { return getListSelectModel().isIndexDisplayed(index); } @Override public void setIndexDisplayed(final int index, final boolean newDisplayed) { getListSelectModel().setIndexDisplayed(index, newDisplayed); } //TODO fix property change event @Override public boolean isValueEnabled(final V value) { return getListSelectModel().isValueEnabled(value); } @Override public void setValueEnabled(final V value, final boolean newEnabled) { getListSelectModel().setValueEnabled(value, newEnabled); } //TODO fix property change event @Override public boolean isIndexEnabled(final int index) { return getListSelectModel().isIndexEnabled(index); } @Override public void setIndexEnabled(final int index, final boolean newEnabled) { getListSelectModel().setIndexEnabled(index, newEnabled); } //TODO fix property change event @Override public void addListListener(final ListListener listListener) { getEventListenerManager().add(ListListener.class, listListener); //add the listener } @Override public void removeListListener(final ListListener listListener) { getEventListenerManager().remove(ListListener.class, listListener); //remove the listener } @Override public void addListSelectionListener(final ListSelectionListener selectionListener) { getEventListenerManager().add(ListSelectionListener.class, selectionListener); //add the listener } @Override public void removeListSelectionListener(final ListSelectionListener selectionListener) { getEventListenerManager().remove(ListSelectionListener.class, selectionListener); //remove the listener } /** * Fires an event to all registered list listeners indicating the list was modified. * @param index The index at which an element was added and/or removed, or -1 if the index is unknown. * @param addedElement The element that was added to the list, or null if no element was added or it is unknown whether or which elements were * added. * @param removedElement The element that was removed from the list, or null if no element was removed or it is unknown whether or which elements * were removed. * @see ListListener * @see ListEvent */ protected void fireListModified(final int index, final V addedElement, final V removedElement) { final EventListenerManager eventListenerManager = getEventListenerManager(); //get the event listener manager if(eventListenerManager.hasListeners(ListListener.class)) { //if there are appropriate listeners registered final ListEvent listEvent = new ListEvent(this, index, addedElement, removedElement); //create a new event for(final ListListener listener : eventListenerManager.getListeners(ListListener.class)) { //for each registered event listeners listener.listModified(listEvent); //dispatch the event } } } /** * Fires an event to all registered selection listeners indicating the selection changed. * @param addedIndex The index that was added to the selection, or null if no index was added or it is unknown whether or which indices were * added. * @param removedIndex The index that was removed from the list, or null if no index was removed or it is unknown whether or which indices were * removed. * @see ListSelectionListener * @see ListSelectionEvent */ protected void fireSelectionChanged(final Integer addedIndex, final Integer removedIndex) { final EventListenerManager eventListenerManager = getEventListenerManager(); //get the event listener manager if(eventListenerManager.hasListeners(ListSelectionListener.class)) { //if there are appropriate listeners registered final ListSelectionEvent selectionEvent = new ListSelectionEvent(this, addedIndex, removedIndex); //create a new event for(final ListSelectionListener listener : eventListenerManager.getListeners(ListSelectionListener.class)) { //for each registered event listeners listener.listSelectionChanged(selectionEvent); //dispatch the event } } } //List delegations @Override public int size() { return getListSelectModel().size(); } @Override public boolean isEmpty() { return getListSelectModel().isEmpty(); } @Override public boolean contains(final Object value) { return getListSelectModel().contains(value); } @Override public Iterator iterator() { return getListSelectModel().iterator(); } @Override public Object[] toArray() { return getListSelectModel().toArray(); } @Override public T[] toArray(final T[] array) { return getListSelectModel().toArray(array); } /** * {@inheritDoc} *

* This version delegates to {@link #add(int, Object)}. *

*/ @Override public boolean add(final V value) { return getListSelectModel().add(value); } @Override public boolean remove(final Object value) { return getListSelectModel().remove(value); } @Override public boolean containsAll(final Collection collection) { return getListSelectModel().containsAll(collection); } @Override public boolean addAll(final Collection collection) { return getListSelectModel().addAll(collection); } @Override public synchronized boolean addAll(final int index, final Collection collection) { return getListSelectModel().addAll(index, collection); } @Override public boolean removeAll(final Collection collection) { return getListSelectModel().removeAll(collection); } @Override public boolean retainAll(final Collection collection) { return getListSelectModel().retainAll(collection); } @Override public void clear() { getListSelectModel().clear(); } @Override public V get(final int index) { return getListSelectModel().get(index); } @Override public V set(final int index, final V value) { return getListSelectModel().set(index, value); } @Override public void add(final int index, final V value) { getListSelectModel().add(index, value); } @Override public V remove(final int index) { return getListSelectModel().remove(index); } @Override public int indexOf(final Object value) { return getListSelectModel().indexOf(value); } @Override public int lastIndexOf(final Object value) { return getListSelectModel().lastIndexOf(value); } @Override public ListIterator listIterator() { return getListSelectModel().listIterator(); } @Override public ListIterator listIterator(final int index) { return getListSelectModel().listIterator(index); } @Override public List subList(final int fromIndex, final int toIndex) { return getListSelectModel().subList(fromIndex, toIndex); } /** * An encapsulation of a component for a tree node along with other metadata, such as whether the component was editable when created. * @author Garret Wilson */ protected static class ValueComponentState extends AbstractCompositeStateComponent.ComponentState { /** * Constructor * @param component The component for a tree node. * @throws NullPointerException if the given component is null. */ public ValueComponentState(final Component component) { super(component); //construct the parent class } } /** * A list select value representation strategy that creates a component by converting the value to a info model. The specified component class must have a * constructor that takes a single {@link InfoModel} as an argument. * @param The type of value the strategy is to represent. * @author Garret Wilson */ public static class ConverterInfoModelValueRepresentationStrategy implements ValueRepresentationStrategy { /** The converter to use for displaying the value as a string. */ private final Converter converter; /** @return The converter to use for displaying the value as a string. */ public Converter getConverter() { return converter; } /** The component constructor that takes a single {@link InfoModel} argument. */ private final Constructor componentInfoModelConstructor; /** * Value class constructor with a default converter. This implementation uses a {@link DefaultStringLiteralConverter}. * @param valueClass The class indicating the type of value to convert. * @param componentClass The class of component to create. * @throws NullPointerException if the given value class and/or component class is null. * @throws IllegalArgumentException if the given component class does not have a constructor with a single {@link InfoModel} constructor. */ public ConverterInfoModelValueRepresentationStrategy(final Class valueClass, final Class componentClass) { this(componentClass, AbstractStringLiteralConverter.getInstance(valueClass)); //construct the class with the appropriate string literal converter } /** * Converter constructor. * @param converter The converter to use for displaying the value as a string. * @param componentClass The class of component to create. * @throws NullPointerException if the given converter is null. * @throws IllegalArgumentException if the given component class does not have a constructor with a single {@link InfoModel} constructor. */ public ConverterInfoModelValueRepresentationStrategy(final Class componentClass, final Converter converter) { this.converter = requireNonNull(converter, "Converter cannot be null."); //save the converter componentInfoModelConstructor = getCompatiblePublicConstructor(requireNonNull(componentClass, "Component class cannot be null."), InfoModel.class); //get the constructor for the component if(componentInfoModelConstructor == null) { //if there is no appropriate constructor throw new IllegalArgumentException("Component class " + componentClass + " has no constructor with a single info model parameter."); } } /** * Creates a component for the given list value. This implementation constructs a component from a info model that converts the value using the saved * converter. * @param model The model containing the value. * @param value The value for which a component should be created. * @param index The index of the value within the list, or -1 if the value is not in the list (e.g. for representing no selection). * @param selected true if the value is selected. * @param focused true if the value has the focus. * @return A new component to represent the given value. * @see #getConverter() */ public Component createComponent(final ListSelectModel model, final VV value, final int index, final boolean selected, final boolean focused) { try { return componentInfoModelConstructor.newInstance(new ValueConverterInfoModel(value, getConverter())); //create a component that will convert the value to a string in a info model } catch(final InstantiationException instantiationException) { throw new AssertionError(instantiationException); } catch(final IllegalAccessException illegalAccessException) { throw new AssertionError(illegalAccessException); } catch(final InvocationTargetException invocationTargetException) { throw new AssertionError(invocationTargetException); } } } /** * A default list select value representation strategy that creates a {@link Label}. A label component will be generated containing the default string * representation of a value. * @param The type of value the strategy is to represent. * @see Label * @author Garret Wilson */ public static class DefaultValueRepresentationStrategy extends ConverterInfoModelValueRepresentationStrategy { /** * Value class constructor with a default converter. This implementation uses a {@link DefaultStringLiteralConverter}. * @param valueClass The class indicating the type of value to convert. * @throws NullPointerException if the given value class is null. */ public DefaultValueRepresentationStrategy(final Class valueClass) { this(AbstractStringLiteralConverter.getInstance(valueClass)); //construct the class with the appropriate string literal converter } /** * Converter constructor. * @param converter The converter to use for displaying the value as a string. * @throws NullPointerException if the given converter is null. */ public DefaultValueRepresentationStrategy(final Converter converter) { super(Label.class, converter); //use a label to represent values } /** * {@inheritDoc} *

* This version uses covariant types to specify that a {@link Label} is returned. *

*/ @Override public Label createComponent(final ListSelectModel model, final VV value, final int index, final boolean selected, final boolean focused) { return (Label)super.createComponent(model, value, index, selected, focused); //construct the component normally and cast it to a Label } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy