summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/undo/UndoManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/undo/UndoManager.java')
-rw-r--r--libjava/classpath/javax/swing/undo/UndoManager.java625
1 files changed, 625 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/undo/UndoManager.java b/libjava/classpath/javax/swing/undo/UndoManager.java
new file mode 100644
index 000000000..9b10cc575
--- /dev/null
+++ b/libjava/classpath/javax/swing/undo/UndoManager.java
@@ -0,0 +1,625 @@
+/* UndoManager.java --
+ Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.undo;
+
+import javax.swing.UIManager;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+
+
+/**
+ * A manager for providing an application’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.UndoableEditEvent} sources do not need to
+ * broadcast their events from inside the Swing worker thread.
+ *
+ * @author Sascha Brawer (brawer@dandelis.ch)
+ */
+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;
+
+
+ /**
+ * 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;
+
+
+ /**
+ * The maximum number of UndoableEdits stored by this UndoManager.
+ */
+ int limit;
+
+
+ /**
+ * 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--)
+ 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--)
+ 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.
+ *
+ * @return 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 = edits.get(i);
+ if (result.isSignificant())
+ return result;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Determines which significant edit would be redone if {@link
+ * #redo()} was called.
+ *
+ * @return 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 = 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 = 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 = 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.UndoableEditEvent} 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());
+ }
+}