//////////////////////////////////////////////////////////////////////////////// // Copyright 2012 Michael Schmalle - Teoti Graphix, LLC // // Licensed 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 // // Author: Michael Schmalle, Principal Architect // mschmalle at teotigraphix dot com //////////////////////////////////////////////////////////////////////////////// package com.teotigraphix.caustk.controller.command; import java.util.ArrayList; import java.util.List; import com.teotigraphix.caustic.core.IDispatcher; /** * @author Michael Schmalle * @copyright Teoti Graphix, LLC * @since 1.0 */ public class CommandHistory implements ICommandHistory { private IDispatcher dispatcher; private int mCursor; public int getCursor() { return mCursor; } private List<IUndoCommand> mCommands; /** * Total number of items in history, irrespective of their undone/redone * state. * * @return total number of items in history */ @Override public int size() { return mCommands.size(); } /** * Gets the command at the top of the history stack. This command will have * already been executed. * * @return command at the current position in the history stack, or null if * we're at position 0, in this case, there may be no commands on * the stack at all. * @see currentPosition * @see numberOfHistoryItems */ @Override public IUndoCommand getCurrent() { if (mCommands.size() == 0 || mCursor == 0) { return null; } return mCommands.get(mCursor - 1); } public CommandHistory(IDispatcher dispatcher) { this.dispatcher = dispatcher; mCommands = new ArrayList<IUndoCommand>(); mCursor = 0; } @Override public void clear() { mCommands.clear(); mCursor = 0; dispatcher.trigger(new OnClearComplete()); } /** * True if there's a command to redo. * * @return true if there's a command to redo */ private boolean hasNext() { return (mCursor < size()); } /** * True if there's a command to undo. * * @return true if there's a command to undo */ private boolean hasPrevious() { return (mCursor > 0); } /** * Redo/execute the next command on the history stack. * * @return position in history stack after this operation */ private int next() { if (!hasNext()) return mCursor; mCommands.get(mCursor).execute(); mCursor++; dispatcher.trigger(new OnNextComplete(getCurrent())); return mCursor; } private int previous() throws Exception { if (!hasPrevious()) return 0; mCommands.get(mCursor - 1).undo(); // If there's no undone command, // dispatch null as the historyevent command IUndoCommand undoneCommand; if (mCommands.size() > 0 && mCursor != 0) { //the undone command undoneCommand = mCommands.get(mCursor - 1); } else { undoneCommand = null; } mCursor--; dispatcher.trigger(new OnPreviousComplete(undoneCommand)); return mCursor; } @Override public int rewind() throws Exception { return rewind(0); } @Override public int rewind(int cursor) throws Exception { int newCurosr; if (cursor == 0) { newCurosr = 0; } else { newCurosr = mCursor - cursor; } if (newCurosr != -1) { // Move backward while possible while (hasPrevious() && mCursor != newCurosr) { previous(); } dispatcher.trigger(new OnRewindComplete(getCurrent())); } else { return -1; } return mCursor; } @Override public int forward() { return forward(0); } @Override public int forward(int cursor) { int newCursor; if (cursor == 0) { newCursor = size(); } else { newCursor = mCursor + cursor; } // Move forward while (hasNext() && mCursor != newCursor) { next(); } dispatcher.trigger(new OnFastForwardComplete(getCurrent())); return mCursor; } @Override public boolean contains(IUndoCommand command) { return mCommands.contains(command); } @Override public int execute(IUndoCommand command) { // this will happen when there is a rewind half way through // and then a new command is pushed which wipes the head // of the stack out down to the currentPosition if (mCursor != size()) { mCommands = mCommands.subList(0, mCursor); } mCommands.add(command); // Execute the command & move pointer forward // if there is an Exception throw, the cursor has not been // touched yet so bailing right now is safe next(); return mCursor; } @Override public String toString() { String output = ""; int count = 0; for (IUndoCommand command : mCommands) { output += count + "" + command + "\n"; count++; } return output; } }