All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.awt.UndoRedo Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * 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: *

* * @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