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

io.guise.framework.component.SequenceCardPanel 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.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.util.List;

import com.globalmentor.beans.*;
import com.globalmentor.model.TaskState;

import io.guise.framework.Bookmark;
import io.guise.framework.component.layout.*;
import io.guise.framework.model.*;
import io.guise.framework.prototype.AbstractActionPrototype;
import io.guise.framework.prototype.ActionPrototype;

import static com.globalmentor.java.Classes.*;
import static io.guise.framework.Resources.*;
import static io.guise.framework.theme.Theme.*;

/**
 * A card panel representing a sequence of cards. If any card has constraints of {@link TaskCardConstraints}, this class will update the task state based upon
 * visited and validated status.
 * @author Garret Wilson
 * @see CardLayout
 */
public class SequenceCardPanel extends AbstractCardPanel implements ArrangeContainer, Commitable {

	/** The bound property of the sequence state. */
	public static final String STATE_PROPERTY = getPropertyName(SequenceCardPanel.class, "state");
	/** The bound property of whether the transitions are enabled. */
	public static final String TRANSITION_ENABLED_PROPERTY = getPropertyName(SequenceCardPanel.class, "transitionEnabled");

	/** The current state of the sequence, or null if the sequence is not occurring. */
	private TaskState state = null;

	/** @return The current state of the sequence, or null if the sequence is not occurring. */
	public TaskState getState() {
		return state;
	}

	/**
	 * Sets the current state of the sequence. This is a bound property.
	 * @param newState The current state of the sequence, or null if the sequence is not occurring.
	 * @see #STATE_PROPERTY
	 */
	protected void setState(final TaskState newState) {
		if(state != newState) { //if the value is really changing
			final TaskState oldState = state; //get the current value
			state = newState; //update the value
			firePropertyChange(STATE_PROPERTY, oldState, newState);
		}
	}

	/** The current transition in the sequence, or null if no transition is occurring. */
	private SequenceTransition transition = null;

	/** @return The current transition in the sequence, or null if no transition is occurring. */
	public SequenceTransition getTransition() {
		return transition;
	}

	/** Whether transitions are enabled, so that changing selected cards will cause the appropriate validate/commit functionality. */
	private boolean transitionEnabled = true;

	/** @return Whether transitions are enabled, so that changing selected cards will cause the appropriate validate/commit functionality. */
	public boolean isTransitionEnabled() {
		return transitionEnabled;
	}

	/**
	 * Sets whether transitions are enabled, so that changing selected cards will cause the appropriate validate/commit functionality. This is a bound property of
	 * type Boolean.
	 * @param newTransitionEnabled true if transitions are enabled, so that changing selected cards will cause the appropriate validate/commit
	 *          functionality.
	 * @see #TRANSITION_ENABLED_PROPERTY
	 */
	public void setTransitionEnabled(final boolean newTransitionEnabled) {
		if(transitionEnabled != newTransitionEnabled) { //if the value is really changing
			final boolean oldTransitionEnabled = transitionEnabled; //get the current value
			transitionEnabled = newTransitionEnabled; //update the value
			firePropertyChange(TRANSITION_ENABLED_PROPERTY, Boolean.valueOf(oldTransitionEnabled), Boolean.valueOf(newTransitionEnabled));
		}
	}

	/** The prototype for the previous action. */
	private final ActionPrototype previousActionPrototype;

	/** @return The prototype for the previous action. */
	public ActionPrototype getPreviousActionPrototype() {
		return previousActionPrototype;
	}

	/** The prototype for the next action. */
	private final ActionPrototype nextActionPrototype;

	/** @return The prototype for the next action. */
	public ActionPrototype getNextActionPrototype() {
		return nextActionPrototype;
	}

	/** The prototype for the action to finish the sequence. */
	private final ActionPrototype finishActionPrototype;

	/** @return The prototype for the action to finish the sequence. */
	public ActionPrototype getFinishActionPrototype() {
		return finishActionPrototype;
	}

	/** The prototype for the continue action, which delegates to {@link #goNext()} or {@link #goFinish()}, depending on the state of {@link #hasNext()}. */
	private final ActionPrototype continueActionPrototype;

