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

editor.undo.AtomicUndoManager Maven / Gradle / Ivy

There is a newer version: 1.18.1
Show newest version
/*
 *
 *  Copyright 2010 Guidewire Software, Inc.
 *
 */
package editor.undo;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.StateEdit;
import javax.swing.undo.StateEditable;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Stack;


/**
 * This class extends UndoManager providing nested atomic (or scoped) undo/redo
 * operations.  Note undo/redo operations are captured in StateEditable implementations.
 * 

* A generic application of this class follows: *

*

 * ...
 * _undoMgr = new AtomicUndoManager( 1000 );
 * ...
 * //
 * // This method decorates a typical change operation with undo/redo capability.
 * // Notice the XXXOperationUndoItem class.  You'll need to create one of these
 * // for each undo operation.  Basically these UndoItem classes are extensions
 * // of StateEditable.  They are responsible for capturing the state
 * // before and after an operation and also for restoring the state as directed
 * // by this undo manager.
 * //
 * void performUndoableXXXOperation( ... )
 * {
 *   XXXOperationUndoItem undoItem = new XXXOperationUndoItem( ... );
 *   StateEdit transaction = new StateEdit( undoItem, "XXX Operation" );
 *
 *   performXXXOperation( ... );
 *
 *   transaction.end();
 *   _undoMgr.addEdit( transaction );
 * }
 *
 * // Your typical change operation.
 * void performXXXOperation( ... )
 * {
 *   // do whatever it is you do...
 * }
 *
 * //
 * // This method exercises the atomic and nested features of AtomicUndoManager.
 * // The atomic boundaries should be enforced with the try/finally idiom so
 * // a failed atomic operation can be safely (and optionally) "rolled back".
 * //
 * void performUndoableZZZOperation
 * {
 *   try
 *   {
 *     _undoMgr.beginUndoAtom( "ZZZ Operation" );
 *
 *     performUndoableXXXOperation( ... );
 *     performUndoableYYYOperation( ... );
 *     ...
 *   }
 *   finally
 *   {
 *     _undoMgr.endUndoAtom();
 *   }
 * }
 *
 *
 * //
 * // Create ui components using Actions such as these:
 * //
 * class UndoAction extends AbstractAction
 * {
 *   UndoAction( String strName, Icon icon )
 *   {
 *     super( strName, icon );
 *   }
 *
 *   public void actionPerformed( ActionEvent e )
 *   {
 *     _undoMgr.undo();
 *   }
 *
 *   public boolean isEnabled()
 *   {
 *     return _undoMgr.canUndo();
 *   }
 * }
 *
 * class RedoAction extends AbstractAction
 * {
 *   RedoAction( String strName, Icon icon )
 *   {
 *     super( strName, icon );
 *   }
 *
 *   public void actionPerformed( ActionEvent e )
 *   {
 *     _undoMgr.redo();
 *   }
 *
 *   public boolean isEnabled()
 *   {
 *     return _undoMgr.canRedo();
 *   }
 * }
*/ public class AtomicUndoManager extends UndoManager { /** * A stack used for managing nested undo atoms. Whenever an atomic undo atom * begins a new DisplayableCompoundEdit is constructed on it's * behalf and pushed onto the stack. When an undo atom ends its DisplayableCompoundEdit * is popped off the stack and added to the next atom's edit list in the stack or, * if the stack is empty, it is directly added to this undo manager's edit list. */ private Stack _undoAtomNest; private boolean _bPaused; private List _changeListeners; private long _lLastUndoTime; /** * An AtomicUndoManager with a default limit of 1000 edits. */ public AtomicUndoManager() { this( 1000 ); } /** * @param iUndoLimit The maximum number of edits this undo manager will hold. */ public AtomicUndoManager( int iUndoLimit ) { super(); setLimit( iUndoLimit ); _undoAtomNest = new Stack<>(); _changeListeners = new ArrayList<>(); } /** * @return Is this undo manager in a Paused state? i.e., are edits being ignored? */ public boolean isPaused() { return _bPaused; } /** * Sets the paused state of the undo manager. If paused, undo operations are ignored * * @param bPaused The paused state. */ public void setPaused( boolean bPaused ) { _bPaused = bPaused; } /** * @param edit The edit to be added. Note nested edits are supported. */ @Override public synchronized boolean addEdit( UndoableEdit edit ) { if( isPaused() ) { // Ignore edits while paused... return false; } fireChangeEvent( UndoChangeEvent.ChangeType.ADD_EDIT, edit ); DisplayableCompoundEdit undoAtom = getUndoAtom(); if( undoAtom != null ) { return undoAtom.addEdit( edit ); } return super.addEdit( edit ); } /** * @param edit The edit to be removed. */ @SuppressWarnings("UnusedDeclaration") public synchronized void removeEdit( UndoableEdit edit ) { int iIndex = edits.indexOf( edit ); if( iIndex < 0 ) { return; } trimEdits( iIndex, iIndex ); } @Override public synchronized void discardAllEdits() { super.discardAllEdits(); _bPaused = false; _undoAtomNest.clear(); } /** * Begin an atomic undo boundary. Edits added to this undo mananger after * this call are considered "subatomic" -- they become part of the atom * defined by the boundary. The atom's boundary ends with a call to endUndoAtom(). *

* Note undo atoms can contain other undo atoms allowing for unlimited nesting of atomic * undo operations. */ public void beginUndoAtom() { beginUndoAtom( null ); } /** * Begin an atomic undo boundary. Edits added to this undo mananger after * this call are considered "subatomic" -- they become part of the atom * defined by the boundary. The atom's boundary ends with a call to endUndoAtom(). *

* Note undo atoms can contain other undo atoms allowing for unlimited nesting of atomic * undo operations. * * @param strDisplayName The name associated with the undo atom. A user interface typically * displays this name in Undo/Redo menu items or toolbar button text. * * @see javax.swing.undo.UndoManager#getUndoPresentationName */ public CompoundEdit beginUndoAtom( String strDisplayName ) { if( isPaused() ) { // Ignore edits while paused... return null; } CompoundEdit parent = null; try { parent = _undoAtomNest.peek(); } catch( EmptyStackException empty ) { // ignore } parent = parent == null ? this : parent; DisplayableCompoundEdit undoAtom = new DisplayableCompoundEdit( strDisplayName ); parent.addEdit( undoAtom ); _undoAtomNest.push( undoAtom ); return undoAtom; } /** * Ends the active atomic undo boundary. * * @see #beginUndoAtom */ public void endUndoAtom() { endUndoAtom( null ); } public void endUndoAtom( CompoundEdit undoAtom ) { if( isPaused() ) { // Ignore edits while paused... return; } try { CompoundEdit csr; do { csr = _undoAtomNest.pop(); csr.end(); } while( undoAtom != null && csr != undoAtom ); } catch( Exception e ) { e.printStackTrace(); } } /** * @return The undo atom currently in progress. */ public DisplayableCompoundEdit getUndoAtom() { DisplayableCompoundEdit undoAtom = null; try { undoAtom = _undoAtomNest.peek(); } catch( EmptyStackException empty ) { // ignore } return undoAtom; } @Override public synchronized void redo() throws CannotRedoException { setLastUndoTime(); try { fireChangeEvent( UndoChangeEvent.ChangeType.REDO, null ); UndoableEdit edit = editToBeRedone(); if( edit instanceof IStagedStateEdit ) { IStagedStateEditable stagedState = ((IStagedStateEdit)edit).getStagedRedoState(); if( stagedState != null && !stagedState.prepareForRedo() ) { return; } } super.redo(); } finally { setLastUndoTime(); } } @Override public synchronized void undo() throws CannotUndoException { setLastUndoTime(); try { fireChangeEvent( UndoChangeEvent.ChangeType.UNDO, null ); UndoableEdit edit = editToBeUndone(); if( edit instanceof IStagedStateEdit ) { IStagedStateEditable stagedState = ((IStagedStateEdit)edit).getStagedUndoState(); if( stagedState != null && !stagedState.prepareForUndo() ) { return; } } super.undo(); } finally { setLastUndoTime(); } } public long getLastUndoTime() { return _lLastUndoTime; } private void setLastUndoTime() { _lLastUndoTime = System.currentTimeMillis(); } @Override public synchronized void undoOrRedo() throws CannotRedoException, CannotUndoException { fireChangeEvent( UndoChangeEvent.ChangeType.UNDO_OR_REDO, null ); super.undoOrRedo(); } @Override protected void undoTo( UndoableEdit edit ) throws CannotUndoException { fireChangeEvent( UndoChangeEvent.ChangeType.UNDO_TO, edit ); super.undoTo( edit ); } @Override protected void redoTo( UndoableEdit edit ) throws CannotRedoException { fireChangeEvent( UndoChangeEvent.ChangeType.REDO_TO, edit ); super.redoTo( edit ); } public void addChangeListener( ChangeListener listener ) { _changeListeners.add( listener ); } public void removeChangeListener( ChangeListener listener ) { _changeListeners.remove( listener ); } /** * Utility method that records a change that can be undone/redone. */ @SuppressWarnings("UnusedDeclaration") public void recordChange( StateEditable change ) { beginUndoAtom(); try { StateEdit transaction = new StateEdit( change ); transaction.end(); addEdit( transaction ); } finally { endUndoAtom(); } } private void fireChangeEvent( UndoChangeEvent.ChangeType type, UndoableEdit edit ) { ChangeListener[] listeners = _changeListeners.toArray( new ChangeListener[_changeListeners.size()] ); if( listeners.length == 0 ) { return; } ChangeEvent e = new UndoChangeEvent( this, type, edit ); for( int i = 0; i < listeners.length; i++ ) { ChangeListener listener = listeners[i]; listener.stateChanged( e ); } } /** */ static class DisplayableCompoundEdit extends CompoundEdit implements IStagedStateEdit { String _strDisplayName; DisplayableCompoundEdit( String strDisplayName ) { super(); _strDisplayName = strDisplayName; } @Override public boolean isInProgress() { return false; } @Override public IStagedStateEditable getStagedUndoState() { UndoableEdit edit = lastEdit(); return edit instanceof IStagedStateEdit ? ((IStagedStateEdit)edit).getStagedUndoState() : null; } @Override public IStagedStateEditable getStagedRedoState() { UndoableEdit edit = edits.size() > 0 ? edits.elementAt( 0 ) : null; return edit instanceof IStagedStateEdit ? ((IStagedStateEdit)edit).getStagedRedoState() : null; } @Override public String getPresentationName() { if( _strDisplayName != null && _strDisplayName.length() > 0 ) { return _strDisplayName; } return super.getPresentationName(); } @Override public String getUndoPresentationName() { String strDisplayName = getPresentationName(); if( strDisplayName != null && strDisplayName.length() > 0 ) { return UndoName + " " + strDisplayName; } return super.getUndoPresentationName(); } @Override public String getRedoPresentationName() { String strDisplayName = getPresentationName(); if( strDisplayName != null && strDisplayName.length() > 0 ) { return RedoName + " " + strDisplayName; } return super.getRedoPresentationName(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy