Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 11. (The basics) Text Components and Undo. Easy for reading, Click here!

Custom Search
Swing Chapter 11. (The basics) Text Components and Undo. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 2/2 



Previous Page Previous Page (1/2)
Subpages: 1. Text Components overview 
2.
Undo/Redo

11.2  Undo/Redo

Undo/redo options are commonplace in applications such as paint programs and word processors, and have been used extensively throughout the writing of this book! It is interesting that this functionality is provided as part of the Swing library, as it is completely Swing independent. In this section we will briefly introduce the javax.swing.undo constituents, and in the process of doing so, we will present an example showing how undo/redo functionality can be integrated into any app. The text components come with built-in undo/redo functionality, and we will also discuss how to take advantage of this.

11.2.1  The UndoableEdit interface

abstract interface javax.swing.undo.UndoableEdit

This interface acts as a template definition for anything that can be undone/redone. Implementations should normally be very lightweight, as undo/redo operations commonly occur quickly in succession.

UndoableEdits are designed to have three states: undoable, redoable, and dead. When an UndoableEdit is in the undoable state, calling undo() will perform an undo operation. Similarly, when an UndoableEdit is in the redoable state, calling redo() will perform a redo operation. Methods canUndo() and canRedo() provide ways to check whether or not an UndoableEdit is in the undoable or redoable state respectively. We can use the die() method to explicitly send an UndoableEdit to the dead state. When in the dead state, an UndoableEdit cannot be undone or redone, and any attempt to do so will generate an exception.

UndoableEdits maintains three String properties (normally used as menu item text): presentationName, undoPresentationName, and redoPresentationName. Methods addEdit() and replaceEdit() are meant to be used to merge two edits and replace an edit, respectively. UndoableEdit also defines the concept of significant and insignificant edits. An insignificant edit is one that UndoManager (see 11.2.6) ignores when an undo/redo request is made. CompoundEdit (see 11.2.3), however, will pay attention to both significant and insignificant edits. The significant property of an UndoableEdit can be queried with isSignificant().

11.2.2  AbstractUndoableEdit

class javax.swing.undo.AbstractUndoableEdit

AbstractUndoableEdit implements UndoableEdit and defines two boolean properties representing the three UndoableEdit states. The alive property is true when an edit is not dead. The done property is true when an undo can be performed, and false when a redo can be performed.

The default behavior provided by this class is good enough for most sub-classes. All AbstractUndoableEdits are significant, and the undoPresentationName and redoPresentationName properties are formed by simply appending "Undo" and "Redo" to presentationName.

The following example demonstrates a basic square painting program with undo/redo functionality. This app simply draws a square outline wherever a mouse press occurs. A Vector of Points is maintained which represent the upper left-hand corner of each square that is drawn on the canvas. We create an AbstractUndoableEdit sub-class to maintain a reference to a Point, with undo() and redo() methods that remove and add that Point from the Vector, respectively. Figure 11.7 illustrates.

Figure 11.7 A square painting app with one level of undo/redo.

The Code: UndoRedoPaintApp.java

see \Chapter11\3

import java.util.*;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.undo.*;

public class UndoRedoPaintApp extends JFrame

{

  protected Vector m_points = new Vector();

  protected PaintCanvas m_canvas = new PaintCanvas(m_points);

  protected UndoablePaintSquare m_edit;

  protected JButton m_undoButton = new JButton("Undo");

  protected JButton m_redoButton = new JButton("Redo");

  public UndoRedoPaintApp() {

    super("Undo/Redo Demo");

    m_undoButton.setEnabled(false);

    m_redoButton.setEnabled(false);

    JPanel buttonPanel = new JPanel(new GridLayout());

    buttonPanel.add(m_undoButton);

    buttonPanel.add(m_redoButton);

    getContentPane().add(buttonPanel, BorderLayout.NORTH);

    getContentPane().add(m_canvas, BorderLayout.CENTER);

    m_canvas.addMouseListener(new MouseAdapter() {

      public void mousePressed(MouseEvent e) {

        Point point = new Point(e.getX(), e.getY());

        m_points.addElement(point);

        m_edit = new UndoablePaintSquare(point, m_points);

        m_undoButton.setText(m_edit.getUndoPresentationName());

        m_redoButton.setText(m_edit.getRedoPresentationName());

        m_undoButton.setEnabled(m_edit.canUndo());

        m_redoButton.setEnabled(m_edit.canRedo());

        m_canvas.repaint();

      }

    });

    m_undoButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        try { m_edit.undo(); }

        catch (CannotRedoException cre) { cre.printStackTrace(); }

        m_canvas.repaint();

        m_undoButton.setEnabled(m_edit.canUndo());

        m_redoButton.setEnabled(m_edit.canRedo());

      }

    });

    m_redoButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        try { m_edit.redo(); }

        catch (CannotRedoException cre) { cre.printStackTrace(); }

        m_canvas.repaint();

        m_undoButton.setEnabled(m_edit.canUndo());

        m_redoButton.setEnabled(m_edit.canRedo());

      }

    });

    setSize(400,300);

    setVisible(true);

  }

  public static void main(String argv[]) {

    new UndoRedoPaintApp();

  }

}

