org.opencms.acacia.client.CmsUndoRedoHandler Maven / Gradle / Ivy
Show all versions of opencms-gwt Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (C) Alkacon Software (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.acacia.client;
import org.opencms.acacia.client.CmsUndoRedoHandler.UndoRedoState;
import org.opencms.acacia.shared.CmsEntity;
import java.util.Stack;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.Timer;
/**
* Handler for the undo redo function.
*/
public final class CmsUndoRedoHandler implements HasValueChangeHandlers {
/** The change types. */
public enum ChangeType {
/** New value added change. */
add,
/** A choice change. */
choice,
/** Value removed change. */
remove,
/** Value sort change. */
sort,
/** A simple value change. */
value
}
/** Representing the undo/redo state. */
public class UndoRedoState {
/** Indicating if there are changes to be re done. */
private boolean m_hasRedo;
/** Indicating if there are changes to be undone. */
private boolean m_hasUndo;
/**
* Constructor.
*
* @param hasUndo if there are changes to be undone
* @param hasRedo if there are changes to be re done
*/
UndoRedoState(boolean hasUndo, boolean hasRedo) {
m_hasUndo = hasUndo;
m_hasRedo = hasRedo;
}
/**
* Returns if there are changes to be re done.
*
* @return true
if there are changes to be re done.
*/
public boolean hasRedo() {
return m_hasRedo;
}
/**
* Returns if there are changes to be undone.
*
* @return true
if there are changes to be undone.
*/
public boolean hasUndo() {
return m_hasUndo;
}
}
/**
* A timer to delay the addition of a change.
*/
protected class ChangeTimer extends Timer {
/** The attribute name. */
private String m_attributeName;
/** The change type. */
private ChangeType m_changeType;
/** The value index. */
private int m_valueIndex;
/** The value path. */
private String m_valuePath;
/**
* Constructor.
*
* @param valuePath the entity value path
* @param attributeName the attribute name
* @param valueIndex the value index
* @param changeType the change type
*/
protected ChangeTimer(String valuePath, String attributeName, int valueIndex, ChangeType changeType) {
m_valuePath = valuePath;
m_attributeName = attributeName;
m_valueIndex = valueIndex;
m_changeType = changeType;
}
/**
* @see com.google.gwt.user.client.Timer#run()
*/
@Override
public void run() {
internalAddChange(m_valuePath, m_attributeName, m_valueIndex, m_changeType);
}
/**
* Checks whether the timer change properties match the given ones.
*
* @param valuePath the entity value path
* @param attributeName the attribute name
* @param valueIndex the value index
*
* @return true
if the timer change properties match the given ones
*/
protected boolean matches(String valuePath, String attributeName, int valueIndex) {
return m_valuePath.equals(valuePath)
&& m_attributeName.equals(attributeName)
&& (m_valueIndex == valueIndex);
}
}
/**
* Representing a change stack entry.
*/
private class Change {
/** The attribute name. */
private String m_attributeName;
/** The entity data. */
private CmsEntity m_entityData;
/** The entity id. */
private String m_entityId;
/** The change type. */
private ChangeType m_type;
/** The value index. */
private int m_valueIndex;
/**
* Constructor.
*
* @param entity the chane entity data
* @param entityId the entity id
* @param attributeName the attribute name
* @param valueIndex the value index
* @param type the change type
*/
Change(CmsEntity entity, String entityId, String attributeName, int valueIndex, ChangeType type) {
m_entityId = entityId;
m_attributeName = attributeName;
m_valueIndex = valueIndex;
m_type = type;
m_entityData = entity;
}
/**
* Returns the attribute name.
*
* @return the attribute name
*/
public String getAttributeName() {
return m_attributeName;
}
/**
* Returns the change entity data.
*
* @return the change entity data
*/
public CmsEntity getEntityData() {
return m_entityData;
}
/**
* Returns the change entity id.
*
* @return the entity id
*/
public String getEntityId() {
return m_entityId;
}
/**
* The change type.
*
* @return the change type
*/
public ChangeType getType() {
return m_type;
}
/**
* Returns the value index.
*
* @return the value index
*/
public int getValueIndex() {
return m_valueIndex;
}
}
/** The change timer delay. */
private static final int CHANGE_TIMER_DELAY = 500;
/** The static instance. */
private static CmsUndoRedoHandler INSTANCE;
/** The ad change timer. */
private ChangeTimer m_changeTimer;
/** The current data state. */
private Change m_current;
/** The editor instance. */
private CmsEditorBase m_editor;
/** The edited entity. */
private CmsEntity m_entity;
/** The event bus. */
private SimpleEventBus m_eventBus;
/** The redo stack. */
private Stack m_redo;
/** The root attribute handler. */
private CmsRootHandler m_rootHandler;
/** The undo stack. */
private Stack m_undo;
/**
* Constructor.
*/
private CmsUndoRedoHandler() {
m_undo = new Stack();
m_redo = new Stack();
}
/**
* Returns the undo redo handler instance.
*
* @return the handler instance
*/
public static CmsUndoRedoHandler getInstance() {
if (INSTANCE == null) {
INSTANCE = new CmsUndoRedoHandler();
}
return INSTANCE;
}
/**
* Adds a change to the undo stack.
*
* @param valuePath the entity value path
* @param attributeName the attribute name
* @param valueIndex the value index
* @param changeType the change type
*/
public void addChange(String valuePath, String attributeName, int valueIndex, ChangeType changeType) {
if (ChangeType.value.equals(changeType)) {
if (m_changeTimer != null) {
if (!m_changeTimer.matches(valuePath, attributeName, valueIndex)) {
// only in case the change properties of the timer do not match the current change,
// add the last change and start a new timer
m_changeTimer.cancel();
m_changeTimer.run();
m_changeTimer = new ChangeTimer(valuePath, attributeName, valueIndex, changeType);
m_changeTimer.schedule(CHANGE_TIMER_DELAY);
}
} else {
m_changeTimer = new ChangeTimer(valuePath, attributeName, valueIndex, changeType);
m_changeTimer.schedule(CHANGE_TIMER_DELAY);
}
} else {
if (m_changeTimer != null) {
m_changeTimer.cancel();
m_changeTimer.run();
}
internalAddChange(valuePath, attributeName, valueIndex, changeType);
}
}
/**
* @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
*/
public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) {
return addHandler(handler, ValueChangeEvent.getType());
}
/**
* Clears the undo/redo stacks and all references.
*/
public void clear() {
m_undo.clear();
m_redo.clear();
m_entity = null;
m_editor = null;
m_rootHandler = null;
}
/**
* @see com.google.gwt.event.shared.HasHandlers#fireEvent(com.google.gwt.event.shared.GwtEvent)
*/
public void fireEvent(GwtEvent> event) {
ensureHandlers().fireEventFromSource(event, this);
}
/**
* Indicates if there are changes to be undone.
*
* @return true
if there are changes to be undone
*/
public boolean hasRedo() {
return !m_redo.isEmpty();
}
/**
* Indicates if there are changes to be undone.
*
* @return true
if there are changes to be undone
*/
public boolean hasUndo() {
return !m_undo.isEmpty();
}
/**
* Initializes the handler to be used for the given entity.
*
* @param entity the edited entity
* @param editor the editor instance
* @param rootHandler the root attribute handler
*/
public void initialize(CmsEntity entity, CmsEditorBase editor, CmsRootHandler rootHandler) {
m_undo.clear();
m_redo.clear();
m_entity = entity;
m_editor = editor;
m_rootHandler = rootHandler;
m_current = new Change(m_entity.cloneEntity(), null, null, 0, null);
fireStateChange();
}
/**
* Indicates if the handler has been initialized.
*
* @return true
if the handler has been initialized
*/
public boolean isIntitalized() {
return m_entity != null;
}
/**
* Re-applies the latest state in the redo stack.
*/
public void redo() {
if (!m_redo.isEmpty()) {
m_undo.push(m_current);
m_current = m_redo.pop();
changeEntityContentValues(
m_current.getEntityData(),
m_current.getEntityId(),
m_current.getAttributeName(),
m_current.getValueIndex(),
m_current.getType());
fireStateChange();
}
}
/**
* Reverts to the latest state in the undo stack.
*/
public void undo() {
if (hasUndo()) {
ChangeType type = m_current.getType();
String entityId = m_current.getEntityId();
String attributeName = m_current.getAttributeName();
int valueIndex = m_current.getValueIndex();
m_redo.push(m_current);
m_current = m_undo.pop();
changeEntityContentValues(m_current.getEntityData(), entityId, attributeName, valueIndex, type);
fireStateChange();
}
}
/**
* Adds this handler to the widget.
*
* @param the type of handler to add
* @param type the event type
* @param handler the handler
* @return {@link HandlerRegistration} used to remove the handler
*/
protected HandlerRegistration addHandler(final H handler, GwtEvent.Type type) {
return ensureHandlers().addHandlerToSource(type, this, handler);
}
/**
* Internally adds a change to the undo stack.
*
* @param valuePath the entity value path
* @param attributeName the attribute name
* @param valueIndex the value index
* @param changeType the change type
*/
void internalAddChange(String valuePath, String attributeName, int valueIndex, ChangeType changeType) {
m_changeTimer = null;
//TODO: keep the IDs, otherwise redo will not work
CmsEntity currentData = m_entity.cloneEntity();
if (!currentData.equals(m_current.getEntityData())) {
m_undo.push(m_current);
m_current = new Change(currentData, valuePath, attributeName, valueIndex, changeType);
m_redo.clear();
fireStateChange();
}
}
/**
* Sets the editor to the given state.
*
* @param newContent the state content
* @param entityId the value path elements
* @param attributeName the attribute name
* @param valueIndex the value index
* @param type the change type
*/
private void changeEntityContentValues(
CmsEntity newContent,
String entityId,
String attributeName,
int valueIndex,
ChangeType type) {
switch (type) {
case value:
CmsAttributeHandler handler = m_rootHandler.getHandlerById(entityId, attributeName);
CmsEntity entity = newContent.getEntityById(entityId);
if ((entity != null) && (entity.getAttribute(attributeName) != null)) {
String value = entity.getAttribute(attributeName).getSimpleValues().get(valueIndex);
if ((handler != null) && handler.hasValueView(valueIndex) && (value != null)) {
handler.changeValue(value, valueIndex);
break;
}
}
//$FALL-THROUGH$
default:
m_editor.rerenderForm(newContent);
}
}
/**
* Lazy initializing the handler manager.
*
* @return the handler manager
*/
private SimpleEventBus ensureHandlers() {
if (m_eventBus == null) {
m_eventBus = new SimpleEventBus();
}
return m_eventBus;
}
/**
* Fires a value change event to indicate the undo/redo state has changed.
*/
private void fireStateChange() {
ValueChangeEvent.fire(this, new UndoRedoState(hasUndo(), hasRedo()));
}
}