This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

FYI: Patch: javax.swing.undo.UndoManager


Hi list,


I just commited this to merge from classpath. Someone should probably 
commit this to libjava-gui-branch too.


Michael


2004-02-14  Sascha Brawer  <brawer@dandelis.ch>

	* javax/swing/undo/UndoManager.java: Re-written from scratch.

Index: javax/swing/undo/UndoManager.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/javax/swing/undo/UndoManager.java,v
retrieving revision 1.2
diff -u -b -B -r1.2 UndoManager.java
--- javax/swing/undo/UndoManager.java	11 Jun 2003 13:20:41 -0000	1.2
+++ javax/swing/undo/UndoManager.java	14 Feb 2004 20:42:19 -0000
@@ -1,5 +1,5 @@
 /* AbstractTableModel.java --
-   Copyright (C) 2002 Free Software Foundation, Inc.
+   Copyright (C) 2002, 2004 Free Software Foundation, Inc.
 
 This file is part of GNU Classpath.
 
@@ -38,6 +38,7 @@
 
 package javax.swing.undo;
 
+import javax.swing.UIManager;
 import javax.swing.event.UndoableEditEvent;
 import javax.swing.event.UndoableEditListener;
 
@@ -41,220 +42,584 @@
 import javax.swing.event.UndoableEditEvent;
 import javax.swing.event.UndoableEditListener;
 
+
 /**
- * UndoManager
- * @author	Andrew Selkirk
- */
-public class UndoManager extends CompoundEdit implements UndoableEditListener {
+ * A manager for providing an application&#x2019;s undo/redo
+ * functionality.
+ *
+ * <p>Tyipcally, an application will create only one single instance
+ * of UndoManager. When the user performs an undoable action, for
+ * instance changing the color of an object from green to blue, the
+ * application registers an {@link UndoableEdit} object with the
+ * <code>UndoManager</code>. To implement the &#x201c;undo&#x201d; and
+ * &#x201c;redo&#x201d; menu commands, the application invokes the
+ * UndoManager&#x2019;s {@link #undo} and {@link #redo} methods.  The
+ * human-readable text of these menu commands is provided by {@link
+ * #getUndoPresentationName} and {@link #getRedoPresentationName},
+ * respectively. To determine whether the menu item should be
+ * selectable or greyed out, use {@link #canUndo} and {@link
+ * #canRedo}.
+ *
+ * <p>The UndoManager will only keep a specified number of editing
+ * actions, the <em>limit</em>. The value of this parameter can be
+ * retrieved by calling {@link #getLimit} and set with {@link
+ * #setLimit}.  If more UndoableEdits are added to the UndoManager,
+ * the oldest actions will be discarded.
+ *
+ * <p>Some applications do not provide separate menu commands for
+ * &#x201c;undo&#x201d; and &#x201c;redo.&#x201d; Instead, they
+ * have just a single command whose text switches between the two.
+ * Such applications would use an UndoManager with a <code>limit</code>
+ * of 1. The text of this combined menu item is available via
+ * {@link #getUndoOrRedoPresentationName}, and it is implemented
+ * by calling {@link #undoOrRedo}.
+ *
+ * <p><b>Thread Safety:</b> In constrast to the other classes of the
+ * <code>javax.swing.undo</code> package, the public methods of an
+ * <code>UndoManager</code> are safe to call from concurrent threads.
+ * The caller does not need to perform external synchronization, and
+ * {@link javax.swing.event.UndoableEvent} sources do not need to
+ * broadcast their events from inside the Swing worker thread.
+ *
+ * @author <a href="mailto:brawer@dandelis.ch";>Sascha Brawer</a>
+ */
+public class UndoManager
+  extends CompoundEdit
+  implements UndoableEditListener
+{
+  /**
+   * The unique ID for serializing instances of this class. Determined
+   * using the <code>serialver</code> tool of Sun JDK 1.4.1_01 on
+   * GNU/Linux.
+   */
+  static final long serialVersionUID = -2077529998244066750L;
 
-	//-------------------------------------------------------------
-	// Variables --------------------------------------------------
-	//-------------------------------------------------------------
 
 	/**
-	 * indexOfNextAdd
+   * An index into the inherited {@link #edits} Vector that indicates
+   * at which position newly added editing actions would get inserted.
+   *
+   * <p>Normally, the value of <code>indexOfNextAdd</code> equals
+   * the number of UndoableEdits stored by this UndoManager, i.e.
+   * <code>edits.size()</code>. For each call to {@link #undo},
+   * <code>indexOfNextAdd</code> is decremented by one. For each
+   * call to {@link #redo}, it is incremented again.
 	 */
 	int indexOfNextAdd;
 
-	/**
-	 * limit
-	 */
-	int limit;
-
-
-	//-------------------------------------------------------------
-	// Initialization ---------------------------------------------
-	//-------------------------------------------------------------
-
-	/**
-	 * Constructor UndoManager
-	 */
-	public UndoManager() {
-		// TODO
-	} // UndoManager()
-
-
-	//-------------------------------------------------------------
-	// Methods ----------------------------------------------------
-	//-------------------------------------------------------------
-
-	/**
-	 * toString
-	 * @returns String
-	 */
-	public String toString() {
-		return null; // TODO
-	} // toString()
-
-	/**
-	 * end
-	 */
-	public synchronized void end() {
-		// TODO
-	} // end()
-
-	/**
-	 * getLimit
-	 * @returns int
-	 */
-	public synchronized int getLimit() {
-		return 0; // TODO
-	} // getLimit()
-
-	/**
-	 * discardAllEdits
-	 */
-	public synchronized void discardAllEdits() {
-		// TODO
-	} // discardAllEdits()
 
 	/**
-	 * trimForLimit
+   * The maximum number of UndoableEdits stored by this UndoManager.
 	 */
-	protected void trimForLimit() {
-		// TODO
-	} // trimForLimit()
-
-	/**
-	 * trimEdits
-	 * @param value0 TODO
-	 * @param value1 TODO
-	 */
-	protected void trimEdits(int value0, int value1) {
-		// TODO
-	} // trimEdits()
-
-	/**
-	 * setLimit
-	 * @param value0 TODO
-	 */
-	public synchronized void setLimit(int value0) {
-		// TODO
-	} // setLimit()
-
-	/**
-	 * editToBeUndone
-	 * @returns UndoableEdit
-	 */
-	protected UndoableEdit editToBeUndone() {
-		return null; // TODO
-	} // editToBeUndone()
-
-	/**
-	 * editToBeRedone
-	 * @returns UndoableEdit
-	 */
-	protected UndoableEdit editToBeRedone() {
-		return null; // TODO
-	} // editToBeRedone()
-
-	/**
-	 * undoTo
-	 * @param value0 TODO
-	 * @exception CannotUndoException TODO
-	 */
-	protected void undoTo(UndoableEdit value0) throws CannotUndoException {
-		// TODO
-	} // undoTo()
-
-	/**
-	 * redoTo
-	 * @param value0 TODO
-	 * @exception CannotRedoException TODO
-	 */
-	protected void redoTo(UndoableEdit value0) throws CannotRedoException {
-		// TODO
-	} // redoTo()
-
-	/**
-	 * undoOrRedo
-	 * @exception CannotRedoException TODO
-	 * @exception CannotUndoException TODO
-	 */
-	public synchronized void undoOrRedo() throws CannotRedoException, CannotUndoException {
-		// TODO
-	} // undoOrRedo()
-
-	/**
-	 * canUndoOrRedo
-	 * @returns boolean
-	 */
-	public synchronized boolean canUndoOrRedo() {
-		return false; // TODO
-	} // canUndoOrRedo()
-
-	/**
-	 * undo
-	 * @exception CannotUndoException TODO
-	 */
-	public synchronized void undo() throws CannotUndoException {
-		// TODO
-	} // undo()
-
-	/**
-	 * canUndo
-	 * @returns boolean
-	 */
-	public synchronized boolean canUndo() {
-		return false; // TODO
-	} // canUndo()
-
-	/**
-	 * redo
-	 * @exception CannotRedoException TODO
-	 */
-	public synchronized void redo() throws CannotRedoException {
-		// TODO
-	} // redo()
-
-	/**
-	 * canRedo
-	 * @returns boolean
-	 */
-	public synchronized boolean canRedo() {
-		return false; // TODO
-	} // canRedo()
-
-	/**
-	 * addEdit
-	 * @param value0 TODO
-	 * @returns boolean
-	 */
-	public synchronized boolean addEdit(UndoableEdit value0) {
-		return false; // TODO
-	} // addEdit()
-
-	/**
-	 * getUndoOrRedoPresentationName
-	 * @returns String
-	 */
-	public synchronized String getUndoOrRedoPresentationName() {
-		return null; // TODO
-	} // getUndoOrRedoPresentationName()
+  int limit;
 
-	/**
-	 * getUndoPresentationName
-	 * @returns String
-	 */
-	public synchronized String getUndoPresentationName() {
-		return null; // TODO
-	} // getUndoPresentationName()
 
 	/**
-	 * getRedoPresentationName
-	 * @returns String
-	 */
-	public synchronized String getRedoPresentationName() {
-		return null; // TODO
-	} // getRedoPresentationName()
-
-	/**
-	 * undoableEditHappened
-	 * @param value0 TODO
-	 */
-	public void undoableEditHappened(UndoableEditEvent value0) {
-		// TODO
-	} // undoableEditHappened()
-
-
-} // UndoManager
+   * Constructs an UndoManager.
+   *
+   * <p>The <code>limit</code> of the freshly constructed UndoManager
+   * is 100.
+   */
+  public UndoManager()
+  {
+    limit = 100;
+  }
+
+
+  /**
+   * Returns a string representation for this UndoManager. This may be
+   * useful for debugging purposes. For the text of menu items, please
+   * refer to {@link #getUndoPresentationName}, {@link
+   * #getRedoPresentationName}, and {@link
+   * #getUndoOrRedoPresentationName}.
+   */
+  public String toString()
+  {
+    return super.toString()
+      + " limit: " + limit
+      + " indexOfNextAdd: " + indexOfNextAdd;
+  }
+
+
+  /**
+   * Puts this UndoManager into a state where it acts as a normal
+   * {@link CompoundEdit}. It is unlikely that an application would
+   * want to do this.
+   */
+  public synchronized void end()
+  {
+    super.end();
+    trimEdits(indexOfNextAdd, edits.size() - 1);
+  }
+
+
+  /**
+   * Returns how many edits this UndoManager can maximally hold.
+   *
+   * @see #setLimit
+   */
+  public synchronized int getLimit()
+  {
+    return limit;
+  }
+
+
+  /**
+   * Changes the maximal number of edits that this UndoManager can
+   * process. If there are currently more edits than the new limit
+   * allows, they will receive a {@link UndoableEdit#die() die}
+   * message in reverse order of addition.
+   *
+   * @param limit the new limit.
+   *
+   * @throws IllegalStateException if {@link #end()} has already been
+   * called on this UndoManager.
+   */
+  public synchronized void setLimit(int limit)
+  {
+    if (!isInProgress())
+      throw new IllegalStateException();
+
+    this.limit = limit;
+    trimForLimit();
+  }
+
+
+  /**
+   * Discards all editing actions that are currently registered with
+   * this UndoManager. Each {@link UndoableEdit} will receive a {@link
+   * UndoableEdit#die() die message}.
+   */
+  public synchronized void discardAllEdits()
+  {
+    int size;
+
+    size = edits.size();
+    for (int i = size - 1; i >= 0; i--)
+      ((UndoableEdit) edits.get(i)).die();
+    indexOfNextAdd = 0;
+    edits.clear();
+  }
+
+
+  /**
+   * Called by various internal methods in order to enforce
+   * the <code>limit</code> value.
+   */
+  protected void trimForLimit()
+  {
+    int high, s;
+
+    s = edits.size();
+
+    /* The Sun J2SE1.4.1_01 implementation can be observed to do
+     * nothing (instead of throwing an exception) with a negative or
+     * zero limit. It may be debatable whether this is the best
+     * behavior, but we replicate it for sake of compatibility.
+     */
+    if (limit <= 0 || s <= limit)
+      return;
+
+    high = Math.min(indexOfNextAdd + limit/2 - 1, s - 1);
+    trimEdits(high + 1, s - 1);
+    trimEdits(0, high - limit);
+  }
+
+
+  /**
+   * Discards a range of edits. All edits in the range <code>[from
+   * .. to]</code> will receive a {@linkplain UndoableEdit#die() die
+   * message} before being removed from the edits array.  If
+   * <code>from</code> is greater than <code>to</code>, nothing
+   * happens.
+   *
+   * @param from the lower bound of the range of edits to be
+   * discarded.
+   *
+   * @param to the upper bound of the range of edits to be discarded.
+   */
+  protected void trimEdits(int from, int to)
+  {
+    if (from > to)
+      return;
+
+    for (int i = to; i >= from; i--)
+        ((UndoableEdit) edits.get(i)).die();
+
+    // Remove the range [from .. to] from edits. If from == to, which
+    // is likely to be a very common case, we can do better than
+    // creating a sub-list and clearing it.
+    if (to == from)
+      edits.remove(from);
+    else
+      edits.subList(from, to + 1).clear();
+
+    if (indexOfNextAdd > to)
+      indexOfNextAdd = indexOfNextAdd - to + from - 1;
+    else if (indexOfNextAdd >= from)
+      indexOfNextAdd = from;
+  }
+
+
+  /**
+   * Determines which significant edit would be undone if {@link
+   * #undo()} was called.
+   *
+   * @returns the significant edit that would be undone, or
+   * <code>null</code> if no significant edit would be affected by
+   * calling {@link #undo()}.
+   */
+  protected UndoableEdit editToBeUndone()
+  {
+    UndoableEdit result;
+
+    for (int i = indexOfNextAdd - 1; i >= 0; i--)
+      {
+        result = (UndoableEdit) edits.get(i);
+        if (result.isSignificant())
+          return result;
+      }
+
+    return null;
+  }
+
+
+  /**
+   * Determines which significant edit would be redone if {@link
+   * #redo()} was called.
+   *
+   * @returns the significant edit that would be redone, or
+   * <code>null</code> if no significant edit would be affected by
+   * calling {@link #redo()}.
+   */
+  protected UndoableEdit editToBeRedone()
+  {
+    UndoableEdit result;
+
+    for (int i = indexOfNextAdd; i < edits.size(); i++)
+      {
+        result = (UndoableEdit) edits.get(i);
+        if (result.isSignificant())
+          return result;
+      }
+
+    return null;
+  }
+
+
+  /**
+   * Undoes all editing actions in reverse order of addition,
+   * up to the specified action,
+   *
+   * @param edit the last editing action to be undone.
+   */
+  protected void undoTo(UndoableEdit edit)
+    throws CannotUndoException
+  {
+    UndoableEdit cur;
+
+    if (!edits.contains(edit))
+      throw new CannotUndoException();
+
+    while (true)
+      {
+        indexOfNextAdd -= 1;
+        cur = (UndoableEdit) edits.get(indexOfNextAdd);
+        cur.undo();
+        if (cur == edit)
+          return;
+      }
+  }
+
+
+  /**
+   * Redoes all editing actions in the same order as they were
+   * added to this UndoManager, up to the specified action.
+   *
+   * @param edit the last editing action to be redone.
+   */
+  protected void redoTo(UndoableEdit edit)
+    throws CannotRedoException
+  {
+    UndoableEdit cur;
+
+    if (!edits.contains(edit))
+      throw new CannotRedoException();
+
+    while (true)
+      {
+        cur = (UndoableEdit) edits.get(indexOfNextAdd);
+        indexOfNextAdd += 1;
+        cur.redo();
+        if (cur == edit)
+          return;
+      }
+  }
+
+  
+  /**
+   * Undoes or redoes the last action. If the last action has already
+   * been undone, it will be re-done, and vice versa.
+   *
+   * <p>This is useful for applications that do not present a separate
+   * undo and redo facility, but just have a single menu item for
+   * undoing and redoing the very last action. Such applications will
+   * use an <code>UndoManager</code> whose <code>limit</code> is 1.
+   */
+  public synchronized void undoOrRedo()
+    throws CannotRedoException, CannotUndoException
+  {
+    if (indexOfNextAdd == edits.size())
+      undo();
+    else
+      redo();
+  }
+
+
+  /**
+   * Determines whether it would be possible to either undo or redo
+   * this editing action.
+   *
+   * <p>This is useful for applications that do not present a separate
+   * undo and redo facility, but just have a single menu item for
+   * undoing and redoing the very last action. Such applications will
+   * use an <code>UndoManager</code> whose <code>limit</code> is 1.
+   *
+   * @return <code>true</code> to indicate that this action can be
+   * undone or redone; <code>false</code> if neither is possible at
+   * the current time.
+   */
+  public synchronized boolean canUndoOrRedo()
+  {
+    return indexOfNextAdd == edits.size() ? canUndo() : canRedo();
+  }
+
+
+  /**
+   * Undoes one significant edit action. If insignificant actions have
+   * been posted after the last signficant action, the insignificant
+   * ones will be undone first.
+   *
+   * <p>However, if {@link #end()} has been called on this
+   * UndoManager, it will behave like a normal {@link
+   * CompoundEdit}. In this case, all actions will be undone in
+   * reverse order of addition. Typical applications will never call
+   * {@link #end()} on their <code>UndoManager</code>.
+   *
+   * @throws CannotUndoException if no action can be undone.
+   *
+   * @see #canUndo()
+   * @see #redo()
+   * @see #undoOrRedo()
+   */
+  public synchronized void undo()
+    throws CannotUndoException
+  {
+    if (!isInProgress())
+      {
+        super.undo();
+        return;
+      }
+
+    UndoableEdit edit = editToBeUndone();
+    if (edit == null)
+      throw new CannotUndoException();
+
+    undoTo(edit);
+  }
+
+
+  /**
+   * Determines whether it would be possible to undo this editing
+   * action.
+   *
+   * @return <code>true</code> to indicate that this action can be
+   * undone; <code>false</code> otherwise.
+   *
+   * @see #undo()
+   * @see #canRedo()
+   * @see #canUndoOrRedo()
+   */
+  public synchronized boolean canUndo()
+  {
+    UndoableEdit edit;
+
+    if (!isInProgress())
+      return super.canUndo();
+
+    edit = editToBeUndone();
+    return edit != null && edit.canUndo();
+  }
+
+
+
+  /**
+   * Redoes one significant edit action. If insignificant actions have
+   * been posted in between, the insignificant ones will be redone
+   * first.
+   *
+   * <p>However, if {@link #end()} has been called on this
+   * UndoManager, it will behave like a normal {@link
+   * CompoundEdit}. In this case, <em>all</em> actions will be redone
+   * in order of addition. Typical applications will never call {@link
+   * #end()} on their <code>UndoManager</code>.
+   *
+   * @throws CannotRedoException if no action can be redone.
+   *
+   * @see #canRedo()
+   * @see #redo()
+   * @see #undoOrRedo()
+   */
+  public synchronized void redo()
+    throws CannotRedoException
+  {
+    if (!isInProgress())
+      {
+        super.redo();
+        return;
+      }
+
+    UndoableEdit edit = editToBeRedone();
+    if (edit == null)
+      throw new CannotRedoException();
+
+    redoTo(edit);
+  }
+
+
+  /**
+   * Determines whether it would be possible to redo this editing
+   * action.
+   *
+   * @return <code>true</code> to indicate that this action can be
+   * redone; <code>false</code> otherwise.
+   *
+   * @see #redo()
+   * @see #canUndo()
+   * @see #canUndoOrRedo()
+   */
+  public synchronized boolean canRedo()
+  {
+    UndoableEdit edit;
+
+    if (!isInProgress())
+      return super.canRedo();
+
+    edit = editToBeRedone();
+    return edit != null && edit.canRedo();
+  }
+
+
+  /**
+   * Registers an undoable editing action with this UndoManager.  If
+   * the capacity <code>limit</code> is reached, the oldest action
+   * will be discarded (and receives a {@linkplain UndoableEdit#die()
+   * die message}. Equally, any actions that were undone (but not re-done)
+   * will be discarded, too.
+   *
+   * @param edit the editing action that is added to this UndoManager.
+   *
+   * @return <code>true</code> if <code>edit</code> could be
+   * incorporated; <code>false</code> if <code>edit</code> has not
+   * been incorporated because {@link #end()} has already been called
+   * on this <code>UndoManager</code>.
+   */
+  public synchronized boolean addEdit(UndoableEdit edit)
+  {
+    boolean result;
+
+    // Discard any edits starting at indexOfNextAdd.
+    trimEdits(indexOfNextAdd, edits.size() - 1);
+
+    result = super.addEdit(edit);
+    indexOfNextAdd = edits.size();
+    trimForLimit();
+    return result;
+  }
+
+
+  /**
+   * Calculates a localized text for presenting the undo or redo
+   * action to the user, for example in the form of a menu command.
+   *
+   * <p>This is useful for applications that do not present a separate
+   * undo and redo facility, but just have a single menu item for
+   * undoing and redoing the very last action. Such applications will
+   * use an <code>UndoManager</code> whose <code>limit</code> is 1.
+   *
+   * @return the redo presentation name if the last action has already
+   * been undone, or the undo presentation name otherwise.
+   *
+   * @see #getUndoPresentationName()
+   * @see #getRedoPresentationName()
+   */
+  public synchronized String getUndoOrRedoPresentationName()
+  {
+    if (indexOfNextAdd == edits.size())
+      return getUndoPresentationName();
+    else
+      return getRedoPresentationName();
+  }
+
+
+  /**
+   * Calculates a localized text for presenting the undo action
+   * to the user, for example in the form of a menu command.
+   */
+  public synchronized String getUndoPresentationName()
+  {
+    UndoableEdit edit;
+
+    if (!isInProgress())
+      return super.getUndoPresentationName();
+
+    edit = editToBeUndone();
+    if (edit == null)
+      return UIManager.getString("AbstractUndoableEdit.undoText");
+    else
+      return edit.getUndoPresentationName();
+  }
+
+
+  /**
+   * Calculates a localized text for presenting the redo action
+   * to the user, for example in the form of a menu command.
+   */
+  public synchronized String getRedoPresentationName()
+  {
+    UndoableEdit edit;
+
+    if (!isInProgress())
+      return super.getRedoPresentationName();
+
+    edit = editToBeRedone();
+    if (edit == null)
+      return UIManager.getString("AbstractUndoableEdit.redoText");
+    else
+      return edit.getRedoPresentationName();
+  }
+  
+  
+  /**
+   * Registers the edit action of an {@link UndoableEditEvent}
+   * with this UndoManager.
+   *
+   * <p><b>Thread Safety:</b> This method may safely be invoked from
+   * concurrent threads.  The caller does not need to perform external
+   * synchronization. This means that {@link
+   * javax.swing.event.UndoableEvent} sources do not need to broadcast
+   * their events from inside the Swing worker thread.
+   *
+   * @param event the event whose <code>edit</code> will be
+   * passed to {@link #addEdit}.
+   *
+   * @see UndoableEditEvent#getEdit()
+   * @see #addEdit
+   */
+  public void undoableEditHappened(UndoableEditEvent event)
+  {
+    // Note that this method does not need to be synchronized,
+    // because addEdit will obtain and release the mutex.
+    addEdit(event.getEdit());
+  }
+}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]