class PaintCanvas extends JPanel

{

  Vector m_points;

  protected int width = 50;

  protected int height = 50;

  public PaintCanvas(Vector vect) {

    super();

    m_points = vect;

    setOpaque(true);

    setBackground(Color.white);

  }

  public void paintComponent(Graphics g) {

    super.paintComponent(g);

    g.setColor(Color.black);

    Enumeration enum = m_points.elements();

    while(enum.hasMoreElements()) {

      Point point = (Point) enum.nextElement();

      g.drawRect(point.x, point.y, width, height);

    }

  }

}

class UndoablePaintSquare extends AbstractUndoableEdit

{

  protected Vector m_points;

  protected Point m_point;

  public UndoablePaintSquare(Point point, Vector vect) {

    m_points = vect;

    m_point = point;

  }  

  public String getPresentationName() {

    return "Square Addition";

  }

  public void undo() {

    super.undo();

    m_points.remove(m_point);

  }

  public void redo() {

    super.redo();

    m_points.add(m_point);

  }

}

One thing to note about this example is that it is extremely limited. Because we are not maintaining an ordered collection of UndoableEdits we can only perform one undo/redo. CompoundEdit and UndoManager directly address this limitation.

11.2.3  CompoundEdit

class javax.swing.undo.CompoundEdit

This class extends AbstractUndoableEdit to support an ordered collection of UndoableEdits, maintained as a protected Vector called edits. UndoableEdits can be added to this vector with addEdit(), but they cannot so easily be removed (for this, a sub-class would be necessary).

Even though CompoundEdit is more powerful than AbstractUndoableEdit, it is far from the ideal solution. Edits cannot be undone until all edits have been added. Once all UndoableEdits are added we are expected to call end(), at which point CompoundEdit will no longer accept any additional edits. Once end() is called,  a call to undo() will undo all edits, whether significant or not. A redo() will then redo them all, and we can continue to cycle back and forth like this as long as the CompoundEdit itself remains alive. For this reason, CompoundEdit is useful for a pre-defined or intentionally limited set of states.

CompoundEdit introduces an additional state property called inProgress, which is true if end() has not been called. We can retrieve the value of inProgess with isInProgress(). The significant property, inherited from UndoableEdit, will be true if one or more of the contained UndoableEdits is significant, and false otherwise.

11.2.4  UndoableEditEvent

class javax.swing.event.UndoableEditEvent

This event encapsulates a source Object and an UndoableEdit, and is meant to be passed to implementations of the UndoableEditListener interface.

11.2.5  The UndoableEditListener interface

class javax.swing.event.UndoableEditListener

This listener is intended for use by any class wishing to listen for operations that can be undone/redone. When such an operation occurs an UndoableEditEvent can be sent to an UndoableEditListener for processing. UndoManager implements this interface so we can simply add it to any class defining undoable/redoable operations. It is important to emphasize that UndoableEditEvents are not fired when an undo or redo actually occurs, but when an operation occurs which has an UndoableEdit associated with it. This interface declares one method, undoableEditHappened(), which accepts an UndoableEditEvent. We are generally responsible for passing UndoableEditEvents to this method. The example in the next section demonstrates.

11.2.6  UndoManager

class javax.swing.undo.UndoManager

UndoManager extends CompoundEdit and relieves us of the limitation where undos and redos cannot be performed until edit() is called. It also relieves us of the limitation where all edits are undone or redone at once. Another major difference from CompoundEdit is that UndoManager simply skips over all insignificant edits when undo() or redo() is called, effectively not paying them any attention. Interestingly, UndoManager allows us to add edits while inProgress is true, but if end() is ever called, UndoManager immediately starts acting like a CompoundEdit.

UndoManager introduces a new state called undoOrRedo which, when true, signifies that calling undo() or redo() is valid. This property can only be true if there is more than one edit stored, and only if there is at least one edit in the undoable state and one in the redoable state. The value of this property can be retrieved with canUndoOrRedo(), and the getUndoOrRedoPresentationName() method will return an appropriate name for use in a menu item or elsewhere.

