WizardDemo.java

/*
 * WizardDemo.java
 *
 * Copyright (C) 2004-05 Side of Software (SOS)
 * All rights reserved.
 *
 *    http://www.sideofsoftware.com
 *    info@sideofsoftware.com
 */

package examples;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import sos.wizard.*;

/**
 * Sample program that demonstrates the functionality and
 * flexibility of Side of Software's Wizard Library.<p>
 *
 * @author  Side of Software
 */
public class WizardDemo implements java.io.Serializable
{
  /**
   * Runs the wizard demo.<p>
   *
   * @param args ignored
   * @see #run
   */  
  public static void main(String[] args)
  {
    try
    {
      WizardDemo demo = new WizardDemo();
      demo.run();
    }
    catch( Throwable e )
    {
      e.printStackTrace();
    }
    finally
    {
      System.exit( 0 ); // neccessary to stop the Swing threads
    }
  }
  
  /**
   * Creates and returns a new <code>JCheckBox</code> with the specified
   * text, font, and selection state. When the check box changes
   * state, it updates the specified label with the appropriate
   * text.<p>
   *
   * @param text string to include in the check box
   * @param font font to use in the check box
   * @param isSelected initial selection state of the check box
   * @param confirmationLabel label to update when a change occurs
   * @param selectedText string to give the label when the check box is selected
   * @param unselectedText string to give the label when the check box is unselected
   * @return a new <code>JCheckbox</code>
   */  
  private static JCheckBox createCheckBox( String text, Font font,
    boolean isSelected, final JLabel confirmationLabel, 
    final String selectedText, final String unselectedText )
  {
    final JCheckBox checkBox = new JCheckBox( text, isSelected );
    checkBox.setFont( font );
    checkBox.setOpaque( false );
    checkBox.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        if( checkBox.isSelected() )
          confirmationLabel.setText( selectedText );
        else
          confirmationLabel.setText( unselectedText );
      }
    } );
    
    return checkBox;
  }
  
  /**
   * Creates and returns a new <code>JLabel</code> with the specified text
   * and font.<p>
   *
   * @param text text of the new label
   * @param font font of the new label
   * @return a new <code>JLabel</code>
   */  
  private static JLabel createLabel( String text, Font font )
  {
    JLabel label = new JLabel( text );
    if( font != null )
      label.setFont( font );
    return label;
  }
  
  /**
   * Creates and returns a <code>JPanel</code> with the specified layout manager.<p>
   *
   * @param layout layout manager of the new panel
   * @return a new <code>JPanel</code>
   */  
  private static JPanel createPanel( LayoutManager layout )
  {
    JPanel panel = new JPanel();
    panel.setOpaque( false );
    panel.setLayout( layout );
    return panel;
  }
  
  /**
   * Creates and returns a new <code>JRadioButton</code> with the specified
   * text, font, and selection state. When the radio button changes
   * becomes selected, it updates the specified label with the
   * specified text.<p>
   *
   * @param text text to include in the radio button
   * @param font font to use in the radio button
   * @param isSelected initial selection state of the radio button
   * @param confirmationLabel label to update when a change occurs
   * @param selectedText string to give the label when radio button
   *    becomes selected
   * @return a new <code>JRadioButton</code>
   */  
  private static JRadioButton createRadioButton( String text, 
    Font font, boolean isSelected,
    final JLabel confirmationLabel, final String selectedText )
  {
    final JRadioButton radioButton = new JRadioButton( text, isSelected );
    radioButton.setFont( font );
    radioButton.setOpaque( false );
    radioButton.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        if( radioButton.isSelected() )
          confirmationLabel.setText( selectedText );
      }
    } );
    return radioButton;
  }
  
  /**
   * Creates a task to execute when the sample wizard finishes.<p>
   *
   * @return a task to execute
   */  
  private static Task createSampleTask()
  {
    return new AbstractTask( "Finishing wizard..." ) {
      public void run()
      {
        for( int i = 1; i <= 100; i++ )
        {
          if( shouldAbort() )
            break;

          try
          {
            Thread.currentThread().sleep( 30 );
          }
          catch( InterruptedException ie )
          {
            Thread.currentThread().interrupt();
          }
          setProgress( i );
        }
      }

      // override <code>isInterruptible</code> to indicate that 
      // this task is interruptible
      public boolean isInterruptible()
      {
        return true;
      }
    };
  }
  
  /**
   * Displays a wizard that allows the user to create a sample wizard
   * and that demonstrates the use and customization of the wizard library.<p>
   */  
  public void run()
  {
    // Use this font for many of the components in the wizard pages
    Font font = new JTextField().getFont();
    
    // The wizard will contain five user input pages. This does
    // not include the final summary page.
    Page[] pages = new Page[5];

    // Create an "Overview" page.
    Object overviewMessage = "<html>This wizard will step you through " +
      "the creation of your own sample wizard. It demonstrates the " +
      "capabilities and flexibility of Side of Software's Wizard " +
      "Library. By default, the look and feel of these wizards " +
      "follow the guidelines set forth in Volume II of <i>Java<sup>" +
      "<font size=-2>TM</font></sup> Look and Feel Design Guidelines" +
      "</i>.<p><p>The programmer must supply the wizard's content. Any " +
      "component may be placed in a wizard page. This page consists of " +
      "a single <code>JTextPane</code>, displaying HTML text.</html>";
    pages[0] = new OptionPage( "Overview", overviewMessage );

    // Create a "Confirmation" page. This page contains a summary of
    // the options chosen in preceding pages. Create it before
    // the preceding pages, so that the preceding pages
    // can reference text fields in this page.
    //
    // This pages consists of text followed by a 6-row - by - 2-column
    // grid.
    //
    JPanel choicesPanel = createPanel( new GridBagLayout() );
    GridBagConstraints c = new GridBagConstraints();
    c.anchor = GridBagConstraints.FIRST_LINE_START;
    c.insets = new Insets( 1, 6, 1, 6 );
    
    c.gridx = 0; c.gridy = 0;
    choicesPanel.add( createLabel( "Title:", null ), c );
    
    final JLabel confirmationTitleField = createLabel( "My First Wizard", font );
    c.gridx = 1; c.gridy = 0;
    choicesPanel.add( confirmationTitleField, c );
    
    c.gridx = 0; c.gridy = 1;
    choicesPanel.add( createLabel( "Side Panel:", null ), c );
    
    final JLabel confirmationSidePanelField = createLabel( "List of Steps", font );
    c.gridx = 1; c.gridy = 1;
    choicesPanel.add( confirmationSidePanelField, c );
    
    c.gridx = 0;  c.gridy = 2;
    choicesPanel.add( createLabel( "Help Button:", null ), c );
    
    final JLabel confirmationHelpButtonField = createLabel( "No", font );
    c.gridx = 1; c.gridy = 2;
    choicesPanel.add( confirmationHelpButtonField, c );
    
    c.gridx = 0; c.gridy = 3;
    choicesPanel.add( createLabel( "Last Button:", null ), c );
    
    final JLabel confirmationLastButtonField = createLabel( "No", font );
    c.gridx = 1; c.gridy = 3;
    choicesPanel.add( confirmationLastButtonField, c );
    
    c.gridx = 0; c.gridy = 4;
    choicesPanel.add( createLabel( "Progress Page:", null ), c );
    
    final JLabel confirmationProgressPageField = createLabel( "Yes", font );
    c.gridx = 1; c.gridy = 4;
    choicesPanel.add( confirmationProgressPageField, c );
    
    c.gridx = 0; c.gridy = 5;
    choicesPanel.add( createLabel( "Summary Page:", null ), c );
    
    final JLabel confirmationSummaryPageField = createLabel( "Yes", font );
    c.gridx = 1; c.gridy = 5;
    choicesPanel.add( confirmationSummaryPageField, c );
    
    JPanel panel = createPanel( new BorderLayout() );
    panel.setBorder( BorderFactory.createEmptyBorder( 6, 24, 0, 0 ));
    panel.add( choicesPanel, BorderLayout.BEFORE_LINE_BEGINS );

    Object confirmationMessage = new Object[] {
      "<html>Your current choices are listed below. To create " +
      "your sample wizard, press <b>Finish</b>. To modify any of " +
      "your choices, press <b>Back</b>.<p><p></html>",
      panel
    };
    pages[4] = new OptionPage( "Confirmation", confirmationMessage );
    
    // Create a page that prompts the user to enter a title.
    final JTextField titleTextField = new JTextField( "My First Wizard" ) {
      protected void processFocusEvent( FocusEvent event )
      {
        super.processFocusEvent( event );
        selectAll();
      }
    };
    
    Object[] titleMessage = new Object[] {
      "<html>Provide a title for your wizard.<p><html>",
      titleTextField,
      "<html><p><p>Each page can specify when it is incomplete " +
      "or invalid. This page is not complete if the above text field " +
      "is empty. Note that the <b>Next</b> and <b>Last</b> buttons " +
      "become disabled if the page is incomplete. This page is invalid " +
      "if the supplied text contains a non-alphanumeric character. Try " +
      "typing \"Wizard?\" and clicking <b>Next</b>.</html>"
    };
    
    final OptionPage titlePage = new OptionPage( "Wizard Title", titleMessage ) {
      public void validate() throws InvalidPageException
      {
        String title = titleTextField.getText();
        for( int i = 0; i < title.length(); i++ )
        {
          char ch = title.charAt( i );
          if( !Character.isLetterOrDigit( ch ) && !Character.isWhitespace( ch ))
          {
            titleTextField.requestFocus();
            throw new InvalidPageException( "The title may only contain letters and digits." );
          }
        }
      }
    };
    
    titleTextField.getDocument().addDocumentListener( new DocumentListener() {
      public void insertUpdate( DocumentEvent e )
      {
        update();
      }

      public void changedUpdate( DocumentEvent e )
      {
        update();
      }

      public void removeUpdate( DocumentEvent e )
      {
        update();
      }

      private void update()
      {
        String title = titleTextField.getText();
        confirmationTitleField.setText( title );
        
        if( title.length() == 0 )
          titlePage.setComplete( false );
        else
          titlePage.setComplete( true );
      }
    } );
    pages[1] = titlePage;

    // Create a page that allows the user to select the
    // type of side panel. This page uses a group of radio
    // buttons, and each radio button updates the
    // corresponding confirmation text when it becomes selected.
    ButtonGroup sidePanelButtonGroup = new ButtonGroup();
    JRadioButton listOfStepsRadioButton = createRadioButton( "List of Steps", 
      font, true, confirmationSidePanelField, "List of Steps" );
    JRadioButton graphicRadioButton = createRadioButton( "Picture",
      font, false, confirmationSidePanelField, "Picture" );
    JRadioButton noneRadioButton = createRadioButton( "Nothing",
      font, false, confirmationSidePanelField, "Nothing" );
    sidePanelButtonGroup.add( listOfStepsRadioButton );
    sidePanelButtonGroup.add( graphicRadioButton );
    sidePanelButtonGroup.add( noneRadioButton );
    
    JPanel sidePanelButtons = createPanel( null );
    sidePanelButtons.setLayout( new BoxLayout( sidePanelButtons, BoxLayout.PAGE_AXIS ));
    sidePanelButtons.add( listOfStepsRadioButton );
    sidePanelButtons.add( graphicRadioButton );
    sidePanelButtons.add( noneRadioButton );
    sidePanelButtons.setBorder( BorderFactory.createEmptyBorder( 0, 12, 0, 0 ));
    
    JPanel buttonPanel = createPanel( new BorderLayout() );
    buttonPanel.add( sidePanelButtons, BorderLayout.BEFORE_LINE_BEGINS );
    
    Object sidePanelMessage = new Object[] {
      "<html>The library allows you to place any component in a " +
      "wizard's left side. The recommended practice is to display a " +
      "list of the steps, as is used here. At times, it is desirable " +
      "to show a picture or nothing at all.<p><p>What type of side " +
      "panel do you want your sample wizard to have?<p></html>",
      buttonPanel
    };
    pages[2] = new OptionPage( "Wizard Side Panel", sidePanelMessage );

    // Create a page that allows the user to select some
    // options. This page uses check boxes, and each check box updates the
    // corresponding confirmation text when its state changes.
    JCheckBox helpButtonCheckBox = createCheckBox( "Show a Help button",
      font, false, confirmationHelpButtonField, "Yes", "No" );
    JCheckBox lastButtonCheckBox = createCheckBox( "Show a Last button",
      font, false, confirmationLastButtonField, "Yes", "No" );
    JCheckBox progressPageCheckBox = createCheckBox( "Show a progress page " +
      "while task is finishing",
      font, true, confirmationProgressPageField, "Yes", "No" );
    JCheckBox summaryPageCheckBox = createCheckBox( "Show a summary page " +
      "after task finished",
      font, true, confirmationSummaryPageField, "Yes", "No" );
    
    JPanel optionCheckBoxes = createPanel( null );
    optionCheckBoxes.setLayout( new BoxLayout( optionCheckBoxes, BoxLayout.PAGE_AXIS ));
    optionCheckBoxes.add( helpButtonCheckBox );
    optionCheckBoxes.add( lastButtonCheckBox );
    optionCheckBoxes.add( progressPageCheckBox );
    optionCheckBoxes.add( summaryPageCheckBox );
    optionCheckBoxes.setBorder( BorderFactory.createEmptyBorder( 0, 12, 0, 0 ));
    
    JPanel checkBoxPanel = createPanel( new BorderLayout() );
    checkBoxPanel.add( optionCheckBoxes, BorderLayout.BEFORE_LINE_BEGINS );
    
    Object optionsMessage = new Object[] {
      "<html>The library provides many opportunities to customize a wizard, " +
      "such as changing its font, color, or preferred size. A few additional " +
      "options are listed below.<p><p>Check the options you would like your " +
      "wizard to have.<p><html>",
      checkBoxPanel
    };
    pages[3] = new OptionPage( "Wizard Options", optionsMessage );
    
    // Create a summary page
    Object summaryMessage = new Object[] {
      "<html>You have successfully created your sample wizard. It " +
      "will launch after you press <b>Close</b>.</html>"
    };
    Page summaryPage = new OptionPage( "Summary", summaryMessage );
    
    // Create the wizard model. It consists of the five user input
    // pages and a summary page.
    DefaultWizardModel wizardModel = new DefaultWizardModel( pages, null, summaryPage );
    wizardModel.setSupportsLast( true );
    wizardModel.setProgressPageIsUsed( false );
    
    JWizard wizard = new JWizard( wizardModel );
    wizard.setFont( font );
    wizard.setDefaultAccessory( JWizard.DEFAULT_ACCESSORY_STEPS );
    
    // Show the dialog
    int result = wizard.showDialog( null, "Side of Software Wizard Library Demo" );
    if( result != WizardModel.WIZARD_FINISHED )
      return;

    // The user finished the wizard, so create the sample wizard.
    // This wizard will have some dynamically generated preparatory pages and
    // possibly a progress and summary page.
    final DefaultWizardModel sampleWizardModel = new DefaultWizardModel();

    // Create Page 1
    final JRadioButton option1RadioButton = new JRadioButton( "Show pages 2-4", true );
    option1RadioButton.setFont( font );
    option1RadioButton.setOpaque( false );
    JRadioButton option2RadioButton = new JRadioButton( "Show pages 2a, 4" );
    option2RadioButton.setFont( font );
    option2RadioButton.setOpaque( false );
    
    ButtonGroup sampleButtonGroup = new ButtonGroup();
    sampleButtonGroup.add( option1RadioButton );
    sampleButtonGroup.add( option2RadioButton );
    
    JPanel optionsPanel = createPanel( null );
    optionsPanel.setLayout( new BoxLayout( optionsPanel, BoxLayout.Y_AXIS ));
    optionsPanel.add( option1RadioButton );
    optionsPanel.add( option2RadioButton );
    
    JPanel sampleButtonPanel = createPanel( new BorderLayout() );
    sampleButtonPanel.add( optionsPanel, BorderLayout.BEFORE_LINE_BEGINS );
    
    Object selectionMessage = new Object[] {
      "<html>A wizard's pages can change dynamically. In this wizard, " +
      "the subsequent pages depend on your selection here.</html>",
      sampleButtonPanel
    };
    Page selectionPage = new OptionPage( "Selection Page", selectionMessage );

    // The contents of page 4 depend on the user's choices.
    boolean showProgressPage = progressPageCheckBox.isSelected();
    boolean showSummaryPage = summaryPageCheckBox.isSelected();
    
    Object page4Message;
    if( showProgressPage && showSummaryPage )
      page4Message = "<html>After pressing <b>Finish</b>, a progress " +
        "page will show the progress of a fake task. If the task " +
        "successfully finishes, you will be shown a final summary page, " +
        "allowing you to close the dialog.</html>";
    else if( showProgressPage && !showSummaryPage )
      page4Message = "<html>After pressing <b>Finish</b>, a progress " +
        "page will show the progress of a fake task. If the task " +
        "successfully finishes, you will be allowed to close the " +
        "dialog.</html>";
    else if( !showProgressPage && showSummaryPage )
      page4Message = "<html>After pressing <b>Finish</b>, a fake task " +
        "will execute. Lengthy tasks typically show a progress page " +
        "that informs the user of the task's progress. After the task " +
        "finishes, you will be shown a final summary page, allowing you " +
        "to close the dialog.</html>";
    else
      page4Message = "<html>Thank you for running the Wizard Demo. If you " +
        "have any questions or comments please email Side of Software.<p><p>" +
        "The source code for this demonstration is available on the " +
        "library's website.</html>";
    
    final Page page4 = new OptionPage( "Page 4", page4Message );
    
    DefaultWizardModel.Transition selectionPageTransition = new DefaultWizardModel.TransitionAdapter() {
      public void stepNext()
      {
        // add subsequent pages, depending on the radio butto selection
        if( option1RadioButton.isSelected() )
        {
          Object page2Message = "<html>This library separates the model " +
            "from the view to achieve greater flexibility. The wizard model " +
            "is viewed through a <code>JWizard</code> component, while a " +
            "page is viewed through a page editor.</html>";
          Page page2 = new OptionPage( "Page 2", page2Message );
          
          Object page3Message = "<html>The <code>JWizard</code> class is a " +
            "user-interface component that follows the Swing conventions. " +
            "For example, it<ul><li>Delegates its rendering to a look-and-" +
            "feel delegate</li><li>Implements accessibility</li><li>Sends " +
            "property change events when its state changes</li><li>Sends " +
            "action events when an action occurs</li></ul></html>";
          Page page3 = new OptionPage( "Page 3", page3Message );
          
          sampleWizardModel.push( page2 );
          sampleWizardModel.addLast( page3 );
          sampleWizardModel.addLast( page4 );
        }
        else
        {
          Object page2aMessage = "<html>It is possible to hide the control " +
            "buttons and operate the wizard programmatically. As a result, " +
            "you can implement an automatic slide show.</html>";
          Page page2a = new OptionPage( "Page 2a", page2aMessage );
          
          sampleWizardModel.push( page2a );
          sampleWizardModel.addLast( page4 );
        }
      }

      // remove the pages added in stepNext
      public void stepBack()
      {
        int numDynamicPages = option1RadioButton.isSelected()? 3 : 2;
        for( int i = 0; i < numDynamicPages; i++ )
          sampleWizardModel.removeLast();
      }
    };
    
    sampleWizardModel.addLast( selectionPage, selectionPageTransition );
    sampleWizardModel.setLastPage( page4 );
    
    // Create a finish task that pretends to do something.
    // It updates its progress along the way. If it is interrupted
    // it sets its interrupted flag and returns.
    Task sampleFinishTask = createSampleTask();
    
    // Create a summary page if the user checked the box
    // to include a summary page.
    Page sampleSummaryPage = null;
    if( showSummaryPage )
    {
      Object sampleSummaryMessage = "<html>Thank you for running the Wizard " +
        "Demo. If you have any questions or comments please email Side " +
        "of Software.<p><p>The source code for this demonstration is " +
        "available on the library's website.</html>";
      sampleSummaryPage = new OptionPage( "Summary", sampleSummaryMessage );
    }

    sampleWizardModel.setFinishTask( sampleFinishTask );
    sampleWizardModel.setSummaryPage( sampleSummaryPage );
    
    final JWizard sampleWizard = new JWizard( sampleWizardModel );

    // Let's change the editor that renders the progress page.
    // Have it bring up a dialog asking the user to confirm
    // the interruption of the wizard task.
    PageEditor customProgressPageEditor = new ProgressPageEditor() {
      public boolean confirmStop()
      {
        String message = "Are you sure you want to stop the execution?";
        int response = JOptionPane.showConfirmDialog( sampleWizard, message, 
          "Confirm Stop", JOptionPane.YES_NO_OPTION );
        
        if( response == JOptionPane.YES_OPTION )
          return true;
        return false;
      }
    };
    
    sampleWizard.setDefaultPageEditor( ProgressPage.class, customProgressPageEditor );
    
    // Show a Help button if the user checked this option.
    // The Help button will simply display a message.
    boolean showHelpButton = helpButtonCheckBox.isSelected();
    sampleWizard.setHelpButtonIsShown( showHelpButton );

    sampleWizard.addActionListener( new ActionListener() {
      public void actionPerformed( ActionEvent event )
      {
        String command = event.getActionCommand();
        
        if( JWizard.HELP_ACTION.equals( command ))
          JOptionPane.showMessageDialog( sampleWizard,
            "The programmer is responsible for responding to the " +
            "Help button." );
      }
    } );
    
    // Show a Last button if the user checked this option.
    boolean showLastButton = lastButtonCheckBox.isSelected();
    sampleWizardModel.setSupportsLast( showLastButton );
    
    // Show a progress page if the user checked this option.
    sampleWizardModel.setProgressPageIsUsed( showProgressPage );

    // Set the appropriate side panel.
    if( listOfStepsRadioButton.isSelected() )
    {
      sampleWizard.setDefaultAccessory( JWizard.DEFAULT_ACCESSORY_STEPS );
    }
    else if( graphicRadioButton.isSelected() )
    {
      // Load and install the graphic
      Icon icon = new ImageIcon( WizardDemo.class.getClassLoader().getResource( 
        "examples/WizardGraphic.jpg" ));
      JLabel sampleGraphics = new JLabel( icon );
      sampleWizard.setAccessory( sampleGraphics );
    }

    // Show the dialog with the appropriate title
    String sampleTitle = titleTextField.getText();
    sampleWizard.showDialog( null, sampleTitle );
  }
}