	/** @return The prototype for the next action, which delegates to {@link #goNext()} or {@link #goFinish()}, depending on the state of {@link #hasNext()}.. */
	public ActionPrototype getContinueActionPrototype() {
		return continueActionPrototype;
	}

	/** The prototype for the action to cancel the sequence. */
	private final ActionPrototype cancelActionPrototype;

	/** @return The prototype for the action to cancel the sequence. */
	public ActionPrototype getCancelActionPrototype() {
		return cancelActionPrototype;
	}

	/** The listener that ensures making a card displayed doesn't create an invalid gap in a sequence of valid cards. */
	private PropertyChangeListener cardDisplayedChangeListener = new AbstractGenericPropertyChangeListener() { //create a new display change listener

		@Override
		public void propertyChange(final GenericPropertyChangeEvent propertyChangeEvent) { //if the child component's display status changes (we're really listening for the constraint's displayable status, which we'll check later)
			if(isTransitionEnabled() && Boolean.TRUE.equals(propertyChangeEvent.getNewValue())) { //if transitions are turned on and the card is being displayed
				final Object target = propertyChangeEvent.getTarget(); //get the target of the event
				if(target instanceof ControlConstraints) { //if this is control constraints changing
					//TODO del if not needed					final ControlConstraints controlConstraints=(ControlConstraints)target;	//get the control constraints target
					final int currentIndex = getSelectedIndex(); //get the currently selected index
					if(currentIndex >= 0) { //if a card is selected
						final Component card = (Component)propertyChangeEvent.getSource(); //get the card source of the event
						final int cardIndex = indexOf(card); //get the index of the card
						if(cardIndex > currentIndex) { //if this card is in front of the current card
							for(int i = size() - 1; i >= cardIndex; --i) { //for each card (including the one that changed) after the current card
								setEnabled(get(i), false); //disable this component
							}
						}
					}
				}
			}
		}

	};

	/** Default constructor. */
	public SequenceCardPanel() {
		this(new CardLayout()); //default to flowing vertically
	}