We can retrieve the next significant UndoableEdit that is scheduled to be undone or redone with editToBeUndone() or editToBeRedone() respectively. We can kill all stored edits with discardAllEdits(). Methods redoTo() and undoTo() can be used to programmatically invoke undo() or redo() on all edits from the current edit to the edit provided as parameter.

We can set the maximum number of edits that can be stored with setLimit(). The value of the limit property (100 by default) can be retrieved with getLimit(), and if it is set to a value smaller than the current number of edits, they edits will be reduced using the protected trimForLimit() method. Based on the index of the current edit within the edits vector, this method will attempt to kill the most balanced number of edits in undoable and redoable states as it can in order to achieve the given limit. The further an edit is (based on its vector index in the edits vector) the more of a candidate it is for death when a trim occurs, as edits sentenced to death are taken from the extreme ends of the edits vector.

It is very important to note that when an edit is added to the edits vector, all edits in the redoable state (i.e. those appearing after the index of the current edit) do not simply get moved up one index. Rather, they are killed off. So, for example, suppose in a word processor application you enter some text, change the style of 10 different regions of that text, and then undo the most recent 5 style additions. Then a new style change is made. The first 5 style changes that were made remain in the undoable state, and the new edit is added, also in the undoable state. However, the 5 style changes that were undone (i.e. moved to the redoable state) are now completely lost.

Note: All public UndoManager methods are synchronized to enable thread-safety, and make UndoManager a good candidate for use as a central undo/redo manager for any number of functionalities.

The following code shows how we can modify our UndoRedoPaintApp example to allow multiple undos and redos using an UndoManager. Because UndoManager implements UndoableEditListener, we should normally add UndoableEdits to it using the undoableEditHappened() method rather than addEdit(). This is because undoableEditHappened() calls addEdit() for us, and at the same time allows us to keep track of the source of the operation. This enables UndoManager to act as a central location for all undo/redo edits in an app.

The Code: UndoRedoPaintApp.java

see \Chapter11\4

import java.util.*;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.undo.*;

import javax.swing.event.*;

public class UndoRedoPaintApp extends JFrame

{

  protected Vector m_points = new Vector();

  protected PaintCanvas m_canvas = new PaintCanvas(m_points);

  protected UndoManager m_undoManager = new UndoManager();

  protected JButton m_undoButton = new JButton("Undo");

  protected JButton m_redoButton = new JButton("Redo");

  public UndoRedoPaintApp() {

    super("Undo/Redo Demo");

    m_undoButton.setEnabled(false);

    m_redoButton.setEnabled(false);

    JPanel buttonPanel = new JPanel(new GridLayout());

    buttonPanel.add(m_undoButton);

    buttonPanel.add(m_redoButton);

    getContentPane().add(buttonPanel, BorderLayout.NORTH);

    getContentPane().add(m_canvas, BorderLayout.CENTER);

    m_canvas.addMouseListener(new MouseAdapter() {

      public void mousePressed(MouseEvent e) {

        Point point = new Point(e.getX(), e.getY());

        m_points.addElement(point);

        m_undoManager.undoableEditHappened(new UndoableEditEvent(m_canvas,

          new UndoablePaintSquare(point, m_points)));

        m_undoButton.setText(m_undoManager.getUndoPresentationName());

        m_redoButton.setText(m_undoManager.getRedoPresentationName());

        m_undoButton.setEnabled(m_undoManager.canUndo());

        m_redoButton.setEnabled(m_undoManager.canRedo());

        m_canvas.repaint();

      }

    });

    m_undoButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        try { m_undoManager.undo(); }

        catch (CannotRedoException cre) { cre.printStackTrace(); }

        m_canvas.repaint();

        m_undoButton.setEnabled(m_undoManager.canUndo());

        m_redoButton.setEnabled(m_undoManager.canRedo());

      }

    });

    m_redoButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        try { m_undoManager.redo(); }

        catch (CannotRedoException cre) { cre.printStackTrace(); }

        m_canvas.repaint();

        m_undoButton.setEnabled(m_undoManager.canUndo());

        m_redoButton.setEnabled(m_undoManager.canRedo());

      }

    });

    setSize(400,300);

    setVisible(true);

  }

  public static void main(String argv[]) {

    new UndoRedoPaintApp();

  }

}

// Classes PaintCanvas and UndoablePaintSquare unchanged

// from 11.2.2

Run this example and note that we can have up to 100 squares in the undoable or redoable state at any given time. Also note that when several squares are in the redoable state, adding a new square will kill them, and the redo button will become disabled (indicating that no redos can be performed).

11.2.7  The StateEditable interface

abstract interface javax.swing.undo.StateEditable

