JMU
The Command Pattern for Undo/Redo
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Overview
The Process
An Example: A Football Stadium Scoreboard
An Example (cont.)
javaexamples/undoredo/YardMarker.java
import java.awt.*;
import javax.swing.*;

/**
 * A simple yard marker for a football scoreboard
 *
 * @version 1.0
 * @author  Prof. David Benrstein, James Madison University
 */
public class YardMarker extends JPanel
{
    private int          down,  toGo,  yard;
    private JLabel       downL, sideL, toGoL, yardL;
    private String       side;
    

    /**
     * Default Constructor
     */
    public YardMarker()
    {
        super();

        downL = new JLabel();
        sideL = new JLabel();
        toGoL = new JLabel();
        yardL = new JLabel();

        performLayout();
    }

    
    /**
     * Returns the current down
     */
    public int getDown()
    {
        return down;
    }

    /**
     * Returns the current side of the field the
     * ball is on
     */
    public String getSide()
    {
        return side;
    }

    /**
     * Returns the current yards to go for a first down
     */
    public int getToGo()
    {
        return toGo;
    }

    /**
     * Returns the current position of the ball
     */
    public int getYard()
    {
        return yard;
    }


    /**
     * Layout this component
     */
    private void performLayout()
    {
        setLayout(new GridLayout(1,7));

        add(new JLabel("Down: "));
        downL.setForeground(Color.BLUE);
        add(downL);

        add(new JLabel("To Go: "));
        toGoL.setForeground(Color.BLUE);
        add(toGoL);

        add(new JLabel("On: "));
        sideL.setForeground(Color.BLUE);
        add(sideL);
        yardL.setForeground(Color.BLUE);
        add(yardL);
    }


    /**
     * Set the current values
     */
    public void setValues(int down, int toGo, String side, int yard)
    {
        this.down = down;
        this.toGo = toGo;
        this.side = side;
        this.yard = yard;

        downL.setText(""+down);
        toGoL.setText(""+toGo);
        sideL.setText(side);
        yardL.setText(""+yard);
    }
}
        
An Example (cont.)
javaexamples/undoredo/YardMarkerEdit.java
import javax.swing.undo.*;

/**
 * An (undoable) change to a YardMarker
 *
 * @version 1.0
 * @author  Prof. David Benrstein, James Madison University
 */
public class YardMarkerEdit extends AbstractUndoableEdit
{
    private int          down,    toGo,    yard;
    private int          oldDown, oldToGo, oldYard;
    private String       side;
    private String       oldSide;
    private YardMarker   ym;

    /**
     * Explicit Value Constructor
     */
    public YardMarkerEdit(YardMarker ym,
                          int down, int toGo, String side, int yard)
    {
        super();

        this.ym   = ym;

        this.down = down;
        this.toGo = toGo;
        this.side = side;
        this.yard = yard;

        oldDown   = ym.getDown();
        oldToGo   = ym.getToGo();
        oldSide   = ym.getSide();
        oldYard   = ym.getYard();

        ym.setValues(down, toGo, side, yard);
    }


    /**
     * Get a description of this edit
     */
    public String getPresentationName()
    {
        return ""+down+" and "+toGo+" on "+side+" "+yard;
    }

    /**
     * Redo this edit
     */
    public void redo() throws CannotUndoException
    {
        super.redo(); // Sets hasBeenDone to true
        
        ym.setValues(down, toGo, side, yard);
    }



    /**
     * Redo this edit
     */
    public void undo() throws CannotUndoException
    {
        super.undo(); // Sets hasBeenDone to false

        ym.setValues(oldDown, oldToGo, oldSide, oldYard);
    }
}
        
An Example (cont.)
Using Actions for Undo/Redo
javaexamples/undoredo/UndoRedoAction.java
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.undo.*;

/**
 * A fairly generic undo/redo action that can be added to
 * menus and toolbars.
 *
 * It changes its state when an edit occurs and when
 * an undo/redo occurs.
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class UndoRedoAction extends    AbstractAction
                            implements UndoableEditListener
{
    private UndoableEdit      lastEdit;


    /**
     * Default Constructor
     */
    public UndoRedoAction()
    {
        putValue(Action.NAME, "Undo/Redo");
        setEnabled(false);
    }

    /**
     * Perform the action
     */
    public void actionPerformed(ActionEvent evt)
    {
        if (lastEdit != null) 
        {
            if (lastEdit.canUndo()) 
            {
                lastEdit.undo();
                putValue(Action.NAME, 
                         lastEdit.getRedoPresentationName());
            } 
            else if (lastEdit.canRedo()) 
            {
                lastEdit.redo();
                putValue(Action.NAME, 
                         lastEdit.getUndoPresentationName());
            } 
            else 
            {
                putValue(Action.NAME, "Undo/Redo");
                setEnabled(false);
            }
        }
    }


    /**
     * Handle UndoableEidtHappened events
     * (Required by UndoableEditListener)
     */
    public void undoableEditHappened(UndoableEditEvent evt)
    {
        lastEdit = evt.getEdit();

        if (lastEdit.canUndo()) 
        {
            putValue(Action.NAME, 
                     lastEdit.getUndoPresentationName());

            setEnabled(true);
        }
    }
}
        
An Example (cont.)
A Driver
javaexamples/undoredo/Driver.java
import java.awt.*;
import javax.swing.*;
import javax.swing.undo.*;

/**
 * An example that demonstrates undo/redo.
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class Driver implements Runnable
{
    private UndoableEditSupport support;
    private YardMarker          ym;
    
    /**
     * The entry point of the application.
     *
     * @param args  The command line arguments (ignored)
     */
    public static void main(String[] args) throws Exception
    {
        Driver driver = new Driver();
        SwingUtilities.invokeAndWait(driver);


        // Make a change
        YardMarkerEdit edit = new YardMarkerEdit(driver.ym, 1, 10, "Own", 35);
        driver.support.postEdit(edit);


        // Sleep to give time to see the scoreboard
        // before the change
        try 
        {
            Thread.sleep(5000);
        } 
        catch (InterruptedException ie) 
        {

        }

        // Make a change
        edit = new YardMarkerEdit(driver.ym, 2, 8, "Own", 37);
        driver.support.postEdit(edit);
    }
    
    /**
     * The code to run in the event dispatch thread.
     */
    public void run()
    {
        // Layout the frame
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(400,400);
        Container contentPane = f.getContentPane();

        contentPane.setLayout(new BorderLayout());
        contentPane.add(new JLabel("Bernstein Stadium"), BorderLayout.NORTH);

        ym = new YardMarker();
        contentPane.add(ym, BorderLayout.SOUTH);

        contentPane.add(new JLabel("Your ad here!"), BorderLayout.CENTER);

        JMenuBar menuBar  = new JMenuBar();
        JMenu editMenu = new JMenu("Edit");
        UndoRedoAction urAction = new UndoRedoAction();
        editMenu.add(urAction);
        menuBar.add(editMenu);

        f.setJMenuBar(menuBar);

        // Add undo/redo support
        support = new UndoableEditSupport();
        support.addUndoableEditListener(urAction);

        f.setVisible(true);
    }
}