org.opencms.ade.contenteditor.client.CmsContentEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.ade.contenteditor.client.CmsContentEditor.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (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.ade.contenteditor.client;

import org.opencms.acacia.client.CmsAttributeHandler;
import org.opencms.acacia.client.CmsEditorBase;
import org.opencms.acacia.client.CmsUndoRedoHandler;
import org.opencms.acacia.client.CmsUndoRedoHandler.UndoRedoState;
import org.opencms.acacia.client.CmsValidationContext;
import org.opencms.acacia.client.CmsValueFocusHandler;
import org.opencms.acacia.client.I_CmsEntityRenderer;
import org.opencms.acacia.client.I_CmsInlineFormParent;
import org.opencms.acacia.client.entity.CmsEntityBackend;
import org.opencms.acacia.shared.CmsEntity;
import org.opencms.acacia.shared.CmsEntityAttribute;
import org.opencms.acacia.shared.CmsTabInfo;
import org.opencms.acacia.shared.CmsType;
import org.opencms.acacia.shared.CmsValidationResult;
import org.opencms.ade.contenteditor.client.css.I_CmsLayoutBundle;
import org.opencms.ade.contenteditor.shared.CmsComplexWidgetData;
import org.opencms.ade.contenteditor.shared.CmsContentDefinition;
import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService;
import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentServiceAsync;
import org.opencms.ade.contenteditor.widgetregistry.client.WidgetRegistry;
import org.opencms.ade.publish.client.CmsPublishDialog;
import org.opencms.ade.publish.shared.CmsPublishOptions;
import org.opencms.gwt.client.CmsCoreProvider;
import org.opencms.gwt.client.rpc.CmsRpcAction;
import org.opencms.gwt.client.rpc.CmsRpcPrefetcher;
import org.opencms.gwt.client.ui.CmsConfirmDialog;
import org.opencms.gwt.client.ui.CmsErrorDialog;
import org.opencms.gwt.client.ui.CmsInfoHeader;
import org.opencms.gwt.client.ui.CmsModelSelectDialog;
import org.opencms.gwt.client.ui.CmsNotification;
import org.opencms.gwt.client.ui.CmsNotification.Type;
import org.opencms.gwt.client.ui.CmsPushButton;
import org.opencms.gwt.client.ui.CmsToggleButton;
import org.opencms.gwt.client.ui.CmsToolbar;
import org.opencms.gwt.client.ui.I_CmsButton;
import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
import org.opencms.gwt.client.ui.I_CmsButton.Size;
import org.opencms.gwt.client.ui.I_CmsConfirmDialogHandler;
import org.opencms.gwt.client.ui.I_CmsModelSelectHandler;
import org.opencms.gwt.client.ui.input.CmsSelectBox;
import org.opencms.gwt.client.util.CmsDebugLog;
import org.opencms.gwt.client.util.CmsDomUtil;
import org.opencms.gwt.client.util.I_CmsSimpleCallback;
import org.opencms.gwt.shared.CmsIconUtil;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.VerticalAlign;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;

/**
 * The content editor.<p>
 */
public final class CmsContentEditor extends CmsEditorBase {

    /**
     * CmsEntity change handler to watch for changes within given scopes and call the editor change handlers accordingly.<p>
     */
    class EditorChangeHandler implements ValueChangeHandler<CmsEntity> {

        /** The scope values. */
        Map<String, String> m_scopeValues;

        /** The change handler registration. */
        private HandlerRegistration m_handlerRegistration;

        /** The observed entity. */
        private CmsEntity m_observerdEntity;

        /**
         * Constructor.<p>
         *
         * @param entity the entity to observe
         * @param changeScopes the value scopes to watch for changes
         */
        public EditorChangeHandler(CmsEntity entity, Collection<String> changeScopes) {

            m_observerdEntity = entity;
            m_handlerRegistration = entity.addValueChangeHandler(this);
            m_scopeValues = new HashMap<String, String>();
            for (String scope : changeScopes) {
                m_scopeValues.put(scope, CmsContentDefinition.getValueForPath(m_observerdEntity, scope));
            }
        }

        /**
         * Removes this observer from the entities change handler registration and clears registered listeners.<p>
         */
        public void clear() {

            if (m_handlerRegistration != null) {
                m_handlerRegistration.removeHandler();
                m_handlerRegistration = null;
            }
            m_scopeValues.clear();
            m_observerdEntity = null;
        }

        /**
         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
         */
        public void onValueChange(ValueChangeEvent<CmsEntity> event) {

            final CmsEntity entity = event.getValue();
            m_needsValueChangeProcessing = true;
            Scheduler.get().scheduleFinally(new ScheduledCommand() {

                public void execute() {

                    if (m_needsValueChangeProcessing) {
                        m_needsValueChangeProcessing = false;
                        Set<String> changedScopes = new HashSet<String>();
                        for (String scope : m_scopeValues.keySet()) {
                            String scopeValue = CmsContentDefinition.getValueForPath(entity, scope);
                            String previousValue = m_scopeValues.get(scope);
                            if (((scopeValue != null) && !scopeValue.equals(previousValue))
                                    || ((scopeValue == null) && (previousValue != null))) {
                                // the value within this scope has changed, notify all listeners

                                changedScopes.add(scope);
                                m_scopeValues.put(scope, scopeValue);
                            }
                        }
                        if (!changedScopes.isEmpty()) {
                            callEditorChangeHandlers(changedScopes);
                        }

                    }
                }
            });
        }
    }

    /** The add change listener method name. */
    private static final String ADD_CHANGE_LISTENER_METHOD = "cmsAddEntityChangeListener";

    /** The get current entity method name. */
    private static final String GET_CURRENT_ENTITY_METHOD = "cmsGetCurrentEntity";

    /** The in-line editor instance. */
    private static CmsContentEditor INSTANCE;

    /** Flag indicating that an AJAX call for the editor change handler is running. */
    protected boolean m_callingChangeHandlers;

    /** The current content locale. */
    protected String m_locale;

    /** The on close call back. */
    protected Command m_onClose;

    /** The edit tool-bar. */
    protected CmsToolbar m_toolbar;

    /** Flag indicating that we need to collect changed values. */
    boolean m_needsValueChangeProcessing;

    /** Value of the auto-unlock option from the configuration. */
    private boolean m_autoUnlock;

    /** The available locales. */
    private Map<String, String> m_availableLocales;

    /** The form editing base panel. */
    private FlowPanel m_basePanel;

    /** The cancel button. */
    private CmsPushButton m_cancelButton;

    /** The id's of the changed entities. */
    private Set<String> m_changedEntityIds;

    /** Changed scopes which still haven't been sent to the editor change handlers. */
    private Set<String> m_changedScopes = new HashSet<String>();

    /** The window closing handler registration. */
    private HandlerRegistration m_closingHandlerRegistration;

    /** The locales present within the edited content. */
    private Set<String> m_contentLocales;

    /** The editor context. */
    private CmsEditorContext m_context;

    /** The copy locale button. */
    private CmsPushButton m_copyLocaleButton;

    /** The loaded content definitions by locale. */
    private Map<String, CmsContentDefinition> m_definitions;

    /** The entities to delete. */
    private Set<String> m_deletedEntities;

    /** The delete locale button. */
    private CmsPushButton m_deleteLocaleButton;

    /** Flag indicating the resource needs to removed on cancel. */
    private boolean m_deleteOnCancel;

    /** The entity value change handler calling configured editor change handlers. */
    private EditorChangeHandler m_editorChangeHandler;

    /** The entity observer instance. */
    private CmsEntityObserver m_entityObserver;

    /** The hide help bubbles button. */
    private CmsToggleButton m_hideHelpBubblesButton;

    /** Flag which indicate whether the directedit parameter was set to true when loading the editor. */
    private boolean m_isDirectEdit;

    /** Flag indicating the editor was opened as the stand alone version, not from within any other module. */
    private boolean m_isStandAlone;

    /** The locale select box. */
    private CmsSelectBox m_localeSelect;

    /** The open form button. */
    private CmsPushButton m_openFormButton;

    /** The event preview handler registration. */
    private HandlerRegistration m_previewHandlerRegistration;

    /** The publish button. */
    private CmsPushButton m_publishButton;

    /** The redo button. */
    private CmsPushButton m_redoButton;

    /** The registered entity id's. */
    private Set<String> m_registeredEntities;

    /** The resource type name. */
    private String m_resourceTypeName;

    /** The save button. */
    private CmsPushButton m_saveButton;

    /** The save and exit button. */
    private CmsPushButton m_saveExitButton;

    /** The content service. */
    private I_CmsContentServiceAsync m_service;

    /** The resource site path. */
    private String m_sitePath;

    /** The tab informations for this form. */
    private List<CmsTabInfo> m_tabInfos;

    /** The resource title. */
    private String m_title;

    /** The undo button. */
    private CmsPushButton m_undoButton;

    /** The undo redo event handler registration. */
    private HandlerRegistration m_undoRedoHandlerRegistration;

    /**
     * Constructor.<p>
     */
    private CmsContentEditor() {

        super((I_CmsContentServiceAsync) GWT.create(I_CmsContentService.class), new CmsDefaultWidgetService());
        m_service = (I_CmsContentServiceAsync) super.getService();
        String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.contenteditor.CmsContentService.gwt");
        ((ServiceDefTarget) m_service).setServiceEntryPoint(serviceUrl);
        getWidgetService().setWidgetFactories(WidgetRegistry.getInstance().getWidgetFactories());
        for (I_CmsEntityRenderer renderer : WidgetRegistry.getInstance().getRenderers()) {
            getWidgetService().addRenderer(renderer);
        }
        // set the acacia editor message bundle
        setDictionary(Messages.get().getDictionary());
        I_CmsLayoutBundle.INSTANCE.editorCss().ensureInjected();
        m_changedEntityIds = new HashSet<String>();
        m_registeredEntities = new HashSet<String>();
        m_availableLocales = new HashMap<String, String>();
        m_contentLocales = new HashSet<String>();
        m_deletedEntities = new HashSet<String>();
        m_definitions = new HashMap<String, CmsContentDefinition>();
        addValidationChangeHandler(new ValueChangeHandler<CmsValidationContext>() {

            public void onValueChange(ValueChangeEvent<CmsValidationContext> event) {

                handleValidationChange(event.getValue());
            }
        });
    }

    /**
     * Adds an entity change listener.<p>
     *
     * @param changeListener the change listener
     * @param changeScope the change scope
     */
    public static void addEntityChangeListener(I_CmsEntityChangeListener changeListener, String changeScope) {

        CmsDebugLog.getInstance().printLine("trying to ad change listener for scope: " + changeScope);
        if ((INSTANCE == null) || (INSTANCE.m_entityObserver == null)) {
            CmsDebugLog.getInstance().printLine("handling external registration");
            if (isObserverExported()) {
                CmsDebugLog.getInstance().printLine("registration is available");
                try {
                    addNativeListener(changeListener, changeScope);
                } catch (Exception e) {

                    CmsDebugLog.getInstance()
                            .printLine("Exception occured during listener registration" + e.getMessage());
                }
            } else {
                throw new RuntimeException("Editor is not initialized yet.");
            }
        } else {
            INSTANCE.m_entityObserver.addEntityChangeListener(changeListener, changeScope);
        }
    }

    /**
     * Returns the currently edited entity.<p>
     *
     * @return the currently edited entity
     */
    public static CmsEntity getEntity() {

        if ((INSTANCE == null) || (INSTANCE.m_entityObserver == null)) {
            CmsDebugLog.getInstance().printLine("handling external registration");
            if (isObserverExported()) {
                return CmsEntityBackend.createFromNativeWrapper(nativeGetEntity());
            } else {
                throw new RuntimeException("Editor is not initialized yet.");
            }
        } else {
            return INSTANCE.getCurrentEntity();
        }
    }

    /**
     * Returns the in-line editor instance.<p>
     *
     * @return the in-line editor instance
     */
    public static CmsContentEditor getInstance() {

        if (INSTANCE == null) {
            INSTANCE = new CmsContentEditor();
        }
        return INSTANCE;
    }

    /**
     * Returns if the given element or it's descendants are inline editable.<p>
     *
     * @param element the element
     *
     * @return <code>true</code> if the element has editable descendants
     */
    public static boolean hasEditable(Element element) {

        NodeList<Element> children = CmsDomUtil.querySelectorAll("[property^=\"opencms://\"]", element);
        return (children != null) && (children.getLength() > 0);
    }

    /**
     * Checks whether the given element is annotated for inline editing.<p>
     *
     * @param element the element to check
     *
     * @return <code>true</code> if the given element is annotated for inline editing
     */
    public static boolean isEditable(Element element) {

        String property = element.getAttribute("property");
        return (property != null) && property.startsWith("opencms://");
    }

    /**
     * Replaces the id's within about attributes of the given element and all it's children.<p>
     *
     * @param element the element
     * @param oldId the old id
     * @param newId the new id
     */
    public static void replaceResourceIds(Element element, String oldId, String newId) {

        NodeList<Element> children = CmsDomUtil
                .querySelectorAll("[property^=\"opencms://\"][about*=\"" + oldId + "\"]", element);
        if (children.getLength() > 0) {
            for (int i = 0; i < children.getLength(); i++) {
                Element child = children.getItem(i);
                String about = child.getAttribute("about");
                about = about.replace(oldId, newId);
                child.setAttribute("about", about);
            }
        }
    }

    /**
     * Sets all annotated child elements editable.<p>
     *
     * @param element the element
     * @param serverId the editable resource structure id
     * @param editable <code>true</code> to enable editing
     *
     * @return <code>true</code> if the element had editable elements
     */
    public static boolean setEditable(Element element, String serverId, boolean editable) {

        I_CmsLayoutBundle.INSTANCE.editorCss().ensureInjected();
        NodeList<Element> children = CmsDomUtil
                .querySelectorAll("[property^=\"opencms://\"][about*=\"" + serverId + "\"]", element);
        if (children.getLength() > 0) {
            for (int i = 0; i < children.getLength(); i++) {
                Element child = children.getItem(i);
                if (editable) {
                    child.addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().inlineEditable());
                } else {
                    child.removeClassName(I_CmsLayoutBundle.INSTANCE.editorCss().inlineEditable());
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Adds the change listener.<p>
     *
     * @param changeListener the change listener
     * @param changeScope the change scope
     */
    static native void addNativeListener(I_CmsEntityChangeListener changeListener, String changeScope)/*-{
                                                                                                      var instance = changeListener;
                                                                                                      var nat = {
                                                                                                      onChange : function(entity) {
                                                                                                      var cmsEntity = @org.opencms.acacia.client.entity.CmsEntityBackend::createFromNativeWrapper(Lcom/google/gwt/core/client/JavaScriptObject;)(entity);
                                                                                                      instance.@org.opencms.ade.contenteditor.client.I_CmsEntityChangeListener::onEntityChange(Lorg/opencms/acacia/shared/CmsEntity;)(cmsEntity);
                                                                                                      }
                                                                                                      }
                                                                                                      var method = $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD];
                                                                                                      if (typeof method == 'function') {
                                                                                                      method(nat, changeScope);
                                                                                                      }
                                                                                                      }-*/;

    /**
     * Checks whether the add entity change listener method has been exported.<p>
     *
     * @return <code>true</code> if the add entity change listener method has been exported
     */
    private static native boolean isObserverExported()/*-{
                                                      var method = $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD];
                                                      if (typeof method == 'function') {
                                                      return true;
                                                      } else {
                                                      return false;
                                                      }
                                                      }-*/;

    /**
     * Returns the current entity.<p>
     *
     * @return the current entity
     */
    private static native JavaScriptObject nativeGetEntity()/*-{
                                                            return $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD]
                                                            ();
                                                            }-*/;

    /**
     * Closes the editor.<p>
     * May be used from outside the editor module.<p>
     */
    public void closeEditor() {

        if (m_saveButton != null) {
            if (m_saveButton.isEnabled()) {
                CmsConfirmDialog dialog = new CmsConfirmDialog(
                        org.opencms.gwt.client.Messages.get()
                                .key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0),
                        Messages.get().key(Messages.GUI_CONFIRM_LEAVING_EDITOR_0));
                dialog.setHandler(new I_CmsConfirmDialogHandler() {

                    public void onClose() {

                        cancelEdit();
                    }

                    public void onOk() {

                        saveAndExit();
                    }
                });
                dialog.center();
            } else {
                cancelEdit();
            }
        }
    }

    /**
     * Bypasses a focus bug in IE which can happen if the user opens the HTML code editor from the WYSIWYG editor.<p>
     *
     * The next time they open the editor form from the same container page, the user may be unable to focus on any input
     * fields. To prevent this, we create a dummy input field outside the visible screen region and focus it when opening
     * the editor.
     */
    public void fixFocus() {

        TextBox invisibleTextBox = new TextBox();
        Style style = invisibleTextBox.getElement().getStyle();
        style.setPosition(Position.FIXED);
        style.setLeft(-99999, Unit.PX);
        style.setTop(-99999, Unit.PX);
        m_basePanel.add(invisibleTextBox);
        // base panel is already attached at this point, so we can just set the focus
        invisibleTextBox.setFocus(true);
    }

    /**
     * @see org.opencms.acacia.client.CmsEditorBase#getService()
     */
    @Override
    public I_CmsContentServiceAsync getService() {

        return m_service;
    }

    /**
     * Loads the content definition for the given entity and executes the callback on success.<p>
     *
     * @param entityId the entity id
     * @param editedEntity the currently edited entity
     * @param callback the callback
     */
    public void loadDefinition(final String entityId, final CmsEntity editedEntity,
            final I_CmsSimpleCallback<CmsContentDefinition> callback) {

        CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {

            @Override
            public void execute() {

                start(0, true);
                getService().loadDefinition(entityId, editedEntity, getSkipPaths(), this);
            }

            @Override
            protected void onResponse(final CmsContentDefinition result) {

                registerContentDefinition(result);
                WidgetRegistry.getInstance().registerExternalWidgets(result.getExternalWidgetConfigurations(),
                        new Command() {

                            public void execute() {

                                stop(false);
                                callback.execute(result);
                            }
                        });
            }
        };
        action.execute();
    }

    /**
     * Loads the content definition for the given entity and executes the callback on success.<p>
     *
     * @param entityId the entity id
     * @param newLink the new link
     * @param modelFileId  the model file id
     * @param postCreateHandler the post-create handler class name (optional)
     * @param mode the content creation mode
     * @param mainLocale the main language to copy in case the element language node does not exist yet
     * @param callback the callback
     */
    public void loadInitialDefinition(final String entityId, final String newLink, final CmsUUID modelFileId,
            final String postCreateHandler, final String mode, final String mainLocale,
            final I_CmsSimpleCallback<CmsContentDefinition> callback) {

        CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {

            @Override
            public void execute() {

                start(0, true);
                getService().loadInitialDefinition(entityId, newLink, modelFileId, CmsCoreProvider.get().getUri(),
                        mainLocale, postCreateHandler, mode, this);
            }

            @Override
            protected void onResponse(final CmsContentDefinition result) {

                if (result.isModelInfo()) {
                    stop(false);
                    callback.execute(result);
                } else {
                    registerContentDefinition(result);
                    WidgetRegistry.getInstance().registerExternalWidgets(result.getExternalWidgetConfigurations(),
                            new Command() {

                                public void execute() {

                                    stop(false);
                                    callback.execute(result);
                                }
                            });
                }
            }
        };
        action.execute();
    }

    /**
     * Loads the content definition for the given entity and executes the callback on success.<p>
     *
     * @param entityId the entity id
     * @param editedEntity the currently edited entity
     * @param callback the callback
     */
    public void loadNewDefinition(final String entityId, final CmsEntity editedEntity,
            final I_CmsSimpleCallback<CmsContentDefinition> callback) {

        CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {

            @Override
            public void execute() {

                start(0, true);
                getService().loadNewDefinition(entityId, editedEntity, getSkipPaths(), this);
            }

            @Override
            protected void onResponse(final CmsContentDefinition result) {

                registerContentDefinition(result);
                WidgetRegistry.getInstance().registerExternalWidgets(result.getExternalWidgetConfigurations(),
                        new Command() {

                            public void execute() {

                                stop(false);
                                callback.execute(result);
                            }
                        });
            }
        };
        action.execute();
    }

    /**
     * Opens the content editor dialog.<p>
     *
     * @param context the editor context
     * @param locale the content locale
     * @param elementId the element id
     * @param newLink the new link
     * @param modelFileId the model file id
     * @param postCreateHandler the post-create handler class (optional)
     * @param mode the content creation mode
     * @param mainLocale the main language to copy in case the element language node does not exist yet
     * @param onClose the command executed on dialog close
     */
    public void openFormEditor(final CmsEditorContext context, String locale, String elementId, String newLink,
            CmsUUID modelFileId, String postCreateHandler, String mode, String mainLocale, Command onClose) {

        m_onClose = onClose;
        initEventPreviewHandler();
        CmsUUID structureId = new CmsUUID(elementId);
        // make sure the resource is locked, if we are not creating a new one
        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink) || CmsCoreProvider.get().lock(structureId)) {
            loadInitialDefinition(CmsContentDefinition.uuidToEntityId(structureId, locale), newLink, modelFileId,
                    mode, postCreateHandler, mainLocale, new I_CmsSimpleCallback<CmsContentDefinition>() {

                        public void execute(CmsContentDefinition contentDefinition) {

                            if (contentDefinition.isModelInfo()) {
                                openModelSelectDialog(context, contentDefinition);
                            } else {
                                initEditor(context, contentDefinition, null, false, null);
                            }
                        }
                    });
        } else {
            showLockedResourceMessage();
        }

    }

    /**
     * Renders the in-line editor for the given element.<p>
     *
     * @param context the editor context
     * @param elementId the element id
     * @param locale the content locale
     * @param panel the element panel
     * @param mainLocale the main language to copy in case the element language node does not exist yet
     * @param onClose the command to execute on close
     */
    public void openInlineEditor(final CmsEditorContext context, CmsUUID elementId, String locale,
            final I_CmsInlineFormParent panel, final String mainLocale, Command onClose) {

        initEventPreviewHandler();
        String entityId = CmsContentDefinition.uuidToEntityId(elementId, locale);
        m_locale = locale;
        m_onClose = onClose;
        if (CmsCoreProvider.get().lock(elementId)) {
            loadInitialDefinition(entityId, null, null, null, null, mainLocale,
                    new I_CmsSimpleCallback<CmsContentDefinition>() {

                        public void execute(CmsContentDefinition contentDefinition) {

                            initEditor(context, contentDefinition, panel, true, mainLocale);
                        }
                    });
        } else {
            showLockedResourceMessage();
        }
    }

    /**
     * Opens the form based editor. Used within the stand alone acacia/editor.jsp.<p>
     *
     * @param context the editor context
     */
    public void openStandAloneFormEditor(final CmsEditorContext context) {

        initEventPreviewHandler();
        final CmsContentDefinition definition;
        try {
            definition = (CmsContentDefinition) CmsRpcPrefetcher.getSerializedObjectFromDictionary(getService(),
                    I_CmsContentService.DICT_CONTENT_DEFINITION);
        } catch (SerializationException e) {
            RootPanel.get().add(new Label(e.getMessage()));
            return;
        }
        m_isStandAlone = true;
        if (definition.isModelInfo()) {
            openModelSelectDialog(context, definition);
        } else {
            if (CmsCoreProvider.get().lock(CmsContentDefinition.entityIdToUuid(definition.getEntityId()))) {

                registerContentDefinition(definition);
                // register all external widgets
                WidgetRegistry.getInstance().registerExternalWidgets(definition.getExternalWidgetConfigurations(),
                        new Command() {

                            public void execute() {

                                initEditor(context, definition, null, false, null);
                            }
                        });

            } else {
                showLockedResourceMessage();
            }
        }
    }

    /**
     * Registers a deep copy of the source entity with the given target entity id.<p>
     *
     * @param sourceEntityId the source entity id
     * @param targetEntityId the target entity id
     */
    public void registerClonedEntity(String sourceEntityId, String targetEntityId) {

        CmsEntityBackend.getInstance().getEntity(sourceEntityId).createDeepCopy(targetEntityId);
    }

    /**
     * Registers the given content definition.<p>
     *
     * @param definition the content definition
     */
    public void registerContentDefinition(CmsContentDefinition definition) {

        getWidgetService().addConfigurations(definition.getConfigurations());
        CmsType baseType = definition.getTypes().get(definition.getEntityTypeName());
        m_entityBackend.registerTypes(baseType, definition.getTypes());
        for (CmsEntity entity : definition.getEntities().values()) {
            CmsEntity previousValue = m_entityBackend.getEntity(entity.getId());
            if (previousValue != null) {
                m_entityBackend.changeEntityContentValues(previousValue, entity);
            } else {
                m_entityBackend.registerEntity(entity);
                m_registeredEntities.add(entity.getId());
            }
        }
        for (Map.Entry<String, CmsComplexWidgetData> entry : definition.getComplexWidgetData().entrySet()) {
            String attrName = entry.getKey();
            CmsComplexWidgetData widgetData = entry.getValue();
            getWidgetService().registerComplexWidgetAttribute(attrName, widgetData.getRendererName(),
                    widgetData.getConfiguration());
        }
    }

    /**
     * Saves the given entities.<p>
     *
     * @param clearOnSuccess <code>true</code> to clear the VIE instance on success
     * @param callback the call back command
     */
    public void saveAndDeleteEntities(final boolean clearOnSuccess, final Command callback) {

        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
        saveAndDeleteEntities(entity, new ArrayList<String>(m_deletedEntities), clearOnSuccess, callback);
    }

    /**
     * Saves the given entities.<p>
     *
     * @param lastEditedEntity the last edited entity
     * @param deletedEntites the deleted entity id's
     * @param clearOnSuccess <code>true</code> to clear the VIE instance on success
     * @param callback the call back command
     */
    public void saveAndDeleteEntities(final CmsEntity lastEditedEntity, final List<String> deletedEntites,
            final boolean clearOnSuccess, final Command callback) {

        CmsRpcAction<CmsValidationResult> asyncCallback = new CmsRpcAction<CmsValidationResult>() {

            @Override
            public void execute() {

                start(200, true);
                getService().saveAndDeleteEntities(lastEditedEntity, deletedEntites, getSkipPaths(), m_locale,
                        clearOnSuccess, this);
            }

            @Override
            protected void onResponse(CmsValidationResult result) {

                stop(false);
                if ((result != null) && result.hasErrors()) {
                    showValidationErrorDialog(result);
                } else {
                    callback.execute();
                    if (clearOnSuccess) {
                        destroyForm(true);
                    }
                }
            }
        };
        asyncCallback.execute();
    }

    /**
     * Saves a value in an Xml content.<p>
     *
     * @param contentId the structure id of the content
     * @param contentPath the xpath for which to set the value
     * @param locale the locale for which to set the value
     * @param value the new value
     * @param asyncCallback the callback for the result
     */
    public void saveValue(final String contentId, final String contentPath, final String locale, final String value,
            final AsyncCallback<String> asyncCallback) {

        CmsRpcAction<String> action = new CmsRpcAction<String>() {

            @Override
            public void execute() {

                start(0, false);
                getService().saveValue(contentId, contentPath, locale, value, this);

            }

            @Override
            protected void onResponse(String result) {

                stop(false);
                asyncCallback.onSuccess(result);

            }
        };
        action.execute();
    }

    /**
     * Sets the show editor help flag to the user session.<p>
     *
     * @param show the show editor help flag
     */
    public void setShowEditorHelp(final boolean show) {

        CmsCoreProvider.get().setShowEditorHelp(show);
    }

    /**
     * Removes the given entity from the entity VIE store.<p>
     *
     * @param entityId the entity id
     */
    public void unregistereEntity(String entityId) {

        CmsEntityBackend.getInstance().removeEntity(entityId);
    }

    /**
     * @see org.opencms.acacia.client.CmsEditorBase#clearEditor()
     */
    @Override
    protected void clearEditor() {

        super.clearEditor();
        m_context = null;
        if (m_undoRedoHandlerRegistration != null) {
            m_undoRedoHandlerRegistration.removeHandler();
        }
        if (m_toolbar != null) {
            m_toolbar.removeFromParent();
            m_toolbar = null;
        }
        m_cancelButton = null;
        m_localeSelect = null;
        m_deleteLocaleButton = null;
        m_copyLocaleButton = null;
        m_openFormButton = null;
        m_saveButton = null;
        m_onClose = null;
        m_locale = null;
        if (m_basePanel != null) {
            m_basePanel.removeFromParent();
            m_basePanel = null;
        }
        if (m_entityObserver != null) {
            m_entityObserver.clear();
            m_entityObserver = null;
        }
        m_changedEntityIds.clear();
        m_registeredEntities.clear();
        m_availableLocales.clear();
        m_contentLocales.clear();
        m_deletedEntities.clear();
        m_definitions.clear();
        m_title = null;
        m_sitePath = null;
        m_resourceTypeName = null;
        if (m_closingHandlerRegistration != null) {
            m_closingHandlerRegistration.removeHandler();
            m_closingHandlerRegistration = null;
        }
        if (m_isStandAlone) {
            closeEditorWidow();
        } else {
            RootPanel.getBodyElement().getParentElement().getStyle().clearOverflow();
        }
        if (m_previewHandlerRegistration != null) {
            m_previewHandlerRegistration.removeHandler();
            m_previewHandlerRegistration = null;
        }
    }

    /**
     * Gets the editor context.<p>
     *
     * @return the editor context
     */
    protected CmsEditorContext getContext() {

        return m_context;
    }

    /**
     * @see org.opencms.acacia.client.CmsEditorBase#getContextUri()
     */
    @Override
    protected String getContextUri() {

        return CmsCoreProvider.get().getUri();
    }

    /**
     * Gets the entity id.<p>
     *
     * @return the entity id
     */
    protected String getEntityId() {

        return m_entityId;
    }

    /**
     * @see org.opencms.acacia.client.CmsEditorBase#getHtmlContextInfo()
     */
    @Override
    protected String getHtmlContextInfo() {

        return m_context.getHtmlContextInfo();
    }

    /**
     * Returns the paths to be skipped when synchronizing locale independent fields.<p>
     *
     * @return the paths to be skipped when synchronizing locale independent fields
     */
    protected Collection<String> getSkipPaths() {

        return ((CmsDefaultWidgetService) getWidgetService()).getSkipPaths();
    }

    /**
     * Adds a content definition to the internal store.<p>
     *
     * @param definition the definition to add
     */
    void addContentDefinition(CmsContentDefinition definition) {

        m_definitions.put(definition.getLocale(), definition);
        m_contentLocales.add(definition.getLocale());
    }

    /**
     * Calls the editor change handlers.<p>
     *
     * @param changedScopes the changed content value scopes
     */
    void callEditorChangeHandlers(final Set<String> changedScopes) {

        m_changedScopes.addAll(changedScopes);
        if (!m_callingChangeHandlers && (m_changedScopes.size() > 0)) {
            m_callingChangeHandlers = true;
            final Set<String> scopesToSend = new HashSet<String>(m_changedScopes);
            m_changedScopes.clear();
            final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
            final org.opencms.acacia.shared.CmsEntity currentState = entity.createDeepCopy(m_entityId);
            CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {

                @Override
                public void execute() {

                    start(200, true);
                    getService().callEditorChangeHandlers(getEntityId(), currentState, getSkipPaths(), scopesToSend,
                            this);
                }

                @Override
                public void onFailure(Throwable t) {

                    m_callingChangeHandlers = false;
                    super.onFailure(t);

                }

                @Override
                protected void onResponse(CmsContentDefinition result) {

                    m_callingChangeHandlers = false;
                    stop(false);
                    updateEditorValues(currentState, result.getEntity());
                    callEditorChangeHandlers(new HashSet<String>());
                }
            };
            action.execute();
        }
    }

    /**
     * Cancels the editing process.<p>
     */
    void cancelEdit() {

        // store the scroll position
        int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop();
        setEditorState(false);
        unlockResource();
        if (m_onClose != null) {
            m_onClose.execute();
        }
        destroyForm(true);
        clearEditor();
        // restore the scroll position
        RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop);
    }

    /**
     * Checks if the content is valid and sends a notification if not.<p>
     * This will not trigger a validation run, but will use the latest state.<p>
     *
     * @return <code>true</code> in case there are no validation issues
     */
    boolean checkValidation() {

        boolean result;
        if (m_changedEntityIds.isEmpty()) {
            result = true;
        } else if (m_saveButton.isEnabled()) {
            result = true;
        } else {
            result = false;
            CmsNotification.get().send(Type.ERROR, m_saveButton.getDisabledReason());
        }
        return result;
    }

    /**
     * Asks the user to confirm resetting all changes.<p>
     */
    void confirmCancel() {

        if (m_saveButton.isEnabled()) {
            CmsConfirmDialog dialog = new CmsConfirmDialog(
                    org.opencms.gwt.client.Messages.get()
                            .key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0),
                    org.opencms.gwt.client.Messages.get()
                            .key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TEXT_0));
            dialog.setHandler(new I_CmsConfirmDialogHandler() {

                public void onClose() {

                    // nothing to do
                }

                public void onOk() {

                    cancelEdit();
                }
            });
            dialog.center();
        } else {
            cancelEdit();
        }
    }

    /**
     * Opens the confirm delete locale dialog.<p>
     */
    void confirmDeleteLocale() {

        CmsConfirmDialog dialog = new CmsConfirmDialog(
                Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TITLE_0),
                Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TEXT_0));
        dialog.setHandler(new I_CmsConfirmDialogHandler() {

            public void onClose() {

                // nothing to do
            }

            public void onOk() {

                deleteCurrentLocale();
            }
        });
        dialog.center();
    }

    /**
     * Copies the current entity values to the given locales.<p>
     *
     * @param targetLocales the target locales
     */
    void copyLocales(final Set<String> targetLocales) {

        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {

            @Override
            public void execute() {

                start(200, true);
                getService().copyLocale(targetLocales, entity, this);
            }

            @Override
            protected void onResponse(Void result) {

                stop(false);
            }
        };
        action.execute();
        for (String targetLocale : targetLocales) {
            String targetId = getIdForLocale(targetLocale);
            if (!m_entityId.equals(targetId)) {
                if (m_registeredEntities.contains(targetId)) {
                    unregistereEntity(targetId);
                }
                registerClonedEntity(m_entityId, targetId);
                m_registeredEntities.add(targetId);
                m_changedEntityIds.add(targetId);
                m_contentLocales.add(targetLocale);
                m_deletedEntities.remove(targetId);
                enableSave();
            }
        }
        initLocaleSelect();
    }

    /**
     * Deferrers the save action to the end of the browser event queue.<p>
     */
    void deferSave() {

        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            public void execute() {

                save();
            }
        });
    }

    /**
     * Deferrers the save and exit action to the end of the browser event queue.<p>
     */
    void deferSaveAndExit() {

        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            public void execute() {

                saveAndExit();
            }
        });
    }

    /**
     * Deletes the current locale.<p>
     */
    void deleteCurrentLocale() {

        // there has to remain at least one content locale
        if (m_contentLocales.size() > 1) {
            String deletedLocale = m_locale;
            m_contentLocales.remove(deletedLocale);
            m_registeredEntities.remove(m_entityId);
            m_changedEntityIds.remove(m_entityId);
            m_deletedEntities.add(m_entityId);
            getValidationHandler().getValidationContext().removeEntityId(m_entityId);
            unregistereEntity(m_entityId);
            enableSave();
            String nextLocale = null;
            if (m_registeredEntities.isEmpty()) {
                nextLocale = m_contentLocales.iterator().next();
            } else {
                nextLocale = CmsContentDefinition.getLocaleFromId(m_registeredEntities.iterator().next());
            }
            switchLocale(nextLocale);
        }
    }

    /**
     * Disables the save buttons with the given message.<p>
     *
     * @param message the disabled message
     */
    void disableSave(String message) {

        m_saveButton.disable(message);
        m_saveExitButton.disable(message);
    }

    /**
     * Leaves the editor saving the content if necessary.<p>
     */
    void exitWithSaving() {

        if (checkValidation()) {
            if (m_saveExitButton.isEnabled()) {
                saveAndExit();
            } else {
                cancelEdit();
            }
        }
    }

    /**
     * Handles validation changes.<p>
     *
     * @param validationContext the changed validation context
     */
    void handleValidationChange(CmsValidationContext validationContext) {

        if (validationContext.hasValidationErrors()) {
            String locales = "";
            for (String id : validationContext.getInvalidEntityIds()) {
                if (locales.length() > 0) {
                    locales += ", ";
                }
                String locale = CmsContentDefinition.getLocaleFromId(id);
                if (m_availableLocales.containsKey(locale)) {
                    locales += m_availableLocales.get(locale);
                }
            }
            disableSave(Messages.get().key(Messages.GUI_TOOLBAR_VALIDATION_ERRORS_1, locales));
        } else if (!m_changedEntityIds.isEmpty()) {
            enableSave();
        }
    }

    /**
     * Hides the editor help bubbles.<p>
     *
     * @param hide <code>true</code> to hide the help bubbles
     */
    void hideHelpBubbles(boolean hide) {

        setShowEditorHelp(!hide);
        CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), hide);
        if (!hide) {
            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0));
        } else {
            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0));
        }
    }

    /**
     * Initializes the editor.<p>
     *
     * @param context the editor context
     * @param contentDefinition the content definition
     * @param formParent the inline form parent panel, used for inline editing only
     * @param inline <code>true</code> to render the editor for inline editing
     * @param mainLocale the main language to copy in case the element language node does not exist yet
     */
    void initEditor(CmsEditorContext context, CmsContentDefinition contentDefinition,
            I_CmsInlineFormParent formParent, boolean inline, String mainLocale) {

        m_context = context;
        m_locale = contentDefinition.getLocale();
        m_entityId = contentDefinition.getEntityId();
        m_deleteOnCancel = contentDefinition.isDeleteOnCancel();
        m_autoUnlock = contentDefinition.isAutoUnlock();
        m_isDirectEdit = contentDefinition.isDirectEdit();

        initClosingHandler();
        setContentDefinition(contentDefinition);
        initToolbar();
        if (inline && (formParent != null)) {
            if ((mainLocale != null) && (CmsDomUtil.querySelector("[about^='" + m_entityId + "']",
                    formParent.getElement()) == null)) {
                // in case a main locale is given and there are not any HTML elements attributed to the current entity id,
                // check if the content was rendered for the main locale
                CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId);
                String mainLocaleEntityId = CmsContentDefinition.uuidToEntityId(structureId, mainLocale);
                NodeList<Element> elements = CmsDomUtil.querySelectorAll("[about^='" + mainLocaleEntityId + "']",
                        formParent.getElement());
                if (elements.getLength() > 0) {
                    for (int i = 0; i < elements.getLength(); i++) {
                        Element element = elements.getItem(i);
                        element.setAttribute("about",
                                element.getAttribute("about").replace(mainLocaleEntityId, m_entityId));
                    }
                }

            }
            initEditOverlay(formParent.getElement());
            addOverlayClickHandler(new ClickHandler() {

                public void onClick(ClickEvent event) {

                    exitWithSaving();
                }
            });
            m_hideHelpBubblesButton.setVisible(false);
            setNativeResourceInfo(m_sitePath, m_locale);
            initEntityObserver();
            if (m_definitions.get(m_locale).hasEditorChangeHandlers()) {
                initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes());
            }
            renderInlineEntity(m_entityId, formParent);
        } else {
            initFormPanel();
            renderFormContent();
            fixFocus();
        }
        if (contentDefinition.isPerformedAutocorrection()) {
            CmsNotification.get().send(CmsNotification.Type.NORMAL,
                    Messages.get().key(Messages.GUI_WARN_INVALID_XML_STRUCTURE_0));
            setChanged();
        }
    }

    /**
     * Initializes the editor change handler.<p>
     *
     * @param changeScopes the scopes to watch for changes
     */
    void initEditorChangeHandlers(Collection<String> changeScopes) {

        if (m_editorChangeHandler != null) {
            m_editorChangeHandler.clear();
        }
        m_editorChangeHandler = new EditorChangeHandler(getEntity(), changeScopes);
    }

    /**
     * Initializes the entity observer.<p>
     */
    void initEntityObserver() {

        if (m_entityObserver != null) {
            m_entityObserver.clear();
        }
        m_entityObserver = new CmsEntityObserver(getCurrentEntity());
        exportObserver();
    }

    /**
     * Opens the form based editor.<p>
     */
    void initFormPanel() {

        removeEditOverlays();
        m_openFormButton.setVisible(false);
        m_saveButton.setVisible(true);
        m_hideHelpBubblesButton.setVisible(true);
        m_undoButton.setVisible(true);
        m_redoButton.setVisible(true);
        m_basePanel = new FlowPanel();
        m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.editorCss().basePanel());
        // insert base panel before the tool bar to keep the tool bar visible
        RootPanel.get().add(m_basePanel);
        if (m_isStandAlone) {
            RootPanel.getBodyElement().addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().standAloneEditor());
        } else {
            RootPanel.getBodyElement().getParentElement().getStyle().setOverflow(Overflow.HIDDEN);
        }
    }

    /**
     * Opens the copy locale dialog.<p>
     */
    void openCopyLocaleDialog() {

        CmsCopyLocaleDialog dialog = new CmsCopyLocaleDialog(m_availableLocales, m_contentLocales, m_locale,
                m_definitions.get(m_locale).hasSynchronizedElements(), this);
        dialog.center();
    }

    /**
     * Opens the model file select dialog.<p>
     *
     * @param context the editor context
     * @param definition the content definition
     */
    void openModelSelectDialog(final CmsEditorContext context, final CmsContentDefinition definition) {

        I_CmsModelSelectHandler handler = new I_CmsModelSelectHandler() {

            public void onModelSelect(CmsUUID modelStructureId) {

                if (modelStructureId == null) {
                    modelStructureId = CmsUUID.getNullUUID();
                }
                openFormEditor(context, definition.getLocale(), definition.getReferenceResourceId().toString(),
                        definition.getNewLink(), modelStructureId, null, null, null, m_onClose);
            }
        };
        String title = org.opencms.gwt.client.Messages.get()
                .key(org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_TITLE_0);
        String message = org.opencms.gwt.client.Messages.get()
                .key(org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_MESSAGE_0);
        CmsModelSelectDialog dialog = new CmsModelSelectDialog(handler, definition.getModelInfos(), title, message);
        dialog.center();
    }

    /**
     * Previews the native event to enable keyboard short cuts.<p>
     *
     * @param event the event
     */
    void previewNativeEvent(NativePreviewEvent event) {

        Event nativeEvent = Event.as(event.getNativeEvent());
        if (event.getTypeInt() == Event.ONKEYDOWN) {
            int keyCode = nativeEvent.getKeyCode();
            if (nativeEvent.getCtrlKey()) {
                // look for short cuts
                if (nativeEvent.getShiftKey()) {
                    if (keyCode == KeyCodes.KEY_S) {

                        exitWithSaving();
                        nativeEvent.preventDefault();
                        nativeEvent.stopPropagation();
                    } else if (keyCode == KeyCodes.KEY_X) {
                        confirmCancel();
                        nativeEvent.preventDefault();
                        nativeEvent.stopPropagation();
                    }
                } else if (keyCode == KeyCodes.KEY_S) {
                    if (checkValidation()) {
                        save();
                    }
                    nativeEvent.preventDefault();
                    nativeEvent.stopPropagation();
                }

            }
        }
    }

    /**
     * Renders the form content.<p>
     */
    void renderFormContent() {

        initLocaleSelect();
        setNativeResourceInfo(m_sitePath, m_locale);
        CmsInfoHeader header = new CmsInfoHeader(m_title, null, m_sitePath, m_locale,
                CmsIconUtil.getResourceIconClasses(m_resourceTypeName, m_sitePath, false));
        m_basePanel.add(header);
        SimplePanel content = new SimplePanel();
        content.setStyleName(org.opencms.acacia.client.css.I_CmsLayoutBundle.INSTANCE.form().formParent());
        m_basePanel.add(content);
        initEntityObserver();
        if (m_definitions.get(m_locale).hasEditorChangeHandlers()) {
            initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes());
        }
        renderEntityForm(m_entityId, m_tabInfos, content, m_basePanel.getElement());

    }

    /**
     * Saves the content and closes the editor.<p>
     */
    void save() {

        saveAndDeleteEntities(false, new Command() {

            public void execute() {

                setSaved();
                setUnchanged();
            }
        });
    }

    /**
     * Saves the content and closes the editor.<p>
     */
    void saveAndExit() {

        boolean unlock = shouldUnlockAutomatically();
        // store the scroll position
        final int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop();
        saveAndDeleteEntities(unlock, new Command() {

            public void execute() {

                setSaved();
                if (m_onClose != null) {
                    m_onClose.execute();
                }
                clearEditor();
                // restore the scroll position
                RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop);
            }
        });
    }

    /**
     * Sets the has changed flag and enables the save button.<p>
     */
    void setChanged() {

        enableSave();
        m_changedEntityIds.add(m_entityId);
        m_deletedEntities.remove(m_entityId);
        updateOverlayPosition();
        setEditorState(true);
    }

    /**
     * Sets the content definition.<p>
     *
     * @param definition the content definition
     */
    void setContentDefinition(CmsContentDefinition definition) {

        if (m_availableLocales.isEmpty()) {
            // only set the locales when initially setting the content definition
            m_availableLocales.putAll(definition.getAvailableLocales());
            m_contentLocales.addAll(definition.getContentLocales());
        } else {
            m_contentLocales.add(definition.getLocale());
        }
        m_title = definition.getTitle();
        m_sitePath = definition.getSitePath();
        m_resourceTypeName = definition.getResourceType();
        m_registeredEntities.add(definition.getEntityId());
        m_tabInfos = definition.getTabInfos();
        addContentDefinition(definition);
        CmsDefaultWidgetService service = (CmsDefaultWidgetService) getWidgetService();
        service.addConfigurations(definition.getConfigurations());
        service.setSyncValues(definition.getSyncValues());
        service.setSkipPaths(definition.getSkipPaths());
        addEntityChangeHandler(definition.getEntityId(), new ValueChangeHandler<CmsEntity>() {

            public void onValueChange(ValueChangeEvent<CmsEntity> event) {

                setChanged();
            }
        });
    }

    /**
     * Removes the delete on cancel flag for new resources.<p>
     */
    void setSaved() {

        setEditorState(false);
        m_deleteOnCancel = false;
    }

    /**
     * Call after save.<p>
     */
    void setUnchanged() {

        m_changedEntityIds.clear();
        m_deletedEntities.clear();
        disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0));
    }

    /**
     * Enables and disabler the undo redo buttons according to the state.<p>
     *
     * @param state the undo redo state
     */
    void setUndoRedoState(UndoRedoState state) {

        if (state.hasUndo()) {
            m_undoButton.enable();
        } else {
            m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0));
        }
        if (state.hasRedo()) {
            m_redoButton.enable();
        } else {
            m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0));
        }
    }

    /**
     * Returns true if the edited resource should be unlocked automatically after pressing Save/Exit.<p>
     *
     * @return true if the edited resource should be unlocked automatically
     */
    boolean shouldUnlockAutomatically() {

        if (m_isStandAlone) {
            if (m_isDirectEdit) {
                // Classic direct edit case - always unlock
                return true;
            } else {
                // Workplace case - determined by configuration
                return m_autoUnlock;
            }
        }
        // Container page case - always unlock
        return true;
    }

    /**
     * Shows the validation error dialog.<p>
     *
     * @param validationResult the validation result
     */
    void showValidationErrorDialog(CmsValidationResult validationResult) {

        if (validationResult.getErrors().keySet().contains(m_entityId)) {
            getValidationHandler().displayValidation(m_entityId, validationResult);
        }
        String errorLocales = "";
        for (String entityId : validationResult.getErrors().keySet()) {
            String locale = CmsContentDefinition.getLocaleFromId(entityId);
            errorLocales += m_availableLocales.get(locale) + ", ";
        }
        // remove trailing ','
        errorLocales = errorLocales.substring(0, errorLocales.length() - 2);
        CmsErrorDialog dialog = new CmsErrorDialog(
                Messages.get().key(Messages.GUI_VALIDATION_ERROR_1, errorLocales), null);
        dialog.center();
    }

    /**
     * Switches to the selected locale. Will save changes first.<p>
     *
     * @param locale the locale to switch to
     */
    void switchLocale(final String locale) {

        if (locale.equals(m_locale)) {
            return;
        }
        m_locale = locale;
        m_basePanel.clear();
        destroyForm(false);
        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
        m_entityId = getIdForLocale(locale);
        // if the content does not contain the requested locale yet, a new node will be created
        final boolean addedNewLocale = !m_contentLocales.contains(locale);
        if (m_registeredEntities.contains(m_entityId)) {
            unregistereEntity(m_entityId);
        }
        if (addedNewLocale) {
            loadNewDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() {

                public void execute(final CmsContentDefinition contentDefinition) {

                    setContentDefinition(contentDefinition);
                    renderFormContent();
                    setChanged();

                }
            });
        } else {
            loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() {

                public void execute(CmsContentDefinition contentDefinition) {

                    setContentDefinition(contentDefinition);
                    renderFormContent();
                }
            });
        }
    }

    /**
     * Synchronizes the locale independent fields to the other locales.<p>
     */
    void synchronizeCurrentLocale() {

        m_basePanel.clear();
        destroyForm(false);
        CmsEntity entity = m_entityBackend.getEntity(m_entityId);
        m_entityId = getIdForLocale(m_locale);
        ((CmsDefaultWidgetService) getWidgetService()).setSkipPaths(Collections.<String>emptyList());
        loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() {

            public void execute(CmsContentDefinition contentDefinition) {

                setContentDefinition(contentDefinition);
                renderFormContent();
                setChanged();
            }
        });
    }

    /**
     * Unlocks the edited resource.<p>
     */
    void unlockResource() {

        if (!shouldUnlockAutomatically()) {
            return;
        }
        if (m_entityId != null) {
            final CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId);
            if (m_deleteOnCancel) {
                CmsRpcAction<Void> action = new CmsRpcAction<Void>() {

                    @Override
                    public void execute() {

                        CmsCoreProvider.getVfsService().syncDeleteResource(structureId, this);
                    }

                    @Override
                    protected void onResponse(Void result) {

                        // nothing to do
                    }
                };
                action.executeSync();
            } else {
                CmsCoreProvider.get().unlock(structureId);
            }
        }
    }

    /**
     * Updates the editor values.<p>
     *
     * @param previous the previous entity state
     * @param updated the updated entity state
     */
    void updateEditorValues(CmsEntity previous, CmsEntity updated) {

        if (updated.getId().equals(m_entityId)) {
            // only apply the changes to the same locale entity
            updateEditorValues(previous, updated, getEntity(), Collections.<String>emptyList());
        }
    }

    /**
     * Adds the change listener to the observer.<p>
     *
     * @param changeListener the change listener
     * @param changeScope the change scope
     */
    private void addChangeListener(JavaScriptObject changeListener, String changeScope) {

        try {
            System.out.println("Adding native listener for scope " + changeScope);
            m_entityObserver.addEntityChangeListener(new CmsEntityChangeListenerWrapper(changeListener),
                    changeScope);
        } catch (Exception e) {

            CmsDebugLog.getInstance().printLine("Exception occured during listener registration" + e.getMessage());
        }
    }

    /**
     * Changes a simple type entity value.<p>
     *
     * @param attributeName the attribute name
     * @param index the value index
     * @param value the value
     * @param parentPathElements the parent path elements
     */
    private void changeSimpleValue(String attributeName, int index, String value, List<String> parentPathElements) {

        CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements);
        handler.changeValue(value, index);
    }

    /**
     * Closes the editor.<p>
     */
    private native void closeEditorWidow() /*-{
                                           if ($wnd.top.cms_ade_closeEditorDialog) {
                                           $wnd.top.cms_ade_closeEditorDialog();
                                           } else {
                                           var backlink = $wnd[@org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService::PARAM_BACKLINK];
                                           if (backlink) {
                                           $wnd.top.location.href = backlink;
                                           }
                                           }
                                           }-*/;

    /**
     * Creates a push button for the edit tool-bar.<p>
     *
     * @param title the button title
     * @param imageClass the image class
     *
     * @return the button
     */
    private CmsPushButton createButton(String title, String imageClass) {

        CmsPushButton result = new CmsPushButton();
        result.setTitle(title);
        result.setImageClass(imageClass);
        result.setButtonStyle(ButtonStyle.FONT_ICON, null);
        result.setSize(Size.big);
        return result;
    }

    /**
     * Enables the save buttons.<p>
     */
    private void enableSave() {

        m_saveButton.enable();
        m_saveExitButton.enable();
    }

    /**
     * Exports the add entity change listener method.<p>
     */
    private native void exportObserver()/*-{
                                        var self = this;
                                        $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD] = function(
                                        listener, scope) {
                                        var wrapper = {
                                        onChange : listener.onChange
                                        }
                                        self.@org.opencms.ade.contenteditor.client.CmsContentEditor::addChangeListener(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)(wrapper, scope);
                                        }
                                        $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD] = function() {
                                        return new $wnd.acacia.CmsEntityWrapper(
                                        self.@org.opencms.ade.contenteditor.client.CmsContentEditor::getCurrentEntity()());
                                        }
                                        }-*/;

    /**
     * Returns the attribute handler for the given attribute.<p>
     *
     * @param attributeName the attribute name
     * @param parentPathElements the parent path elements
     *
     * @return the attribute handler
     */
    private CmsAttributeHandler getAttributeHandler(String attributeName, List<String> parentPathElements) {

        List<String> childPathElements = new ArrayList<String>(parentPathElements);
        childPathElements.add(attributeName);
        CmsAttributeHandler handler = getRootAttributeHandler()
                .getHandlerByPath(childPathElements.toArray(new String[childPathElements.size()]));
        return handler;
    }

    /**
     * Returns the entity id for the given locale.<p>
     *
     * @param locale the locale
     *
     * @return the entity id
     */
    private String getIdForLocale(String locale) {

        return CmsContentDefinition.uuidToEntityId(CmsContentDefinition.entityIdToUuid(m_entityId), locale);
    }

    /**
     * Initializes the window closing handler to ensure the resource will be unlocked when leaving the editor.<p>
     */
    private void initClosingHandler() {

        m_closingHandlerRegistration = Window.addWindowClosingHandler(new ClosingHandler() {

            /**
             * @see com.google.gwt.user.client.Window.ClosingHandler#onWindowClosing(com.google.gwt.user.client.Window.ClosingEvent)
             */
            public void onWindowClosing(ClosingEvent event) {

                unlockResource();
            }
        });
    }

    /**
     * Initializes the event preview handler.<p>
     */
    private void initEventPreviewHandler() {

        m_previewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {

            public void onPreviewNativeEvent(NativePreviewEvent event) {

                previewNativeEvent(event);
            }
        });
    }

    /**
     * Initializes the locale selector.<p>
     */
    private void initLocaleSelect() {

        if (m_availableLocales.size() < 2) {
            return;
        }
        Map<String, String> selectOptions = new HashMap<String, String>();
        for (Entry<String, String> localeEntry : m_availableLocales.entrySet()) {
            if (m_contentLocales.contains(localeEntry.getKey())) {
                selectOptions.put(localeEntry.getKey(), localeEntry.getValue());
            } else {
                selectOptions.put(localeEntry.getKey(), localeEntry.getValue() + " [-]");
            }
        }
        if (m_localeSelect == null) {
            m_localeSelect = new CmsSelectBox(selectOptions);
            m_toolbar.insertRight(m_localeSelect, 1);
            m_localeSelect.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().inlineBlock());
            m_localeSelect.getElement().getStyle().setWidth(100, Unit.PX);
            m_localeSelect.getElement().getStyle().setVerticalAlign(VerticalAlign.MIDDLE);
            m_localeSelect.addValueChangeHandler(new ValueChangeHandler<String>() {

                public void onValueChange(ValueChangeEvent<String> event) {

                    switchLocale(event.getValue());
                }
            });
        } else {
            m_localeSelect.setItems(selectOptions);
        }
        m_localeSelect.setFormValueAsString(m_locale);
        if (m_deleteLocaleButton == null) {
            m_deleteLocaleButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_DELETE_LOCALE_0),
                    "opencms-icon-remove-locale");
            m_deleteLocaleButton.addClickHandler(new ClickHandler() {

                public void onClick(ClickEvent event) {

                    confirmDeleteLocale();
                }
            });
            m_toolbar.insertRight(m_deleteLocaleButton, 2);
        }
        if (m_contentLocales.size() > 1) {
            m_deleteLocaleButton.enable();
        } else {
            m_deleteLocaleButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_CANT_DELETE_LAST_LOCALE_0));
        }
        if (m_copyLocaleButton == null) {
            m_copyLocaleButton = createButton(I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getTitle(),
                    I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getIconClass());
            m_copyLocaleButton.addClickHandler(new ClickHandler() {

                public void onClick(ClickEvent event) {

                    openCopyLocaleDialog();
                }
            });
            m_toolbar.insertRight(m_copyLocaleButton, 3);
        }
    }

    /**
     * Generates the button bar displayed beneath the editable fields.<p>
     */
    private void initToolbar() {

        m_toolbar = new CmsToolbar();
        m_toolbar.setAppTitle(Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_0));
        m_publishButton = createButton(I_CmsButton.ButtonData.PUBLISH_BUTTON.getTitle(),
                I_CmsButton.ButtonData.PUBLISH_BUTTON.getIconClass());
        m_toolbar.addLeft(m_publishButton);
        m_publishButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                boolean unlock = shouldUnlockAutomatically();

                saveAndDeleteEntities(unlock, new Command() {

                    public void execute() {

                        setSaved();
                        HashMap<String, String> params = new HashMap<String, String>(
                                getContext().getPublishParameters());
                        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(getEntityId());
                        params.put(CmsPublishOptions.PARAM_CONTENT, "" + structureId);
                        params.put(CmsPublishOptions.PARAM_START_WITH_CURRENT_PAGE, "");
                        CmsPublishDialog.showPublishDialog(params, new CloseHandler<PopupPanel>() {

                            public void onClose(CloseEvent<PopupPanel> closeEvent) {

                                if (m_onClose != null) {
                                    m_onClose.execute();
                                }
                                clearEditor();
                            }
                        }, new Runnable() {

                            public void run() {

                                // ignore
                            }

                        }, null);

                    }
                });

            }
        });
        m_saveExitButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_SAVE_AND_EXIT_0),
                "opencms-icon-save-exit");
        m_saveExitButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                deferSaveAndExit();
            }
        });
        m_toolbar.addLeft(m_saveExitButton);
        m_saveButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_SAVE_0),
                I_CmsButton.ButtonData.SAVE_BUTTON.getIconClass());
        m_saveButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                deferSave();
            }
        });
        m_saveButton.setVisible(false);
        m_toolbar.addLeft(m_saveButton);
        disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0));
        m_undoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_0), "opencms-icon-undo");
        m_undoButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                if (CmsUndoRedoHandler.getInstance().isIntitalized()) {
                    CmsUndoRedoHandler.getInstance().undo();
                }
            }
        });
        m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0));
        m_undoButton.setVisible(false);
        m_toolbar.addLeft(m_undoButton);
        m_redoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_REDO_0), "opencms-icon-redo");
        m_redoButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                if (CmsUndoRedoHandler.getInstance().isIntitalized()) {
                    CmsUndoRedoHandler.getInstance().redo();
                }
            }
        });
        m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0));
        m_redoButton.setVisible(false);
        m_toolbar.addLeft(m_redoButton);

        m_undoRedoHandlerRegistration = CmsUndoRedoHandler.getInstance()
                .addValueChangeHandler(new ValueChangeHandler<CmsUndoRedoHandler.UndoRedoState>() {

                    public void onValueChange(ValueChangeEvent<UndoRedoState> event) {

                        setUndoRedoState(event.getValue());
                    }
                });
        m_openFormButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_OPEN_FORM_0),
                I_CmsButton.ButtonData.EDIT.getIconClass());
        m_openFormButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                initFormPanel();
                renderFormContent();
            }
        });
        m_toolbar.addLeft(m_openFormButton);

        m_hideHelpBubblesButton = new CmsToggleButton();

        m_hideHelpBubblesButton.setImageClass(I_CmsButton.ButtonData.TOGGLE_HELP.getIconClass());
        m_hideHelpBubblesButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
        m_hideHelpBubblesButton.setSize(Size.big);
        m_hideHelpBubblesButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                CmsToggleButton button = (CmsToggleButton) event.getSource();
                hideHelpBubbles(!button.isDown());
            }
        });
        m_hideHelpBubblesButton.setDown(CmsCoreProvider.get().isShowEditorHelp());
        CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(),
                !CmsCoreProvider.get().isShowEditorHelp());
        if (!CmsCoreProvider.get().isShowEditorHelp()) {
            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0));
        } else {
            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0));
        }
        m_toolbar.addRight(m_hideHelpBubblesButton);

        m_cancelButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_RESET_0),
                I_CmsButton.ButtonData.RESET_BUTTON.getIconClass());
        m_cancelButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {

                confirmCancel();
            }
        });
        m_toolbar.addRight(m_cancelButton);
        RootPanel.get().add(m_toolbar);
    }

    /**
     * Sets the editor state.<p>
     *
     * @param changed if the content has been changed
     */
    private native void setEditorState(boolean changed)/*-{
                                                       if (typeof $wnd.cmsSetEditorChangedState === 'function') {
                                                       $wnd.cmsSetEditorChangedState(changed);
                                                       }
                                                       }-*/;

    /**
     * Sets the resource info to native window context variables.<p>
     *
     * @param sitePath the site path
     * @param locale the content locale
     */
    private native void setNativeResourceInfo(String sitePath, String locale)/*-{
                                                                             $wnd._editResource = sitePath;
                                                                             $wnd._editLanguage = locale;
                                                                             }-*/;

    /**
     * Shows the locked resource error message.<p>
     */
    private void showLockedResourceMessage() {

        CmsErrorDialog dialog = new CmsErrorDialog(
                Messages.get().key(Messages.ERR_RESOURCE_ALREADY_LOCKED_BY_OTHER_USER_0), null);
        dialog.addCloseHandler(new CloseHandler<PopupPanel>() {

            public void onClose(CloseEvent<PopupPanel> event) {

                cancelEdit();
            }
        });
        dialog.center();
    }

    /**
     * Updates the editor values according to the given entity.<p>
     *
     * @param previous the previous entity state
     * @param updated the updated entity state
     * @param target the target entity
     * @param parentPathElements the parent path elements
     */
    private void updateEditorValues(CmsEntity previous, CmsEntity updated, CmsEntity target,
            List<String> parentPathElements) {

        for (String attributeName : m_entityBackend.getType(target.getTypeName()).getAttributeNames()) {
            CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements);
            if (previous.hasAttribute(attributeName) && updated.hasAttribute(attributeName)
                    && target.hasAttribute(attributeName)) {
                CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName);
                CmsEntityAttribute previousAttribute = previous.getAttribute(attributeName);
                CmsEntityAttribute targetAttribute = target.getAttribute(attributeName);
                if (updatedAttribute.isSimpleValue()) {
                    if ((updatedAttribute.getValueCount() == previousAttribute.getValueCount())
                            && (updatedAttribute.getValueCount() == targetAttribute.getValueCount())) {
                        for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
                            if (!updatedAttribute.getSimpleValues().get(i)
                                    .equals(previousAttribute.getSimpleValues().get(i))
                                    && previousAttribute.getSimpleValues().get(i)
                                            .equals(targetAttribute.getSimpleValues().get(i))) {

                                changeSimpleValue(attributeName, i, updatedAttribute.getSimpleValues().get(i),
                                        parentPathElements);
                            }
                        }
                    } else {
                        if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) {
                            // only act, if the value count has not been altered while executing the server request
                            if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) {
                                // new values have been added
                                for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
                                    if (i >= previousAttribute.getSimpleValues().size()) {
                                        handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i));
                                    } else if (!updatedAttribute.getSimpleValues().get(i)
                                            .equals(previousAttribute.getSimpleValues().get(i))
                                            && previousAttribute.getSimpleValues().get(i)
                                                    .equals(targetAttribute.getSimpleValues().get(i))) {
                                        changeSimpleValue(attributeName, i,
                                                updatedAttribute.getSimpleValues().get(i), parentPathElements);
                                    }
                                }
                            } else {
                                // values have been removed
                                for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) {
                                    if (i >= updatedAttribute.getSimpleValues().size()) {
                                        handler.removeAttributeValue(i);
                                    } else if (!updatedAttribute.getSimpleValues().get(i)
                                            .equals(previousAttribute.getSimpleValues().get(i))
                                            && previousAttribute.getSimpleValues().get(i)
                                                    .equals(targetAttribute.getSimpleValues().get(i))) {
                                        changeSimpleValue(attributeName, i,
                                                updatedAttribute.getSimpleValues().get(i), parentPathElements);
                                    }
                                }
                            }
                        }
                    }
                } else {
                    if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) {
                        // only act, if the value count has not been altered while executing the server request
                        if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) {
                            // new values have been added
                            for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
                                if (i >= previousAttribute.getSimpleValues().size()) {
                                    handler.addNewAttributeValue(m_entityBackend
                                            .registerEntity(updatedAttribute.getComplexValues().get(i), true));
                                } else {
                                    List<String> childPathElements = new ArrayList<String>(parentPathElements);
                                    childPathElements.add(attributeName + "[" + i + "]");
                                    updateEditorValues(previousAttribute.getComplexValues().get(i),
                                            updatedAttribute.getComplexValues().get(i),
                                            targetAttribute.getComplexValues().get(i), childPathElements);
                                }
                            }
                        } else {
                            // values have been removed
                            for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) {
                                if (i >= updatedAttribute.getValueCount()) {

                                    handler.removeAttributeValue(i);
                                } else {
                                    List<String> childPathElements = new ArrayList<String>(parentPathElements);
                                    childPathElements.add(attributeName + "[" + i + "]");
                                    updateEditorValues(previousAttribute.getComplexValues().get(i),
                                            updatedAttribute.getComplexValues().get(i),
                                            targetAttribute.getComplexValues().get(i), childPathElements);
                                }
                            }
                        }
                    }
                }
            } else if (previous.hasAttribute(attributeName) && target.hasAttribute(attributeName)) {
                for (int i = target.getAttribute(attributeName).getValueCount() - 1; i >= 0; i--) {
                    handler.removeAttributeValue(i);
                }

            } else if (!previous.hasAttribute(attributeName) && !target.hasAttribute(attributeName)
                    && updated.hasAttribute(attributeName)) {
                CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName);
                for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
                    if (updatedAttribute.isSimpleValue()) {
                        handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i));
                    } else {
                        handler.addNewAttributeValue(
                                m_entityBackend.registerEntity(updatedAttribute.getComplexValues().get(i), true));
                    }
                }
            }
        }
    }
}