The StateEditable interface is intended for use by objects that wish to maintain specific before (pre) and after (post) states. This provides an alternative to managing undos and redos in UndoableEdits. Once a before and after state is defined we can use a StateEdit object to switch between the two states. Two methods must be implemented by StateEditable implementations. storeState() is intended to be used by an object to store its state as a set of key/value pairs in a given Hashtable. Normally this entails storing the name of an object and a copy of that object (unless a primitive is stored). restoreState() is intended to be used by an object to restore its state according to the key/vaue pairs stored in a given Hashtable.

11.2.8  StateEdit

class javax.swing.undo.StateEdit

StateEdit extends AbstractUndoableEdit, and is meant to store the before and after Hashtables of a StateEditable instance. When a StateEdit is instantiated it is passed a StateEditable object, and a protected Hashtable called preState is passed to that StateEditable's storeState() method. Similarly, when  end() is called on a StateEdit, a protected Hashtable called postState is passed to the corresponding StateEditable's storeState() method. After end() is called, undos and redos toggle the state of the StateEditable between postState and preState by passing the appropriate Hashtable to that StateEditable's restoreState() method.

11.2.9  UndoableEditSupport

class javax.swing.undo.UndoableEditSupport

This convenience class is used for managing UndoableEditListeners. We can add and remove an UndoableEditListener with addUndoableEditListener() and removeUndoableEditListener() respectively. UndoableEditSupport maintains an updateLevel property which basically specifies how many times the beginUpdate() method has been called. As long as this value is above 0, UndoableEdits added with the postEdit() method will be stored in a temporary CompoundEdit object without being fired. The endEdit() method decrements the updateLevel property. When updateLevel is 0, any calls to postEdit() will fire the edit passed in, or the CompoundEdit that has been accumulating edits up to that point.

Warning: The endUpdate() and beginUpdate() methods may call undoableEditHappened() in each UndoableEditListener, possibly resulting in deadlock if these methods are actually invoked from one of the listeners themselves.

11.2.10            CannotUndoException

class javax.swing.undo.CannotUndoException

This exception is thrown when undo() is invoked on an UndoableEdit that cannot be undone.

11.2.11            CannotRedoException

class javax.swing.undo.CannotRedoException

This exception is thrown when redo() is invoked on an UndoableEdit that cannot be redone.

11.2.12            Using built-in text component undo/redo functionality

All default text component Document models fire UndoableEdits. For PlainDocuments this involves keeping track of text insertions and removals, as well as any structural changes. For StyledDocuments, however, this involves keeping track of a much larger group of changes. Fortunately this work has been built into these document models for us. The following example shows how easy it is to add undo/redo support to text components. Figure 11.8 illustrates.

Figure 11.8 Undo/Redo functionality added to a JTextArea.

The Code: UndoRedoTextApp.java

see \Chapter11\5

import java.awt.*;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.undo.*;

import javax.swing.event.*;

public class UndoRedoTextApp extends JFrame

{

  protected JTextArea m_editor = new JTextArea();

  protected UndoManager m_undoManager = new UndoManager();

  protected JButton m_undoButton = new JButton("Undo");

  protected JButton m_redoButton = new JButton("Redo");

  public UndoRedoTextApp() {

    super("Undo/Redo Demo");

    m_undoButton.setEnabled(false);

    m_redoButton.setEnabled(false);

    JPanel buttonPanel = new JPanel(new GridLayout());

    buttonPanel.add(m_undoButton);

    buttonPanel.add(m_redoButton);

    JScrollPane scroller = new JScrollPane(m_editor);

    getContentPane().add(buttonPanel, BorderLayout.NORTH);

    getContentPane().add(scroller, BorderLayout.CENTER);

    m_editor.getDocument().addUndoableEditListener(

     new UndoableEditListener() {

      public void undoableEditHappened(UndoableEditEvent e) {

        m_undoManager.addEdit(e.getEdit());

        updateButtons();

      }

    });

    m_undoButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        try { m_undoManager.undo(); }

        catch (CannotRedoException cre) { cre.printStackTrace(); }

        updateButtons();

      }

    });

    m_redoButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        try { m_undoManager.redo(); }

        catch (CannotRedoException cre) { cre.printStackTrace(); }

        updateButtons();

      }

    });

    setSize(400,300);

    setVisible(true);

  }

  public void updateButtons() {

    m_undoButton.setText(m_undoManager.getUndoPresentationName());

    m_redoButton.setText(m_undoManager.getRedoPresentationName());

    m_undoButton.setEnabled(m_undoManager.canUndo());

    m_redoButton.setEnabled(m_undoManager.canRedo());

  }

  public static void main(String argv[]) {

    new UndoRedoTextApp();

  }

}



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy