org.openide.awt.UndoRedo Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.awt;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.UIManager;
import javax.swing.event.*;
import javax.swing.undo.*;
import org.openide.util.ChangeSupport;
import org.openide.util.Lookup;
import org.openide.util.Utilities;
/** Undo and Redo manager for top components and workspace elements.
* It allows UndoAction
and RedoAction
to listen to editing changes of active
* components and to changes in their ability to do undo and redo.
*
*
* Related links:
*
* - org.openide.actions.UndoAction
* - org.openide.actions.RedoAction
* - org.openide.windows.TopComponent.getUndoRedo()
*
*
* @author Jaroslav Tulach
*/
public interface UndoRedo {
/** Empty implementation that does not allow
* any undo or redo actions.
*/
public static final UndoRedo NONE = new Empty();
/** Test whether the component currently has edits which may be undone.
* @return true
if undo is allowed
*/
public boolean canUndo();
/** Test whether the component currently has undone edits which may be redone.
* @return true
if redo is allowed
*/
public boolean canRedo();
/** Undo an edit.
* @exception CannotUndoException if it fails
*/
public void undo() throws CannotUndoException;
/** Redo a previously undone edit.
* @exception CannotRedoException if it fails
*/
public void redo() throws CannotRedoException;
/** Add a change listener.
* The listener will be notified every time the undo/redo
* ability of this object changes.
* @param l the listener to add
*/
public void addChangeListener(ChangeListener l);
/** Remove a change listener.
* @param l the listener to remove
* @see #addChangeListener
*/
public void removeChangeListener(ChangeListener l);
/** Get a human-presentable name describing the
* undo operation.
* @return the name
*/
public String getUndoPresentationName();
/** Get a human-presentable name describing the
* redo operation.
* @return the name
*/
public String getRedoPresentationName();
/** Components that provide {@link UndoRedo} shall announce that by
* implementing this provider interface. Both Edit/Undo and Edit/Redo actions
* seek this interface inside current selection (e.g. {@link Utilities#actionsGlobalContext()}).
* To control these actions make sure your implementation of this interface
* is exposed in instance representing {@link Lookup current context}.
*
* @since 7.25
*/
public static interface Provider {
/** Getter for {@link UndoRedo} implementation associated with this provider.
* @return non-null implementation
*/
public UndoRedo getUndoRedo();
}
/**
* An undo manager which fires a change event each time it consumes a new undoable edit.
*
* Compared to Swing this implementation is more stable.
* If any contained undo edit throws an exception from its undo/redo methods
* the implementation will fail gracefully (unlike in Swing it will not change
* an internal pointer inside edits).
*/
public static class Manager extends UndoManager implements UndoRedo {
static final long serialVersionUID = 6721367974521509720L;
// Protected field "edits" is retained
private int indexOfNextAdd;
private int limit;
private boolean inProgress;
private boolean hasBeenDone;
private boolean alive;
private final ChangeSupport cs = new ChangeSupport(this);
public Manager() {
hasBeenDone = true;
alive = true;
inProgress = true;
// Already done: edits = new Vector();
indexOfNextAdd = 0;
limit = 100;
edits.ensureCapacity(limit);
}
@Override
public void die() {
int size = edits.size();
for (int i = size-1; i >= 0; i--) {
UndoableEdit e = edits.elementAt(i);
e.die();
}
alive = false;
}
@Override
public boolean isInProgress() {
return inProgress;
}
@Override
public void end() {
inProgress = false;
this.trimEdits(indexOfNextAdd, edits.size()-1);
}
@Override
public void undo() throws CannotUndoException {
if (inProgress) {
UndoableEdit edit = editToBeUndone();
if (edit == null) {
throw new CannotUndoException();
}
undoTo(edit);
} else {
if (!canUndo()) {
throw new CannotUndoException();
}
int i = edits.size() - 1;
try {
for (; i >= 0; i--) {
edits.get(i).undo(); // may throw CannotUndoException
}
hasBeenDone = false;
} finally {
if (i != -1) { // i-th edit's undo failed => redo the ones above
int size = edits.size();
while (++i < size) {
edits.get(i).redo();
}
}
}
}
cs.fireChange();
}
@Override
protected void undoTo(UndoableEdit edit) throws CannotUndoException {
int i = indexOfNextAdd;
boolean done = false;
try {
while (!done) {
UndoableEdit next = edits.get(--i);
next.undo(); // may throw CannotUndoException
done = next == edit;
}
indexOfNextAdd = i;
} finally {
if (!done) { // i-th edit's undo failed => redo the ones above
i++;
for (; i < indexOfNextAdd; i++) {
edits.get(i).redo();
}
}
}
}
@Override
public boolean canUndo() {
if (inProgress) {
UndoableEdit edit = editToBeUndone();
return edit != null && edit.canUndo();
} else {
return !isInProgress() && alive && hasBeenDone;
}
}
@Override
public void redo() throws CannotRedoException {
if (inProgress) {
UndoableEdit edit = editToBeRedone();
if (edit == null) {
throw new CannotRedoException();
}
redoTo(edit);
} else {
if (!canRedo()) {
throw new CannotRedoException();
}
int i = 0;
int size = edits.size();
try {
for (; i < size; i++) {
edits.get(i).redo(); // may throw CannotRedoException
}
hasBeenDone = true;
} finally {
if (i != size) { // i-th edit's redo failed => undo the ones below
while (--i >= 0) {
edits.get(i).undo();
}
}
}
}
cs.fireChange();
}
@Override
protected void redoTo(UndoableEdit edit) throws CannotRedoException {
int i = indexOfNextAdd;
boolean done = false;
try {
while (!done) {
UndoableEdit next = edits.elementAt(i++);
next.redo(); // may throw CannotRedoException
done = next == edit;
}
indexOfNextAdd = i;
} finally {
if (!done) { // (i-1)-th edit's redo failed => undo the ones below
i -= 2;
for (; i >= indexOfNextAdd; i--) {
edits.get(i).undo();
}
}
}
}
@Override
public boolean canRedo() {
if (inProgress) {
UndoableEdit edit = editToBeRedone();
return edit != null && edit.canRedo();
} else {
return !isInProgress() && alive && !hasBeenDone;
}
}
@Override
public void undoOrRedo() throws CannotRedoException, CannotUndoException {
if (indexOfNextAdd == edits.size()) {
undo();
} else {
redo();
}
}
@Override
public boolean canUndoOrRedo() {
if (indexOfNextAdd == edits.size()) {
return canUndo();
} else {
return canRedo();
}
}
@Override
public int getLimit() {
return limit;
}
@Override
public void setLimit(int l) {
if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
limit = l;
trimForLimit();
}
@Override
protected void trimForLimit() {
if (limit >= 0) {
int size = edits.size();
if (size > limit) {
int halfLimit = limit/2;
int keepFrom = indexOfNextAdd - 1 - halfLimit;
int keepTo = indexOfNextAdd - 1 + halfLimit;
// These are ints we're playing with, so dividing by two
// rounds down for odd numbers, so make sure the limit was
// honored properly. Note that the keep range is
// inclusive.
if (keepTo - keepFrom + 1 > limit) {
keepFrom++;
}
// The keep range is centered on indexOfNextAdd,
// but odds are good that the actual edits Vector
// isn't. Move the keep range to keep it legal.
if (keepFrom < 0) {
keepTo -= keepFrom;
keepFrom = 0;
}
if (keepTo >= size) {
int delta = size - keepTo - 1;
keepTo += delta;
keepFrom += delta;
}
trimEdits(keepTo+1, size-1);
trimEdits(0, keepFrom-1);
}
}
}
@Override
protected void trimEdits(int from, int to) {
if (from <= to) {
for (int i = to; from <= i; i--) {
UndoableEdit e = edits.elementAt(i);
e.die();
edits.removeElementAt(i);
}
if (indexOfNextAdd > to) {
indexOfNextAdd -= to-from+1;
} else if (indexOfNextAdd >= from) {
indexOfNextAdd = from;
}
}
}
@Override
public void discardAllEdits() {
Enumeration cursor = edits.elements();
while (cursor.hasMoreElements()) {
UndoableEdit e = (UndoableEdit)cursor.nextElement();
e.die();
}
edits = new Vector();
indexOfNextAdd = 0;
cs.fireChange();
}
@Override
protected UndoableEdit lastEdit() {
int count = edits.size();
if (count > 0)
return edits.elementAt(count-1);
else
return null;
}
@Override
protected UndoableEdit editToBeUndone() {
int i = indexOfNextAdd;
while (i > 0) {
UndoableEdit edit = edits.elementAt(--i);
if (edit.isSignificant()) {
return edit;
}
}
return null;
}
@Override
protected UndoableEdit editToBeRedone() {
int count = edits.size();
int i = indexOfNextAdd;
while (i < count) {
UndoableEdit edit = edits.elementAt(i++);
if (edit.isSignificant()) {
return edit;
}
}
return null;
}
/** Consume an undoable edit.
* Delegates to superclass and notifies listeners.
* @param ue the edit
*/
@Override
public void undoableEditHappened(final UndoableEditEvent ue) {
addEdit(ue.getEdit());
cs.fireChange();
}
@Override
public boolean addEdit(UndoableEdit anEdit) {
boolean retVal;
// Trim from the indexOfNextAdd to the end, as we'll
// never reach these edits once the new one is added.
trimEdits(indexOfNextAdd, edits.size()-1);
if (!inProgress) {
retVal = false;
} else {
UndoableEdit last = lastEdit();
if (last == null) {
edits.addElement(anEdit);
} else if (!last.addEdit(anEdit)) {
if (anEdit.replaceEdit(last)) {
edits.removeElementAt(edits.size() - 1);
}
edits.addElement(anEdit);
}
retVal = true;
}
// Maybe super added this edit, maybe it didn't (perhaps
// an in progress compound edit took it instead. Or perhaps
// this UndoManager is no longer in progress). So make sure
// the indexOfNextAdd is pointed at the right place.
indexOfNextAdd = edits.size();
// Enforce the limit
trimForLimit();
return retVal;
}
@Override
public boolean replaceEdit(UndoableEdit anEdit) {
return false;
}
@Override
public boolean isSignificant() {
Enumeration cursor = edits.elements();
while (cursor.hasMoreElements()) {
if (((UndoableEdit)cursor.nextElement()).isSignificant()) {
return true;
}
}
return false;
}
@Override
public String getPresentationName() {
UndoableEdit last = lastEdit();
if (last != null) {
return last.getPresentationName();
} else {
return "";
}
}
@Override
public String getUndoPresentationName() {
// The following code does an original code of: return this.canUndo() ? super.getUndoPresentationName() : "";
if (canUndo()) {
// UndoManager.getUndoPresentationName() follows
if (inProgress) {
if (canUndo()) {
return editToBeUndone().getUndoPresentationName();
} else {
return UIManager.getString("AbstractUndoableEdit.undoText");
}
} else {
UndoableEdit last = lastEdit();
if (last != null) {
return last.getUndoPresentationName();
} else {
String name = getPresentationName();
if (!"".equals(name)) {
name = UIManager.getString("AbstractUndoableEdit.undoText")
+ " " + name;
} else {
name = UIManager.getString("AbstractUndoableEdit.undoText");
}
return name;
}
}
} else {
return "";
}
}
@Override
public String getRedoPresentationName() {
// The following code does an original code of: return this.canRedo() ? super.getRedoPresentationName() : "";
if (canRedo()) {
// UndoManager.getRedoPresentationName() follows
UndoableEdit last = lastEdit();
if (last != null) {
if (inProgress) {
if (canRedo()) {
return editToBeRedone().getRedoPresentationName();
} else {
return UIManager.getString("AbstractUndoableEdit.redoText");
}
} else {
return super.getRedoPresentationName();
}
} else {
String name = getPresentationName();
if (!"".equals(name)) {
name = UIManager.getString("AbstractUndoableEdit.redoText")
+ " " + name;
} else {
name = UIManager.getString("AbstractUndoableEdit.redoText");
}
return name;
}
} else {
return "";
}
}
@Override
public String getUndoOrRedoPresentationName() {
if (indexOfNextAdd == edits.size()) {
return getUndoPresentationName();
} else {
return getRedoPresentationName();
}
}
@Override
public String toString() {
return super.toString()
+ " hasBeenDone: " + hasBeenDone
+ " alive: " + alive
+ " inProgress: " + inProgress
+ " edits: " + edits
+ " limit: " + limit
+ " indexOfNextAdd: " + indexOfNextAdd;
}
@Override
public void addChangeListener(ChangeListener l) {
cs.addChangeListener(l);
}
/* Removes the listener
*/
@Override
public void removeChangeListener(ChangeListener l) {
cs.removeChangeListener(l);
}
}
// XXX cannot be made private in an interface, consider removing later
/** Empty implementation that does not support any undoable edits.
* @deprecated Use {@link UndoRedo#NONE} rather than instantiating this.
*/
@Deprecated
public static final class Empty extends Object implements UndoRedo {
@Override
public boolean canUndo() {
return false;
}
@Override
public boolean canRedo() {
return false;
}
@Override
public void undo() throws CannotUndoException {
throw new CannotUndoException();
}
@Override
public void redo() throws CannotRedoException {
throw new CannotRedoException();
}
@Override
public void addChangeListener(ChangeListener l) {
}
@Override
public void removeChangeListener(ChangeListener l) {
}
@Override
public String getUndoPresentationName() {
return ""; // NOI18N
}
@Override
public String getRedoPresentationName() {
return ""; // NOI18N
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy