/******************************************************************************* * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * emil.crumhorn@gmail.com - initial API and implementation *******************************************************************************/ package org.eclipse.nebula.widgets.ganttchart.undoredo; import java.util.ArrayList; import java.util.List; import org.eclipse.nebula.widgets.ganttchart.GanttComposite; import org.eclipse.nebula.widgets.ganttchart.undoredo.commands.IUndoRedoCommand; /** * Deals with Undo/Redo events in the chart. Implemented per Command-structure standards. * * @author cre */ public class GanttUndoRedoManager { public static final int STACK_SIZE = 50; private final List _undoRedoEvents; private int _currentIndex; private int _maxStackSize; private final GanttComposite _comp; private final List _listeners; public GanttUndoRedoManager(final GanttComposite parent, final int maxStackSize) { _comp = parent; _undoRedoEvents = new ArrayList(); _listeners = new ArrayList(); _maxStackSize = maxStackSize; } public List getUndoRedoEvents() { return _undoRedoEvents; } /** * Adds a listener to be notified when undo/redo possibilities change * * @param listener */ public void addUndoRedoListener(final IUndoRedoListener listener) { if (!_listeners.contains(listener)) { _listeners.add(listener); } } /** * Removes a listener from being notified when undo/redo possibilities change * * @param listener */ public void removeUndoRedoListener(final IUndoRedoListener listener) { _listeners.remove(listener); } /** * Records an undoable/redoable command * * @param command */ public void record(final IUndoRedoCommand command) { // ensure size etc fixStack(); _undoRedoEvents.add(command); _currentIndex++; // tell listeners a command was added for (int i = 0; i < _listeners.size(); i++) { final IUndoRedoListener listener = (IUndoRedoListener) _listeners.get(i); listener.undoableCommandAdded(command); } updateListeners(); } private void updateListeners() { // notify listeners for (int i = 0; i < _listeners.size(); i++) { final IUndoRedoListener listener = (IUndoRedoListener) _listeners.get(i); listener.canRedoChanged(canRedo()); listener.canUndoChanged(canUndo()); } } /** * Removes all undo/redo events from the stack */ public void clear() { _undoRedoEvents.clear(); _currentIndex = 0; updateListeners(); } /** * Whether an Undo is possible. * * @return true if user can Undo */ public boolean canUndo() { return _currentIndex != 0 && !_undoRedoEvents.isEmpty(); } /** * Undoes the last GanttChart action. * * @return */ public boolean undo() { if (!canUndo()) { return false; } final IUndoRedoCommand command = (IUndoRedoCommand) _undoRedoEvents.get(_currentIndex - 1); command.undo(); _comp.heavyRedraw(); _currentIndex--; if (_currentIndex < 0) { _currentIndex = 0; } updateListeners(); for (int i = 0; i < _listeners.size(); i++) { final IUndoRedoListener listener = (IUndoRedoListener) _listeners.get(i); listener.commandUndone(command); } return true; } /** * Redoes the last GanttChart action. * * @return */ public boolean redo() { if (!canRedo()) { return false; } final IUndoRedoCommand command = (IUndoRedoCommand) _undoRedoEvents.get(_currentIndex); command.redo(); _comp.heavyRedraw(); _currentIndex++; if (_currentIndex > _undoRedoEvents.size()) { _currentIndex = _undoRedoEvents.size(); } updateListeners(); for (int i = 0; i < _listeners.size(); i++) { final IUndoRedoListener listener = (IUndoRedoListener) _listeners.get(i); listener.commandRedone(command); } return true; } /** * Whether a Redo is possible. * * @return true if user can Redo */ public boolean canRedo() { if (_undoRedoEvents.isEmpty()) { return false; } return _currentIndex != _undoRedoEvents.size(); } /** * The current index of where the undo/redo marker is * * @return */ public int getCurrentIndex() { return _currentIndex; } /** * @param currentIndex The current index of where the undo/redo marker is */ public void setCurrentIndex(int currentIndex) { _currentIndex = currentIndex; } /** * Clears up the stack of undo/redo events and keeps its size in check. */ private void fixStack() { final List toRemove = new ArrayList(); // first nuke any items past the current index for (int i = _currentIndex; i < _undoRedoEvents.size(); i++) { toRemove.add(_undoRedoEvents.get(i)); } _undoRedoEvents.removeAll(toRemove); for (int i = 0; i < toRemove.size(); i++) { ((IUndoRedoCommand) toRemove.get(i)).dispose(); } toRemove.clear(); if (_undoRedoEvents.size() > _maxStackSize) { // remove from the front for (int i = 0; i < (_maxStackSize - _undoRedoEvents.size()); i++) { toRemove.add(_undoRedoEvents.get(i)); } } for (int i = 0; i < toRemove.size(); i++) { ((IUndoRedoCommand) toRemove.get(i)).dispose(); } _undoRedoEvents.removeAll(toRemove); } /** * Sets a new max undo/redo sack size, value must be a positive integer or it is ignored. * * @param stackSize new max undo/redo stack size */ public void setMaxStackSize(final int stackSize) { if (stackSize <= 0) { return; } _maxStackSize = stackSize; } }