diff --git a/libjava/ChangeLog b/libjava/ChangeLog index cce4eb504de..56030dd8576 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,7 @@ +2004-02-14 Sascha Brawer + + * javax/swing/undo/UndoManager.java: Re-written from scratch. + 2004-02-14 Per Bothner * java/nio/channels/spi/AbstractInterruptibleChannel.java (close): diff --git a/libjava/javax/swing/undo/UndoManager.java b/libjava/javax/swing/undo/UndoManager.java index 56e1c1b7247..c1b7cb88ffa 100644 --- a/libjava/javax/swing/undo/UndoManager.java +++ b/libjava/javax/swing/undo/UndoManager.java @@ -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,223 +38,588 @@ exception statement from your version. */ package javax.swing.undo; +import javax.swing.UIManager; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; + /** - * UndoManager - * @author Andrew Selkirk + * A manager for providing an application’s undo/redo + * functionality. + * + *

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 + * UndoManager. To implement the “undo” and + * “redo” menu commands, the application invokes the + * UndoManager’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}. + * + *

The UndoManager will only keep a specified number of editing + * actions, the limit. 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. + * + *

Some applications do not provide separate menu commands for + * “undo” and “redo.” Instead, they + * have just a single command whose text switches between the two. + * Such applications would use an UndoManager with a limit + * of 1. The text of this combined menu item is available via + * {@link #getUndoOrRedoPresentationName}, and it is implemented + * by calling {@link #undoOrRedo}. + * + *

Thread Safety: In constrast to the other classes of the + * javax.swing.undo package, the public methods of an + * UndoManager 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 Sascha Brawer */ -public class UndoManager extends CompoundEdit implements UndoableEditListener { - - //------------------------------------------------------------- - // Variables -------------------------------------------------- - //------------------------------------------------------------- - - /** - * indexOfNextAdd - */ - int indexOfNextAdd; - - /** - * limit - */ - int limit; +public class UndoManager + extends CompoundEdit + implements UndoableEditListener +{ + /** + * The unique ID for serializing instances of this class. Determined + * using the serialver tool of Sun JDK 1.4.1_01 on + * GNU/Linux. + */ + static final long serialVersionUID = -2077529998244066750L; - //------------------------------------------------------------- - // Initialization --------------------------------------------- - //------------------------------------------------------------- - - /** - * Constructor UndoManager - */ - public UndoManager() { - // TODO - } // UndoManager() + /** + * An index into the inherited {@link #edits} Vector that indicates + * at which position newly added editing actions would get inserted. + * + *

Normally, the value of indexOfNextAdd equals + * the number of UndoableEdits stored by this UndoManager, i.e. + * edits.size(). For each call to {@link #undo}, + * indexOfNextAdd is decremented by one. For each + * call to {@link #redo}, it is incremented again. + */ + int indexOfNextAdd; - //------------------------------------------------------------- - // 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 - */ - 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() - - /** - * 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() + /** + * The maximum number of UndoableEdits stored by this UndoManager. + */ + int limit; -} // UndoManager + /** + * Constructs an UndoManager. + * + *

The limit 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 limit 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 [from + * .. to] will receive a {@linkplain UndoableEdit#die() die + * message} before being removed from the edits array. If + * from is greater than to, 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 + * null 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 + * null 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. + * + *

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 UndoManager whose limit 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. + * + *

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 UndoManager whose limit is 1. + * + * @return true to indicate that this action can be + * undone or redone; false 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. + * + *

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 UndoManager. + * + * @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 true to indicate that this action can be + * undone; false 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. + * + *

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 redone + * in order of addition. Typical applications will never call {@link + * #end()} on their UndoManager. + * + * @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 true to indicate that this action can be + * redone; false 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 limit 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 true if edit could be + * incorporated; false if edit has not + * been incorporated because {@link #end()} has already been called + * on this UndoManager. + */ + 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. + * + *

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 UndoManager whose limit 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. + * + *

Thread Safety: 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 edit 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()); + } +}