	/**
	 * Layout constructor.
	 * @param layout The layout definition for the container.
	 * @throws NullPointerException if the given layout is null.
	 */
	protected SequenceCardPanel(final CardLayout layout) {
		super(layout); //construct the parent class
		//previous action prototype
		previousActionPrototype = new AbstractActionPrototype(LABEL_PREVIOUS, GLYPH_PREVIOUS) {

			@Override
			protected void action(final int force, final int option) {
				goPrevious(); //go to the previous card
			};
		};
		//next action prototype
		nextActionPrototype = new AbstractActionPrototype(LABEL_NEXT, GLYPH_NEXT) {

			@Override
			protected void action(final int force, final int option) {
				goNext(); //go to the next card
			};
		};
		//finish action prototype
		finishActionPrototype = new AbstractActionPrototype(LABEL_FINISH, GLYPH_FINISH) {

			@Override
			protected void action(final int force, final int option) {
				goFinish(); //finish the sequence
			};
		};
		//continue action prototype
		continueActionPrototype = new AbstractActionPrototype(LABEL_NEXT, GLYPH_NEXT) {

			@Override
			protected void action(final int force, final int option) {
				if(hasNext()) { //if there is a next card
					goNext(); //go to the next card
				} else { //if there is no next card
					goFinish(); //finish the sequence
				}
			};
		};
		//cancel action prototype
		cancelActionPrototype = new AbstractActionPrototype(LABEL_CANCEL, GLYPH_CANCEL) {

			@Override
			protected void action(final int force, final int option) {
				goCancel(); //cancel the sequence
			};
		};
		addVetoableChangeListener(VALUE_PROPERTY, new SequenceCardVetoableChangeListener()); //do validation as needed on card changes
		addPropertyChangeListener(VALUE_PROPERTY, new AbstractGenericPropertyChangeListener() {

			@Override
			public void propertyChange(final GenericPropertyChangeEvent propertyChangeEvent) { //if the selected card changes
				if(isTransitionEnabled()) { //if transitions are enabled
					final Component oldCard = propertyChangeEvent.getOldValue();
					final Component newCard = propertyChangeEvent.getNewValue(); //TODO comment all this
					if(oldCard != null) { //if there was an old card
						final Constraints constraints = oldCard.getConstraints();
						if(constraints instanceof TaskCardConstraints) {
							final TaskCardConstraints taskCardConstraints = (TaskCardConstraints)constraints;
							if(taskCardConstraints.getTaskState() == TaskState.ERROR && oldCard.isValid()) { //if there was an error but the old card is now valid
								taskCardConstraints.setTaskState(TaskState.INCOMPLETE);
							}
							final int oldIndex = indexOf(oldCard); //get the index of the old card
							assert oldIndex >= 0 : "Expected old card to be present in the container.";
							final int newIndex = indexOf(newCard); //see what index the new value has
							if(newIndex > oldIndex) { //if we advanced to a new card
								taskCardConstraints.setTaskState(TaskState.COMPLETE); //show that the old task is complete
							}
						}
					}
					if(newCard != null) { //if there is a new card
						final Constraints constraints = newCard.getConstraints();
						if(constraints instanceof TaskCardConstraints) {
							final TaskCardConstraints taskCardConstraints = (TaskCardConstraints)constraints;
							if(taskCardConstraints.getTaskState() == null) {
								taskCardConstraints.setTaskState(TaskState.INCOMPLETE);
							}
						}
					}
				}
				previousActionPrototype.setEnabled(hasPrevious()); //enable or disable the previous action prototype
				final boolean hasNext = hasNext(); //see if we can go forward
				nextActionPrototype.setEnabled(hasNext); //enable or disable the previous action prototype
				finishActionPrototype.setEnabled(!hasNext); //enable or disable the finish action prototype
				if(hasNext) { //if there is a next TODO replace this with a proxy action prototype
					continueActionPrototype.setLabel(nextActionPrototype.getLabel()); //copy the next action prototype properties
					continueActionPrototype.setGlyphURI(nextActionPrototype.getGlyphURI());
					continueActionPrototype.setEnabled(nextActionPrototype.isEnabled());
				} else { //if there is no next
					continueActionPrototype.setLabel(finishActionPrototype.getLabel()); //copy the finish action prototype properties
					continueActionPrototype.setGlyphURI(finishActionPrototype.getGlyphURI());
					continueActionPrototype.setEnabled(finishActionPrototype.isEnabled());
				}
			}

		});
	}

	/**
	 * {@inheritDoc}
	 * 

* This version installs a listener for the component's displayed status. *

*/ @Override protected void addComponent(final int index, final Component childComponent) { super.addComponent(index, childComponent); //initialize the child component as needed childComponent.addPropertyChangeListener(DISPLAYED_PROPERTY, cardDisplayedChangeListener); //listen for changes in the component's displayed status and make sure the sequence is disabled as needed } /** * {@inheritDoc} *

* This version uninstalls a listener for the component's displayed status. *

*/ @Override protected void removeComponent(final Component childComponent) { childComponent.removePropertyChangeListener(DISPLAYED_PROPERTY, cardDisplayedChangeListener); //stop listening for changes in the component's displayed status super.removeComponent(childComponent); //uninitialize the child component as needed } /** * {@inheritDoc} *

* This version updates the error status of the child component's constraints if those constraints implement {@link TaskCardConstraints}. *

*/ @Override protected void childComponentValidPropertyChanged(final Component childComponent, final boolean oldValid, final boolean newValid) { super.childComponentValidPropertyChanged(childComponent, oldValid, newValid); //call the parent version final Constraints constraints = childComponent.getConstraints(); //get the child component constraints if(constraints instanceof TaskCardConstraints) { //if these are task card constraints final TaskCardConstraints taskCardConstraints = ((TaskCardConstraints)constraints); //get the constraints as task card constraints if(taskCardConstraints.getTaskState() != null) { //if the task is started taskCardConstraints.setTaskState(newValid ? TaskState.INCOMPLETE : TaskState.ERROR); //update the task status based upon the new valid state } } } /** * Sets the new selected card. This version validates the input on the currently selected card as needed. * @param newValue The new selected card. * @throws ValidationException if the provided value is not valid. * @see #getValidator() * @see #VALUE_PROPERTY */ /*TODO del public void setValue(final Component newValue) throws ValidationException { final Component currentCard=getValue(); //get the currently selected card if(currentCard!=null) { //if there is a selected card, do validation if we need to final int selectedIndex=indexOf(currentCard); //get the index of the selected card assert selectedIndex>=0 : "Expected selected card to be present in the container."; final int newIndex=indexOf(newValue); //see what index the new value has if(newIndex<0) { //if the new value isn't in the container TODO maybe put this in a default card panel validator return; //TODO decide what to do here } if(newIndex>selectedIndex) { //if we're advancing forward in the sequence try { currentCard.validate(); //validate the currently selected card } catch(final ComponentExceptions componentException) { //if the current card doesn't validate return; //don't go forward TODO decide what to do here } } } super.setValue(newValue); //set the value normally } */ /** * Determines if there is a previous step in the sequence. This version returns true if there is a selected card and there exists a card before * the selected card. * @return true if there is a previous step in the sequence. */ public boolean hasPrevious() { return getPrevious() != null; //see if there is a previous component } /** * Determines the previous component in the sequence. Components that are not displayed or not enabled based upon their associated constraints are skipped. * @return The previous component in the sequence, or null if there is no previous component in the sequence. */ public Component getPrevious() { final Component selectedComponent = getSelectedValue(); //get the selected component return selectedComponent != null ? getPrevious(selectedComponent) : null; //return the previous component of the selected component, or null if no component is selected } /** * Determines the previous component in the sequence relative to the given component. Components that are not displayed or not enabled based upon their * associated constraints are skipped. * @param component The component the previous component to which should be found. * @return The previous component in the sequence, or null if there is no previous component in the sequence. * @throws NullPointerException if the given component is null. */ protected Component getPrevious(final Component component) { //TODO check for null and throw an exception instead of this lenient check final int selectedIndex = indexOf(component); //get the index of the given component if(selectedIndex >= 0) { //if a card is selected for(int i = selectedIndex - 1; i >= 0; --i) { //for each previous card final Component card = get(i); //get this card if(isDisplayed(card) && isEnabled(card)) { //if the card is displayed and enabled return card; //return this card } } } return null; //indicate that there is no previous card } /** * Determines if there is a next step in the sequence. This version returns true if there is a selected card and there exists a card after the * selected card. * @return true if there is a next step in the sequence. */ public boolean hasNext() { return getNext() != null; //see if there is a next component } /** * Determines the next component in the sequence. Components that are not displayed or not enabled based upon their associated constraints are skipped. * @return The next component in the sequence, or null if there is no next component in the sequence. */ public Component getNext() { final int selectedIndex = getSelectedIndex(); //get the selected index if(selectedIndex >= 0) { //if a card is selected final int cardCount = size(); //find out how many cards there are for(int i = selectedIndex + 1; i < cardCount; ++i) { //for each next card final Component card = get(i); //get this card if(isDisplayed(card)) //if the card is displayed //TODO decide; currently we need to get disabled cards so that we can enable them if(isDisplayed(card) && isEnabled(card)) //if the card is displayed and enabled { return card; //return this card } } } return null; //indicate that there is no next card } /** * Goes to the previous step in the sequence. If there is no previous step, no action occurs. This method calls {@link #hasPrevious()}. */ public void goPrevious() { final SequenceTransition oldTransition = transition; //save the current transition transition = SequenceTransition.PREVIOUS; //indicate the current transition try { final Component previousCard = getPrevious(); //get the previous card if(previousCard != null) { //if there is a previous card try { final Component selectedCard = getSelectedValue(); //get the selected card assert selectedCard != null : "No card selected, even though hasPrevious() should have returned false if no card is selected."; // try { // selectedCard.validate(); //validate the selected card setValue(previousCard); //select the previous card } // catch(final ComponentExceptions componentException) { //TODO improve; inform user } } catch(final PropertyVetoException propertyVetoException) { //TODO fix throw new AssertionError(validationException); //TODO improve } } } finally { transition = oldTransition; //restore the old transition (usually null) } } /** * Flag to indicate that the card is changing locally and has already been validated. Prevents double validation by the vetoable change listener. */ private boolean alreadyValidated = false; /** * Advances to the next step in the sequence. If the current card passes validation, the next card is enabled before advancing. If there is no next step, no * action occurs. This method calls {@link #getNext()}. */ public void goNext() { //TODO del Log.trace("ready to go next"); final SequenceTransition oldTransition = transition; //save the current transition transition = SequenceTransition.NEXT; //indicate the current transition try { final Component nextCard = getNext(); //get the next card if(nextCard != null) { //if there is a next card if(isTransitionEnabled()) { //if transitions are enabled // TODO del Log.trace("got next card; ready to get selected card"); final Component selectedCard = getSelectedValue(); //get the selected card //TODO del Log.trace("got next card", nextCard, "selected card", selectedCard); assert selectedCard != null : "No card selected, even though getNext() should have returned null if no card is selected."; // TODO del Log.trace("ready to validate selected card"); if(validate()) { //validate this panel; if everything, including the selected card, is valid // TODO del Log.trace("card validated"); //show any notifications, anyway final List notifications = getNotifications(selectedCard); //get the notifications from the card final Runnable valueSetter = new Runnable() { //create code for notifying, committing the card and advancing to the next card @Override public void run() { try { setValue(nextCard); //select the next card } catch(final PropertyVetoException propertyVetoException) { //if the change is vetoed, don't do anything special // TODO del Log.warn(propertyVetoException); } finally { transition = oldTransition; //restore the old transition (usually null) } } }; // TODO del Log.trace("ready to do notify/commit/advance"); final int notificationCount = notifications.size(); //see how many notifications there are if(notificationCount > 0) { //if there is at least one notification getSession().notify(valueSetter, notifications.toArray(new Notification[notifications.size()])); //do the notification, followed by the value setting } else { //if there are no notifications valueSetter.run(); //change the value right away with no notifications } } else { //if the card didn't validate transition = oldTransition; //restore the old transition (usually null) } } else { //if transitions aren't enabled try { setValue(nextCard); //select the next card } catch(final PropertyVetoException propertyVetoException) { //if the change is vetoed, don't do anything special } finally { transition = oldTransition; //restore the old transition (usually null) } } } else { //if there is no next card transition = oldTransition; //restore the old transition (usually null) } } finally { //TODO del transition=oldTransition; //restore the old transition (usually null) } } /** * Finishes the sequence. This method validates and commits the current card, and then calls {@link #finish()}. If no card is selected, no action occurs. The * state is set to {@link TaskState#COMPLETE}. */ public void goFinish() { //TODO fix transition; right now it won't be set properly if we have a notification final SequenceTransition oldTransition = transition; //save the current transition transition = SequenceTransition.NEXT; //indicate the current transition try { final Component selectedCard = getSelectedValue(); //get the selected card if(selectedCard != null) { //if a card is selected //TODO decide if we want to disable validation if transitions are disabled if(validate()) { //validate this panel; if everything, including the selected card, is valid //show any notifications, anyway final List notifications = getNotifications(selectedCard); //get the notifications from the card final Runnable finisher = new Runnable() { //create code for committing and finishing @Override public void run() { try { commit(); //commit this panel finish(); //finish the sequence setState(TaskState.COMPLETE); //indicate that the sequence is finished } catch(final IOException ioException) { //if there is a problem commiting the result getSession().notify(new Notification(ioException)); //notify the user } } }; final int notificationCount = notifications.size(); //see how many notifications there are if(notificationCount > 0) { //if there is at least one notification getSession().notify(finisher, notifications.toArray(new Notification[notifications.size()])); //do the notification, followed by finishing } else { //if there are no notifications finisher.run(); //finish right away with no notifications } } } } finally { transition = oldTransition; //restore the old transition (usually null) } } /** * Cancels the sequence. This method calls {@link #cancel()}. The state is set to {@link TaskState#CANCELED}. */ public void goCancel() { cancel(); //cancel the sequence setState(TaskState.CANCELED); //indicate that the sequence has been canceled } /** * Resets the sequence by navigating to the first card and disabling all subsequent cards. The state is set to {@link TaskState#INCOMPLETE}. */ public void resetSequence() { resetValue(); //reset the value so that changing the index won't trigger validation if(size() > 0) { //if there are cards in the sequence try { setSelectedIndexes(0); //browse to the first index } catch(final PropertyVetoException propertyVetoException) { //TODO fix throw new AssertionError(validationException); //TODO improve } final Component selectedCard = getValue(); //get the selected card for(final Component card : this) { //for each card final Constraints constraints = card.getConstraints(); //get the card constraints //TODO ask the layout to create constraints, because the card may not yet have constraints if(constraints instanceof Enableable) { //if these constraints can be enabled or disabled ((Enableable)constraints).setEnabled(card == selectedCard); //only the selected card should be enabled } } } setState(TaskState.INCOMPLETE); //indicate that the sequence is started } @Override public boolean validate() { if(!super.validate()) { //validate the component normally; if the component does not validate Notification notification = getNotification(); //see if this panel has any notification final Component selectedCard = getValue(); //get the selected card if(selectedCard != null) { //if there is a selected card final Constraints constraints = selectedCard.getConstraints(); //get the current card constraints if(constraints instanceof TaskCardConstraints) { //if these are task card constraints ((TaskCardConstraints)constraints).setTaskState(TaskState.ERROR); //set the task status to error } if(notification == null) { //if we don't have a notification final List notifications = getNotifications(selectedCard); //get the notifications from the card if(!notifications.isEmpty()) { //if there are notifications notification = notifications.get(0); //use the first notification } } } if(notification == null) { //if we didn't find a custom notification notification = new Notification(VALIDATION_FALSE_MESSAGE_RESOURCE_REFERENCE, Notification.Severity.ERROR); //use a general validation notification } getSession().notify(notification); //indicate that there was a validation error } return isValid(); //return the current valid state } /** * Commits the data. This version commits the selected card if there is a selected card and it implements {@link Commitable}. Subclass versions should call * this version. * @throws IOException if there is an error committing data. */ public void commit() throws IOException { final Component selectedCard = getSelectedValue(); //get the selected card if(selectedCard instanceof Commitable) { //if the selected card is committable ((Commitable)selectedCard).commit(); //tell the card to commit itself } } /** * Finishes the sequence. This version does nothing. */ public void finish() { } /** * Cancels the sequence. This version does nothing. */ public void cancel() { } /** * {@inheritDoc} *

* This version finds the first previous enabled and displayed card, searching backwards from the requested card, if the requested card is not enabled and * displayed. This version chooses the first card if no card is requested. *

*/ @Override protected Component getComponent(final Bookmark bookmark) { /*TODO del if not wanted; why did we put this in in the first place? will improving AbstractCardPanel to update the URL help, so that we will always have the correct bookmark? //choose the first card if no card was specified final String parameterValue; //see if there is a parameter value if(bookmark!=null) { //if there is a bookmark final String parameterName=getName(); //use this component's name as the bookmark parameter name parameterValue=parameterName!=null ? bookmark.getParameterValue(parameterName) : null; //get the parameter value if there is a parameter name } else { //if there is no bookmark parameterValue=null; //there is no parameter value } if(parameterValue==null) { //if no parameter for this component was specified if(size()>0) { //if this panel has components return get(0); //automatically choose the first card } } */ Component component = super.getComponent(bookmark); //get the requested component normally if(component != null) { //if a component was requested if(!isDisplayed(component) || !isEnabled(component)) { //if the component is not displayed or not enabled component = getPrevious(component); //get the previous component } } return component; //return the determined component } /** * A vetoable property change listener validates cards before changing to new cards. When {@link SequenceTransition#NEXT} occurs, validation is assumed to * have already occurred and it not performed again. * @author Garret Wilson */ protected class SequenceCardVetoableChangeListener extends AbstractGenericVetoableChangeListener { @Override public void vetoableChange(final GenericPropertyChangeEvent genericPropertyChangeEvent) throws PropertyVetoException { if(isTransitionEnabled() && genericPropertyChangeEvent.getNewValue() != getValue()) { //if transitions are enabled and the index is really changing (the VetoableChangeListener contract says that if a change is vetoed this method will be called again with a reverse change, and we don't want to validate in those circumstances) // TODO del Log.trace("validating vetoable change"); final Component currentCard = genericPropertyChangeEvent.getOldValue(); //get the currently selected card if(currentCard != null) { //if there is a selected card, do validation if we need to final int selectedIndex = indexOf(currentCard); //get the index of the selected card // TODO del Log.trace("selected index:", selectedIndex); assert selectedIndex >= 0 : "Expected selected card to be present in the container."; final Component newCard = genericPropertyChangeEvent.getNewValue(); //get the new card final int newIndex = indexOf(newCard); //see what index the proposed new value has //TODO del Log.trace("selected index", selectedIndex, "new index", newIndex, "current selected index", getSelectedIndex()); /*TODO del if(newIndex<0) { //if the new value isn't in the container TODO maybe put this in a default card panel validator return false; //we can't select a card not in the container } */ final int indexDelta = newIndex - selectedIndex; //get the relative index // TODO del Log.trace("index delta:", indexDelta); final SequenceTransition transition = getTransition(); //get thet current transition // TODO del Log.trace("current transition:", transition); if(transition != SequenceTransition.NEXT && transition != SequenceTransition.PREVIOUS && !isEnabled(newCard)) { //if the new card is not enabled (with exceptions for the previous and next buttons) throw new PropertyVetoException("Card not enabled.", genericPropertyChangeEvent); //indicate that the new card isn't enabled and the value shouldn't be changed TODO i18n } final boolean needsValidationCommit; //we'll find out whether we should validate the card; going forward always gets validation; going backwards only gets validation if there is any subsequent card that is enabled if(newIndex > selectedIndex) { //if we're advancing forward in the sequence needsValidationCommit = true; //we always validate going forwards } else { //if we're going backwards in the sequence (or staying put) boolean hasNextCard = false; //we'll see if there is a next card TODO see if we can combine this code with getNext() eventually final int cardCount = size(); //find out how many cards there are for(int i = selectedIndex + 1; i < cardCount; ++i) { //for each next card final Component card = get(i); //get this card if(isDisplayed(card) && isEnabled(card)) { //if the card is displayed and enabled hasNextCard = true; //show that we have a next card break; //stop looking for a next card } } needsValidationCommit = hasNextCard; //we need validation if we have a next card } if(needsValidationCommit) { //if we need to validate and commit // TODO del Log.trace("need to validate and commit; ready to validate"); if(transition == SequenceTransition.NEXT || validate()) { //validate this panel (unless this is a "next" transition, meaning validation already occurred); if everything, including the selected card, is valid // TODO del Log.trace("validated; ready to check canTransition if needed"); if(!(currentCard instanceof SequenceTransitionable) || ((SequenceTransitionable)currentCard).canTransition(indexDelta)) { //if the current card is sequence transitionable, see if it OKs the transition try { // TODO del Log.trace("can transition; ready to commit"); commit(); //commit this panel // TODO del Log.trace("committed; ready to set enabled if supported"); final Constraints newCardConstraints = newCard.getConstraints(); //get the new card's constraints if(newCardConstraints instanceof Enableable) { //if the new card constraints is enableable ((Enableable)newCardConstraints).setEnabled(true); //enable the new card constraints } // TODO del Log.trace("enabled"); } catch(final IOException ioException) { //if there is a problem commiting the result getSession().notify(new Notification(ioException)); //notify the user throw new PropertyVetoException(ioException.getMessage(), genericPropertyChangeEvent); //indicate that the card value shouldn't be changed } } else { //if the card denies transition throw new PropertyVetoException("Card denied transition.", genericPropertyChangeEvent); //indicate that the card didn't allow transition TODO i18n } } else { //if the panel doesn't validate throw new PropertyVetoException(VALIDATION_FALSE_MESSAGE_RESOURCE_REFERENCE, genericPropertyChangeEvent); //indicate that the old card didn't validate and the value shouldn't be changed } } else { //if we don't need to validate and commit if(currentCard instanceof SequenceTransitionable && !((SequenceTransitionable)currentCard).canTransition(indexDelta)) { //if the current card is sequence transitionable and denies transition throw new PropertyVetoException("Card denied transition.", genericPropertyChangeEvent); //indicate that the card didn't allow transition TODO i18n } } //TODO del Log.trace("vetoable change seemed to go OK"); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy