/* AbstractWizardDialog.java created 2007-09-17 * */ package org.signalml.app.view.common.dialogs; import static org.signalml.app.util.i18n.SvarogI18n._; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.Window; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import org.signalml.app.model.components.validation.ValidationErrors; import org.signalml.app.util.IconUtils; import org.signalml.app.view.common.dialogs.errors.Dialogs; import org.signalml.plugin.export.SignalMLException; import org.springframework.validation.Errors; /** * Abstract dialog which can have more then one step. * Contains: * <ul> * <li>the panel with CardLayout in which the interface of steps are displayed, * </li><li>the extended control panel with buttons to go to the {@link * NextAction next} and {@link PreviousAction previous} step,</li> * <li>functions that should be implemented by sub-classes:<ul><li> * creation of the interface for the step,</li><li> * {@link #validateDialogStep(int, Object, Errors) validation} of the step,</li> * <li>the operations that should be performed when the step changes.</li></ul> * <li></ul> * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o. */ public abstract class AbstractWizardDialog extends AbstractDialog { private static final long serialVersionUID = 1L; /** * the interface for this dialog with the CardLayout and the number * of cards equal to the {@link #getStepCount() number} of steps */ private JPanel interfacePanel; /** * the interfaces for steps; the number of elements in this array equals * the number of steps */ private JComponent[] interfaceStepComponents; /** * the layout for {@link #interfacePanel} */ private CardLayout cardLayout; /** * {@link NextAction action} to go to the next step */ private NextAction nextAction; /** * {@link PreviousAction action} to go to the previous step */ private PreviousAction previousAction; /** * the button to {@link #nextAction go} to the next step */ private JButton nextButton; /** * the button to {@link #previousAction go} to the previous step */ private JButton previousButton; private int currentStep = 0; /** * Constructor. Sets message source, parent window and if this dialog * blocks top-level windows. * @param f the parent window or null if there is no parent * @param isModal true, dialog blocks top-level windows, false otherwise */ public AbstractWizardDialog(Window f, boolean isModal) { super(f, isModal); } /** * Returns the number of steps this dialog contains. * @return the number of steps */ public abstract int getStepCount(); /** * Returns the interface for the given step. * If such interface doesn't exist it is {@link #createInterface() * created}. * @param step the number of the step * @return the interface for the step */ public JComponent getInterfaceForStep(int step) { if (interfaceStepComponents == null) { interfaceStepComponents = new JComponent[getStepCount()]; } if (interfaceStepComponents[step] == null) { interfaceStepComponents[step] = createInterfaceForStep(step); } return interfaceStepComponents[step]; } /** * Creates the interface for the given step. * Components of this interface depend on the implementation. * @param step the number of the step * @return the interface for the step */ protected abstract JComponent createInterfaceForStep(int step); /** * Validates the given step. * @param step the number of the step to be validated * @param model the model for this dialog (not used in current * implementations) * @param errors the object in which the errors will be stored * @throws SignalMLException depends on the implementation */ public void validateDialogStep(int step, Object model, ValidationErrors errors) throws SignalMLException { /* do nothing */ } /** * {@link #validateDialogStep(int, Object, Errors) Validates} all steps of * this dialog. */ @Override public void validateDialog(Object model, ValidationErrors errors) throws SignalMLException { // default implementation revalidates all steps for (int i=0; i<getStepCount(); i++) { validateDialogStep(i, model, errors); } } /** * Creates the interface for this dialog with the CardLayout and the number * of cards equal to the {@link #getStepCount() number} of steps. * Each card contains the {@link #getInterfaceForStep(int) interface} for * for the consecutive step. */ @Override public JComponent createInterface() { interfacePanel = new JPanel(); cardLayout = new CardLayout(); interfacePanel.setLayout(cardLayout); int stepCount = Math.max(0, getStepCount()); for (int i=0; i<stepCount; i++) { interfacePanel.add(getInterfaceForStep(i), "step"+i); } return interfacePanel; } /** * Creates the control panel that allows to (buttons from left to right): * <ul> * <li>go to the {@link PreviousAction previous} and {@link NextAction * next} step,</li> * <li>finish (if all steps are completed) or cancel this dialog.</li> * </ul> */ @Override protected JPanel createControlPane() { JPanel controlPane = super.createControlPane(); controlPane.setBorder(new EmptyBorder(8, 4, 2, 4)); getOkAction().putValue(AbstractAction.NAME, _("Finish")); getOkButton().setPreferredSize(new Dimension(120, 25)); controlPane.add(Box.createHorizontalStrut(5), 1); nextAction = new NextAction(); nextButton = new JButton(nextAction); nextButton.setHorizontalTextPosition(JButton.LEADING); nextButton.setPreferredSize(new Dimension(130, 25)); getRootPane().setDefaultButton(nextButton); controlPane.add(nextButton, 1); controlPane.add(Box.createHorizontalStrut(3), 1); previousAction = new PreviousAction(); previousButton = new JButton(previousAction); previousButton.setPreferredSize(new Dimension(130, 25)); controlPane.add(previousButton, 1); previousAction.setEnabled(false); return controlPane; } /** * Goes back to step 0. */ @Override protected void resetDialog() { super.resetDialog(); currentStep = 0; getOkAction().setEnabled(isFinishAllowedOnStep(0)); previousAction.setEnabled(false); nextAction.setEnabled(true); getRootPane().setDefaultButton(nextButton); cardLayout.show(interfacePanel,"step0"); } /** * Performs operations necessary when a step is changed. * Depends on an implementation. * @param toStep the number of the step before the change * @param fromStep the number of the step after the change * @param model the model for this dialog * @return {@code true} if operation is successful, {@code false} otherwise * @throws SignalMLException never thrown in implementations */ protected boolean onStepChange(int toStep, int fromStep, Object model) throws SignalMLException { return true; } /** * Returns if this dialog can be finished on a given step. * @param step the number of the step * @return {@code true} if this dialog can be finished (and OK button * should be active), {@code false} otherwise */ public boolean isFinishAllowedOnStep(int step) { return true; } /** * Returns if validation should be performed when the step is changed * back. * By default false. * @return {@code false} */ public boolean isBackTransitionValidated() { return false; } /** * Abstract action performed when a user changes the step (presses * {@link PreviousAction previous} or {@link NextAction next} button. * */ protected class NavigationAction extends AbstractAction { private static final long serialVersionUID = 1L; /** * the number of steps that should be navigated back (negative value) * or forward (positive value) */ protected int step; /** * Constructor. * Sets the step change and the text of the button for this action. * @param step the number of steps that should be navigated back (negative value) * or forward (positive value) * @param text the text of the button for this action */ public NavigationAction(int step, String text) { super(text); this.step = step; } /** * Performed when a user changes the step (presses * {@link PreviousAction previous} or {@link NextAction next} button: * <ul> * <li>{@link AbstractWizardDialog#validateDialogStep(int, Object, * Errors) validates} the current step - if the step is not valid * {@link AbstractWizardDialog#showValidationErrors(Errors) shows} * errors and returns,</li> * <li>{@link AbstractWizardDialog#onStepChange(int, int, Object) * performs} operations necessary when the step is changed,</li> * <li>enables or disables buttons: * <ul><li> {@link PreviousAction previous} and {@link NextAction * next} button depending on the number of the step (if it is first, * last or none of those),</li> * <li> {@link AbstractWizardDialog#getOkAction() OK} button depending * on whether dialog * {@link AbstractWizardDialog#isFinishAllowedOnStep(int) can be * finished} or not.</li></ul> * </ul> */ @Override public void actionPerformed(ActionEvent e) { if (isBackTransitionValidated() || step > 0) { ValidationErrors errors = new ValidationErrors(); try { validateDialogStep(currentStep,getCurrentModel(),errors); } catch (SignalMLException ex) { logger.error("Dialog validation threw an exception", ex); Dialogs.showExceptionDialog(AbstractWizardDialog.this, ex); setCurrentModel(null); setClosedWithOk(false); setVisible(false); return; } if (errors.hasErrors()) { showValidationErrors(errors); return; } } try { boolean allow = onStepChange(currentStep+step, currentStep, getCurrentModel()); if (!allow) { return; } } catch (SignalMLException ex) { logger.error("Dialog transition threw an exception", ex); Dialogs.showExceptionDialog(AbstractWizardDialog.this, ex); setCurrentModel(null); setClosedWithOk(false); setVisible(false); return; } currentStep += step; cardLayout.show(interfacePanel, "step"+currentStep); if (currentStep == 0) { previousAction.setEnabled(false); } else { previousAction.setEnabled(true); } getOkAction().setEnabled(isFinishAllowedOnStep(currentStep)); if (currentStep == (getStepCount()-1)) { nextAction.setEnabled(false); getRootPane().setDefaultButton(getOkButton()); } else { nextAction.setEnabled(true); getRootPane().setDefaultButton(nextButton); } getOkButton().repaint(); } } /** * Action performed when a user presses {@code next} button. * It is a {@link NavigationAction} with a step {@code 1}. */ protected class NextAction extends NavigationAction { private static final long serialVersionUID = 1L; /** * Constructor. Sets the step, the message and the icon. */ public NextAction() { super(1,_("Next")); putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/next.png")); } /** * Performed when a user presses {@code next} button. * If the number of the next step exceeds the number of steps * warning is displayed and no action is taken. * Otherwise parent {@link NavigationAction#actionPerformed(ActionEvent) * function} is called. */ @Override public void actionPerformed(ActionEvent ev) { if ((currentStep + 1) >= getStepCount()) { logger.warn("WARNING: attempt to navigate past step count"); return; } super.actionPerformed(ev); } } /** * Action performed when a user presses {@code previous} button. * It is a {@link NavigationAction} with a step {@code -1}. */ protected class PreviousAction extends NavigationAction { private static final long serialVersionUID = 1L; /** * Constructor. Sets the step, the message and the icon. */ public PreviousAction() { super(-1,_("Back")); putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/previous.png")); } /** * Performed when a user presses {@code previous} button. * If the number of the previous step exceeds the number of steps * warning is displayed and no action is taken. * Otherwise parent {@link NavigationAction#actionPerformed(ActionEvent) * function} is called. */ @Override public void actionPerformed(ActionEvent ev) { if ((currentStep + step) < 0) { logger.warn("WARNING: attempt to navigate back past the first element"); return; } super.actionPerformed(ev); } } }