
com.globalmentor.swing.AbstractSequencePanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of globalmentor-swing Show documentation
Show all versions of globalmentor-swing Show documentation
GlobalMentor Java Swing library.
The newest version!
/*
* Copyright © 1996-2009 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 com.globalmentor.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import com.globalmentor.model.Verifiable;
/**
* Base class for a panel that allows progression in a sequence.
*
* When progressing through the sequence, this panel attempts to verify the content component, if it is {@link Verifiable}, before changing position in the
* sequence and before finishing.
*
* @author Garret Wilson
* @see Verifiable
*/
public abstract class AbstractSequencePanel extends ToolStatusPanel {
/** The lazily-created default default component. */
private Component defaultComponent;
/** The action for starting the sequence. */
private final Action startAction;
/** @return The action for starting the sequence. */
public Action getStartAction() {
return startAction;
}
/** The action for going to the previous component. */
private final Action previousAction;
/** @return The action for going to the previous component. */
public Action getPreviousAction() {
return previousAction;
}
/** The action for going to the next component. */
private final Action nextAction;
/** @return The action for going to the next component. */
public Action getNextAction() {
return nextAction;
}
/** The action for finishing the sequence. */
private final Action finishAction;
/** @return The action for finishing the sequence. */
public Action getFinishAction() {
return finishAction;
}
/** The action for confirming an action. */
private final Action confirmAction;
/** @return The action for confirming an action. */
public Action getConfirmAction() {
return confirmAction;
}
/**
* The action for advancing; serves as a proxy for the start, next, and finish actions, depending on the state of the sequence.
*/
private final ProxyAction advanceAction;
/**
* The action for advancing; serves as a proxy for the start, next, and finish actions, depending on the state of the sequence.
* @see #getStartAction()
* @see #getNextAction()
* @see #getFinishAction()
*/
public ProxyAction getAdvanceAction() {
return advanceAction;
}
/** The button for staring the sequence; created from the corresponding action. */
private final JButton startButton;
/**
* @return The action for starting the sequence; created from the corresponding action.
* @see #getStartAction
*/
private JButton getStartButton() {
return startButton;
}
/** The button for going to the previous component; created from the corresponding action. */
private final JButton previousButton;
/**
* @return The action for going to the previous component; created from the corresponding action.
* @see #getPreviousAction
*/
private JButton getPreviousButton() {
return previousButton;
}
/** The button for going to the next component; created from the corresponding action. */
private final JButton nextButton;
/**
* @return The action for going to the next component; created from the corresponding action.
* @see #getNextAction
*/
private JButton getNextButton() {
return nextButton;
}
/** The button for finishing the sequence; created from the corresponding action. */
private final JButton finishButton;
/**
* @return The action for finishing the sequence; created from the corresponding action.
* @see #getFinishAction
*/
private JButton getFinishButton() {
return finishButton;
}
/** The button for advancing in the sequence; created from the corresponding action. */
private final JButton advanceButton;
/**
* @return The action for advancing in sequence; created from the corresponding action.
* @see #getAdvanceAction
*/
private JButton getAdvanceButton() {
return advanceButton;
}
/** Whether the advance buttons are distinct or dual-duty. */
private boolean distinctAdvance;
/**
* @return Whether the advance buttons are distinct or dual-duty; this defaults to false
.
*/
public boolean isDistinctAdvance() {
return distinctAdvance;
}
/**
* Sets whether the advance buttons are distinct or dual-duty.
* @param distinct true
if there should be distinct buttons for start, next, and finish, or false
if one button should share these
* responsibilitiese.
*/
public void setDistinctAdvance(final boolean distinct) {
distinctAdvance = distinct;
}
/** The length of time, in milliseconds, to wait for confirmation when applicable. */
protected static final int CONFIRM_DELAY = 5000;
/** The timer that allows confirmation only within a specified time. */
private final Timer confirmTimer;
/** @return The timer that allows confirmation only within a specified time. */
//TODO del if not needed protected Timer getConfirmTimer() {return confirmTimer;}
/** The action currently being confirmed and which, if confirmed, will be performed. */
private Action confirmingAction;
/** The action currently being confirmed and which, if confirmed, will be performed. */
protected Action getConfirmingAction() {
return confirmingAction;
}
/**
* Starts the confirmation timer and, if confirmation is received within the required amount of time, the given action is taken. Alternatively, if no action
* is given, the confirmation process is stopped. If the action is already waiting for confirmation, no action is taken.
* @param newConfirmingAction The action to perform if confirmation is received, or null
if no action should be pending confirmation.
*/
protected void setConfirmingAction(final Action newConfirmingAction) {
final Action oldConfirmingAction = confirmingAction; //get the action currently waiting for confirmation
if(oldConfirmingAction != newConfirmingAction) { //if the pending action is really changing
confirmTimer.stop(); //stop any confirmations currently pending
confirmingAction = newConfirmingAction; //update the confirming action
if(newConfirmingAction != null) { //if there is a new action waiting to be confirmed
confirmTimer.restart(); //start the confirmation countdown
}
updateStatus(); //update the status to show whether an action is waiting to be confirmed
}
}
/** Whether each navigation of the sequence must be confirmed. */
private boolean confirmNavigation = false;
/** @return true
if each navigation should be confirmed. */
public boolean isConfirmNavigation() {
return confirmNavigation;
}
/**
* Sets whether each navigation must be confirmed.
* @param confirmNavigation true
if each navigation must be confirmed.
*/
public void setConfirmNavigation(final boolean confirmNavigation) {
this.confirmNavigation = confirmNavigation;
}
/** Default constructor. */
public AbstractSequencePanel() {
this(true, true); //construct and initialize the panel with toolbar and status bar
}
/**
* Toolbar and status bar option constructor.
* @param hasToolBar Whether this panel should have a toolbar.
* @param hasStatusBar Whether this panel should have a status bar.
*/
public AbstractSequencePanel(final boolean hasToolBar, final boolean hasStatusBar) {
this(hasToolBar, hasStatusBar, true); //do the default construction and initialize
}
/**
* Toolbar and status bar option constructor with optional initialization
* @param hasToolBar Whether this panel should have a toolbar.
* @param hasStatusBar Whether this panel should have a status bar.
* @param initialize true
if the panel should initialize itself by calling the initialization methods.
*/
public AbstractSequencePanel(final boolean hasToolBar, final boolean hasStatusBar, boolean initialize) {
super(hasToolBar, hasStatusBar, false); //construct the panel, but don't initialize
defaultComponent = null; //show that we haven't used the default component, yet
startAction = new StartAction(); //create the actions
previousAction = new PreviousAction();
nextAction = new NextAction();
finishAction = new FinishAction();
advanceAction = new ProxyAction(startAction); //the advance action will initially proxy the start action
confirmAction = new ConfirmAction();
confirmTimer = new Timer(CONFIRM_DELAY, new ActionListener() { //create a new action listener that will remove any confirming action after a delay
public void actionPerformed(final ActionEvent actionEvent) {
setConfirmingAction(null);
} //if the timer runs out, show that there is no confirmation action
});
confirmTimer.setRepeats(false); //we only have one waiting period for confirmation
confirmingAction = null; //there is currently no action being confirmed
startButton = new JButton(startAction); //create the buttons
previousButton = new JButton(previousAction);
nextButton = new JButton(nextAction);
finishButton = new JButton(finishAction);
advanceButton = new JButton(advanceAction);
distinctAdvance = false; //default to shared actions for advancing
setStatusBarPosition(BorderLayout.NORTH); //put the status bar at the top of the panel by default
setToolBarPosition(BorderLayout.SOUTH); //put the toolbar at the bottom of the panel by default
if(initialize) //if we should initialize the panel
initialize(); //initialize everything
}
/**
* Initializes actions in the action manager.
* @param actionManager The implementation that manages actions.
*/
protected void initializeActions(final ActionManager actionManager) {
super.initializeActions(actionManager); //do the default initialization
if(isDistinctAdvance()) { //if we should have distinct advance, use separate actions
actionManager.addToolAction(getStartAction());
actionManager.addToolAction(new ActionManager.SeparatorAction());
actionManager.addToolAction(getPreviousAction());
actionManager.addToolAction(getNextAction());
} else { //if we should not have distinct advance, use a dual-use action
actionManager.addToolAction(getPreviousAction());
actionManager.addToolAction(getAdvanceAction());
}
actionManager.addToolAction(new ActionManager.SeparatorAction());
actionManager.addToolAction(getConfirmAction());
}
/** Initializes the user interface. */
protected void initializeUI() {
if(getToolBar() != null) //if we have a toolbar
getToolBar().setButtonTextVisible(true); //show text on the toolbar buttons
super.initializeUI(); //do the default initialization
previousButton.setHorizontalTextPosition(SwingConstants.LEADING); //change the text position of the previous button
setContentComponent(getDefaultComponent()); //start with the default component
setPreferredSize(new Dimension(300, 200)); //set an arbitrary preferred size
}
/**
* Updates the states of the actions, including enabled/disabled status, proxied actions, etc.
*/
public void updateStatus() {
super.updateStatus(); //update the default actions
getStartAction().setEnabled(getAdvanceAction().getProxiedAction() != getStartAction()); //only allow starting if we haven't started, yet
getPreviousAction().setEnabled(hasPrevious()); //only allow going backwards if we have a previous step
getNextAction().setEnabled(hasNext()); //only allow going backwards if we have a next step
getFinishAction().setEnabled(!hasNext()); //only allow finishing if there are no next components
getConfirmAction().setEnabled(isConfirmNavigation() && getConfirmingAction() != null); //only allow confirmation if confirmation is enabled and there is an action waiting to be confirmed
if(getToolBar() != null) { //if we have a toolbar
final Component confirmComponent = getToolBar().getComponent(getConfirmAction()); //see if the confirm action is on the toolbar
if(confirmComponent != null) { //if the action has a corresponding component on the toolbar
confirmComponent.setVisible(isConfirmNavigation()); //only show the confirm action if navigation confirmation is enabled
}
}
if(getAdvanceAction().getProxiedAction() != getStartAction()) { //if we've already started
//determine if advancing should go to the next item in the sequence or finish
getAdvanceAction().setProxiedAction(hasNext() ? getNextAction() : getFinishAction());
}
final JRootPane rootPane = getRootPane(); //get the ancestor root pane, if there is one
if(rootPane != null) { //if there is a root pane
final JButton defaultButton; //determind the default button
if(isDistinctAdvance()) { //if we're using distinct buttons for advance
defaultButton = hasNext() ? getNextButton() : getFinishButton(); //set the next button as the default unless we're finished; in that case, set the finish button as the default
} else { //if we're using a dual-use button for advance
defaultButton = getAdvanceButton(); //set the advance button as the default
}
rootPane.setDefaultButton(defaultButton); //update the default button
}
final Action confirmingAction = getConfirmingAction(); //see if there is an action waiting to be confirmed
if(confirmingAction != null) { //if there is an action waiting to be confirmed
confirmingAction.setEnabled(false); //disable the confirming action, because it will be accessed indirectly through the confirmation action
}
}
/**
* Returns the default component displayed before the sequence begins or if there is no sequence available.
*
* This implementation returns an empty spacer component.
*
* @return The default component displayed before the sequence begins.
*/
protected Component getDefaultComponent() {
if(defaultComponent == null) { //if we haven't created the default component, yet
defaultComponent = Box.createGlue(); //create glue to fill the panel
}
return defaultComponent; //return our shared default component
}
/**
* Starts the sequence by going to the first step in the sequence.
*
* Derived classes should override start()
.
*
* @see #start
*/
public void goStart() {
getAdvanceAction().setProxiedAction(getNextAction()); //from now on, advancing will go to the next item in the sequence
start(); //go the start
}
/**
* Starts the sequence by going to the first step in the sequence.
* @see #goFirst
*/
protected void start() {
goFirst(); //go to the first step in the sequence
}
/**
* Goes to the first step in the sequence.
*
* Derived classes should override first()
.
*
* @see #first
*/
public void goFirst() {
first(); //go the first
updateStatus(); //update the status
}
/** Goes to the first step in the sequence. */
protected abstract void first();
/**
* Goes to the previous step in the sequence. If there is no previous component, no action occurs.
*
* Derived classes should override previous()
.
*
* @see #previous
*/
public void goPrevious() {
if(hasPrevious() && verifyCurrentComponent()) { //if there is a previous step and the current component verifies
previous(); //go the previous step
updateStatus(); //update the status
}
}
/**
* Goes to the previous step in the sequence. If there is no previous step, no action occurs.
*/
protected abstract void previous();
/**
* Goes to the next step in the sequence. If there is no next step, no action occurs.
*
* Derived classes should override next()
.
*
* @see #next
*/
public void goNext() {
if(hasNext() && verifyCurrentComponent()) { //if there is a next step and the current component verifies
next(); //go the next step
updateStatus(); //update the status
}
}
/**
* Goes to the next step in the sequence. If there is no next step, no action occurs.
*/
protected abstract void next();
/**
* Verifies the contents and finishes the sequence. Usually a derived class will not modify this method and instead override finish()
, which this
* method calls if the contents of the current component verifies.
* @see Verifiable#verify
* @see #finish
*/
public void goFinish() {
if(verifyCurrentComponent()) { //if the current component verifies
finish(); //actually finish
}
}
/**
* Finishes the sequence. This version sets the option panel value to JOptionPane.OK_OPTION
if this panel is embedded in an option pane.
* @see JOptionPane#OK_OPTION
* @see BasicPanel#setOptionPaneValue
*/
protected void finish() {
setOptionPaneValue(new Integer(JOptionPane.OK_OPTION)); //set the value of the option pane to OK, if we're embedded in an option pane
}
/**
* Verifies the contents of the current component, if the current component can be verified.
* @return true
if the current component was verified or is not verifiable, else false
if the current component was verifiable but
* returned false
when verified.
* @see Verifiable#verify
*/
protected boolean verifyCurrentComponent() {
final Component currentComponent = getContentComponent(); //get the current content component
if(currentComponent instanceof Verifiable) { //if we can verify the component's contents
if(!((Verifiable)currentComponent).verify()) { //if the current component's contents do not verify
return false; //show that the component verified incorrectly
}
}
return true; //show that the component didn't verify incorrectly
}
/** @return true
if there is a next step after the current one. */
protected abstract boolean hasNext();
/** @return true
if there is a previous step before the current one. */
protected abstract boolean hasPrevious();
/**
* Creates and displays a sequence dialog based upon JOptionPane
, showing this sequence panel.
*
* This method does not start the sequence. The calling method may start the sequence before calling this method by invoking goStart()
.
*
* @param parentComponent Determines the Frame
in which the dialog is displayed; if null
, or if the parentComponent
has
* no Frame
, a default Frame
is used.
* @param title The title string for the dialog.
* @return One of the JOptionPane
result constants.
* @see JOptionPane#showOptionDialog
* @see #goStart()
*/
public int showSequenceDialog(final Component parentComponent, final String title) {
final JButton[] buttons;
final JButton initialButton;
if(isDistinctAdvance()) { //if we should have distinct advance, use separate actions
buttons = new JButton[] { getPreviousButton(), getNextButton(), getFinishButton() };
initialButton = getNextButton();
} else { //if we should not have distinct advance, use a dual-use action
buttons = new JButton[] { getPreviousButton(), getAdvanceButton() };
initialButton = getAdvanceButton();
}
//show an option pane in the parent component using the given title
return BasicOptionPane.showOptionDialog(parentComponent, this, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, buttons,
initialButton);
}
/** Action for starting the progression. */
protected class StartAction extends AbstractAction {
/** Default constructor. */
public StartAction() {
super("Start"); //create the base class TODO i18n
putValue(SHORT_DESCRIPTION, "Start sequence"); //set the short description TODO i18n
putValue(LONG_DESCRIPTION, "Start the sequence."); //set the long description TODO i18n
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_S)); //set the mnemonic key TODO i18n
putValue(SMALL_ICON, IconResources.getIcon(IconResources.ENTER_ICON_FILENAME)); //load the correct icon
}
/**
* Called when the action should be performed.
* @param actionEvent The event causing the action.
*/
public void actionPerformed(final ActionEvent actionEvent) {
if(!isConfirmNavigation() || getConfirmingAction() == this) { //if this action is waiting to be confirmed
goStart(); //try to finish the sequence
setConfirmingAction(null); //show that we're not waiting for confirmation on anything
} else { //if we should confirm this action
setConfirmingAction(this); //perform this action subject to confirmation
}
}
}
/** Action for going to the previous component. */
protected class PreviousAction extends AbstractAction {
/** Default constructor. */
public PreviousAction() {
super("Previous"); //create the base class TODO i18n
putValue(SHORT_DESCRIPTION, "Previous step"); //set the short description TODO i18n
putValue(LONG_DESCRIPTION, "Go to the previous step."); //set the long description TODO i18n
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P)); //set the mnemonic key TODO i18n
putValue(SMALL_ICON, IconResources.getIcon(IconResources.HAND_POINT_LEFT_ICON_FILENAME)); //load the correct icon
}
/**
* Called when the action should be performed.
* @param actionEvent The event causing the action.
*/
public void actionPerformed(final ActionEvent actionEvent) {
if(!isConfirmNavigation() || getConfirmingAction() == this) { //if this action is waiting to be confirmed
goPrevious(); //go to the previous component
setConfirmingAction(null); //show that we're not waiting for confirmation on anything
} else { //if we should confirm this action
setConfirmingAction(this); //perform this action subject to confirmation
}
}
}
/** Action for going to the next component. */
protected class NextAction extends AbstractAction {
/** Default constructor. */
public NextAction() {
super("Next"); //create the base class TODO i18n
putValue(SHORT_DESCRIPTION, "Next step"); //set the short description TODO i18n
putValue(LONG_DESCRIPTION, "Go to the next step."); //set the long description TODO i18n
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_N)); //set the mnemonic key TODO i18n
putValue(SMALL_ICON, IconResources.getIcon(IconResources.HAND_POINT_RIGHT_ICON_FILENAME)); //load the correct icon
}
/**
* Called when the action should be performed.
* @param actionEvent The event causing the action.
*/
public void actionPerformed(final ActionEvent actionEvent) {
if(!isConfirmNavigation() || getConfirmingAction() == this) { //if this action is waiting to be confirmed
goNext(); //go to the next component
setConfirmingAction(null); //show that we're not waiting for confirmation on anything
} else { //if we should confirm this action
setConfirmingAction(this); //perform this action subject to confirmation
}
}
}
/** Action for finishing the progression. */
protected class FinishAction extends AbstractAction {
/** Default constructor. */
public FinishAction() {
super("Finish"); //create the base class TODO i18n
putValue(SHORT_DESCRIPTION, "Finish sequence"); //set the short description TODO i18n
putValue(LONG_DESCRIPTION, "Finish the sequence."); //set the long description TODO i18n
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_F)); //set the mnemonic key TODO i18n
putValue(SMALL_ICON, IconResources.getIcon(IconResources.EXIT_ICON_FILENAME)); //load the correct icon
}
/**
* Called when the action should be performed.
* @param actionEvent The event causing the action.
*/
public void actionPerformed(final ActionEvent actionEvent) {
if(!isConfirmNavigation() || getConfirmingAction() == this) { //if this action is waiting to be confirmed
goFinish(); //try to finish the sequence
setConfirmingAction(null); //show that we're not waiting for confirmation on anything
} else { //if we should confirm this action
setConfirmingAction(this); //perform this action subject to confirmation
}
}
}
/** Action for confirming an action. */
class ConfirmAction extends AbstractAction {
/** Constructs an activity submit action. */
public ConfirmAction() {
super("Confirm"); //create the base class TODO i18n
putValue(SHORT_DESCRIPTION, "Confirm input."); //set the short description TODO i18n
putValue(LONG_DESCRIPTION, "Confirm the input."); //set the long description TODO i18n
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_C)); //set the mnemonic key TODO i18n
putValue(SMALL_ICON, IconResources.getIcon(IconResources.ACCEPT_ICON_FILENAME)); //load the correct icon
}
/**
* Called when the action should be performed.
* @param actionEvent The event causing the action.
*/
public void actionPerformed(final ActionEvent actionEvent) {
final Action confirmingAction = getConfirmingAction(); //see if there is an action waiting to be confirmed
if(confirmingAction != null) { //if there is an action waiting to be confirmed
confirmTimer.stop(); //the action is confirmed; suspend waiting for confirmation
confirmingAction.actionPerformed(actionEvent); //perform the confirming action
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy