// // @(#)UndoRedoManager.java 1.00 4/2002 // // Copyright 2002 Zachary DelProposto. All rights reserved. // Use is subject to license terms. // // // This program 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 of the License, or // (at your option) any later version. // // This program 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 this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Or from http://www.gnu.org/ // package dip.gui.undo; import dip.gui.ClientMenu; import dip.gui.OrderDisplayPanel; import dip.gui.ClientFrame; import dip.misc.Log; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; import java.io.*; import java.util.*; /** * * Manages Undo/Redo events, and associated Edit menu items involving * Undo and Redo. * <p> * This is not a singleton. * * */ public class UndoRedoManager extends UndoManager { // the max number of undo/redo events we can hold private static final int MAX_UNDOS = 1000; // instance variables private transient ClientFrame clientFrame; private transient OrderDisplayPanel orderDisplayPanel; /** Constructor */ public UndoRedoManager(ClientFrame clientFrame, OrderDisplayPanel orderDisplayPanel) { super(); if(clientFrame == null || orderDisplayPanel == null) { throw new IllegalArgumentException("null argument(s)"); } this.clientFrame = clientFrame; this.orderDisplayPanel = orderDisplayPanel; super.setLimit(MAX_UNDOS); }// UndoRedoManager() /** Can be used post-deserialization */ public synchronized void setClientFrame(ClientFrame clientFrame) { if(clientFrame == null) { throw new IllegalArgumentException("null clientFrame"); } this.clientFrame = clientFrame; }// setClientFrame() /** Can be used post-deserialization */ public synchronized void setOrderDisplayPanel(OrderDisplayPanel orderDisplayPanel) { if(orderDisplayPanel == null) { throw new IllegalArgumentException("null clientFrame"); } this.orderDisplayPanel = orderDisplayPanel; }// setOrderDisplayPanel() /** Add an Edit (UndoableEdit) */ public synchronized boolean addEdit(UndoableEdit anEdit) { //System.out.println("URM: addEdit(): edits (before): "+edits.size()); checkState(); //System.out.println(" added: "+anEdit); final boolean retVal = super.addEdit(anEdit); //System.out.println(" addEdit(): edits (after): "+edits.size()); refreshMenu(); return retVal; }// addEdit() /** Redo last undo */ public synchronized void redo() { //System.out.println("URM: redo(): edits (before): "+edits.size()); checkState(); super.redo(); //System.out.println(" redo(): edits (after): "+edits.size()); refreshMenu(); }// redo() /** Undo an UndoableEdit */ public synchronized void undo() { //System.out.println("URM: undo(): edits (before): "+edits.size()); checkState(); super.undo(); //System.out.println(" undo(): edits (after): "+edits.size()); refreshMenu(); }// undo() /** Throw away all stored edits */ public synchronized void discardAllEdits() { checkState(); super.discardAllEdits(); refreshMenu(); }// discardAllEdits() /** Undo or Redo */ public synchronized void undoOrRedo() { checkState(); super.undoOrRedo(); refreshMenu(); }// undoOrRedo() /** For debugging: lists edits. */ public void dumpEdits() { if(Log.isLogging()) { Log.println("UndoRedoManager: "+edits.size()+" edits"); for(int i=0; i<edits.size(); i++) { Log.println(" "+i+": "+edits.get(i)); } } }// dumpEdits() /** Returns the OrderDisplayPanel associated with this UndoRedo manager. */ public synchronized OrderDisplayPanel getOrderDisplayPanel() { return orderDisplayPanel; }// getOrderDisplayPanel() /** Returns the ClientFrame object */ public synchronized ClientFrame getClientFrame() { return clientFrame; }// getWorld() /** * Refreshes the menu items, enabling & adding action names as appropriate. * This is mode-aware; undo/redo is only available when in MODE_EDIT and * MODE_ORDER. */ public void refreshMenu() { ClientMenu menu = clientFrame.getClientMenu(); menu.setText(ClientMenu.EDIT_UNDO, getUndoPresentationName()); menu.setText(ClientMenu.EDIT_REDO, getRedoPresentationName()); if( clientFrame.getMode() == ClientFrame.MODE_ORDER || clientFrame.getMode() == ClientFrame.MODE_EDIT ) { menu.setEnabled(ClientMenu.EDIT_UNDO, canUndo()); menu.setEnabled(ClientMenu.EDIT_REDO, canRedo()); } else { menu.setEnabled(ClientMenu.EDIT_UNDO, false); menu.setEnabled(ClientMenu.EDIT_REDO, false); } }// refreshMenu() /** * Filters the Undo list, in reverse order, removing all actions * until the first UndoResolve action is detected. This is used * when in F2F and switching powers, so that a power can 'undo' * but cannot undo or see another power's moves. */ public synchronized void filterF2F() { Log.println("UndoRedoManager::filterF2F()"); ListIterator listIter = edits.listIterator(edits.size()); int from = Integer.MAX_VALUE; while(listIter.hasPrevious()) { final int idx = listIter.previousIndex(); UndoableEdit ue = (UndoableEdit) listIter.previous(); //Log.println(" checking: ", String.valueOf(idx), ": ", ue.getClass().getName()); if(ue instanceof UndoResolve) { break; } else { from = idx; } } // trim the edits (trimEdits() does nothing if from > to) Log.println(" trimEdits(): ", String.valueOf(from), "->", String.valueOf(edits.size()-1)); trimEdits(from, edits.size()-1); refreshMenu(); }// filterF2F() /** * Simplifies the undo list. We save all undoable actions for the current * TurnState. However, after the TurnState has been resolved, all old * actions (order adds/deletes, edit actions, etc) <b>except</b> for * UndoResolve are eliminated. * <p> * The rationale for this is this enables quickly undoing a number of * resolves, and simplifies the task of going 'back in time' to change * the game. * */ public synchronized void simplify() { Log.println("UndoRedoManager::simplify()"); //dumpEdits(); // search backwards. After we find the first UndoResolve, keep going // backwards, telling those edits to die() and then dequeuing // unless they are UndoResolves. // // We wait until after we find the first UndoResolve to avoid destroying // any edits (if any) in the current unresolved turnstate. // ListIterator listIter = edits.listIterator(edits.size()); boolean foundResolved = false; int from = Integer.MAX_VALUE; int to = -1; while(listIter.hasPrevious()) { final int idx = listIter.previousIndex(); UndoableEdit ue = (UndoableEdit) listIter.previous(); //Log.println(" checking: ", String.valueOf(idx), ": ", ue.getClass().getName()); if(ue instanceof UndoResolve) { if(!foundResolved) { foundResolved = true; } else { break; } } else { if(foundResolved && to < 0) { to = idx; // set but one time } else { from = idx; // this gets updated only when we found a 'to' } } } // trim the edits (trimEdits() does nothing if from > to) Log.println(" trimEdits(): ", String.valueOf(from), "->", String.valueOf(to)); trimEdits(from, to); refreshMenu(); }// simplify() /** Internal consistency check */ private void checkState() { if(clientFrame == null || orderDisplayPanel == null) { throw new IllegalArgumentException("error: clientframe/orderpanel not set"); } }// checkState() }// class UndoRedoManager////