content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (C) Alkacon Software (https://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: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://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.ChangeType;
  import org.opencms.acacia.client.css.I_CmsLayoutBundle;
  import org.opencms.acacia.client.entity.I_CmsEntityBackend;
  import org.opencms.acacia.client.ui.CmsAttributeValueView;
  import org.opencms.acacia.client.ui.CmsInlineEntityWidget;
  import org.opencms.acacia.client.widgets.I_CmsFormEditWidget;
  import org.opencms.acacia.shared.CmsEntity;
  import org.opencms.acacia.shared.CmsEntityAttribute;
  import org.opencms.acacia.shared.CmsType;
  import org.opencms.gwt.client.dnd.CmsDNDHandler;
  import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation;
  import org.opencms.gwt.client.ui.CmsTabbedPanel;
  import org.opencms.gwt.client.util.CmsMoveAnimation;
  import org.opencms.gwt.shared.CmsGwtLog;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import com.google.gwt.dom.client.Element;
  import com.google.gwt.dom.client.Style.Position;
  import com.google.gwt.dom.client.Style.Unit;
  import com.google.gwt.event.logical.shared.ResizeHandler;
  import com.google.gwt.user.client.Command;
  import com.google.gwt.user.client.ui.FlowPanel;
  import com.google.gwt.user.client.ui.Panel;
  import com.google.gwt.user.client.ui.Widget;
  
  /**
   * The attribute handler. Handles value changes, addition of new values, remove and move operations on values.<p>
   */
  public class CmsAttributeHandler extends CmsRootHandler {
  
      /** The global widget resize handler. */
      private static ResizeHandler m_resizeHandler;
  
      /** The scroll element. */
      private static Element m_scrollElement;
  
      /** The attribute name. */
      private String m_attributeName;
  
      /** The attribute type. */
      private CmsType m_attributeType;
  
      /** Registered attribute values. */
      private List<CmsAttributeValueView> m_attributeValueViews;
  
      /** The attribute drag and drop handler. */
      private CmsDNDHandler m_dndHandler;
  
      /** The entity. */
      private CmsEntity m_entity;
  
      /** The entity back end instance. */
      private I_CmsEntityBackend m_entityBackEnd;
  
      /** The entity type. */
      private CmsType m_entityType;
  
      /** The parent attribute handler. */
      private I_CmsAttributeHandler m_parentHandler;
  
      /** The single value index. */
      private int m_singleValueIndex;
  
      /** The widget service. */
      private I_CmsWidgetService m_widgetService;
  
      /**
       * Constructor.<p>
       *
       * @param entityBackEnd the entity back end instance
       * @param entity the entity
       * @param attributeName the attribute name
       * @param widgetService the widget service
       */
      public CmsAttributeHandler(
          I_CmsEntityBackend entityBackEnd,
          CmsEntity entity,
          String attributeName,
          I_CmsWidgetService widgetService) {
  
          // single value handling is disable by default
          m_singleValueIndex = -1;
          m_entityBackEnd = entityBackEnd;
          m_entity = entity;
          m_attributeName = attributeName;
          m_widgetService = widgetService;
          m_attributeValueViews = new ArrayList<CmsAttributeValueView>();
          if (!getAttributeType().isSimpleType()) {
              int count = 0;
              CmsEntityAttribute attribute = entity.getAttribute(attributeName);
              if (attribute != null) {
                  count = attribute.getValueCount();
              }
              initHandlers(count);
          }
      }
  
      /**
       * Clears the error styles on the given tabbed panel.<p>
       *
       * @param tabbedPanel the tabbed panel
       */
      public static void clearErrorStyles(CmsTabbedPanel<?> tabbedPanel) {
  
          for (int i = 0; i < tabbedPanel.getTabCount(); i++) {
              Widget tab = tabbedPanel.getTabWidget(i);
              tab.setTitle(null);
              tab.getParent().removeStyleName(I_CmsLayoutBundle.INSTANCE.form().hasError());
              tab.getParent().removeStyleName(I_CmsLayoutBundle.INSTANCE.form().hasWarning());
          }
      }
  
      /**
       * Returns the global widget resize handler.<p>
       *
       * @return the global widget resize handler
       */
      public static ResizeHandler getResizeHandler() {
  
          return m_resizeHandler;
      }
  
      /**
       * Returns <code>true</code> if a global widget resize handler is present.<p>
       *
       * @return <code>true</code> if a global widget resize handler is present
       */
      public static boolean hasResizeHandler() {
  
          return m_resizeHandler != null;
      }
  
      /**
       * Sets the global widget resize handler.<p>
       *
       * @param handler the resize handler
       */
      public static void setResizeHandler(ResizeHandler handler) {
  
          m_resizeHandler = handler;
      }
  
      /**
       * Sets the scroll element. To be used for automatic scrolling during drag and drop.<p>
       *
       * @param scrollElement the scroll element
       */
      public static void setScrollElement(Element scrollElement) {
  
          m_scrollElement = scrollElement;
      }
  
      /**
       * Adds a new attribute value below the reference view.<p>
       *
       * @param reference the reference value view
       */
      public void addNewAttributeValue(CmsAttributeValueView reference) {
  
          // make sure not to add more values than allowed
          int maxOccurrence = getEntityType().getAttributeMaxOccurrence(m_attributeName);
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          boolean mayHaveMore = ((attribute == null) || (attribute.getValueCount() < maxOccurrence));
          if (mayHaveMore) {
              if (getAttributeType().isSimpleType()) {
                  int valueIndex = reference.getValueIndex() + 1;
                  String defaultValue = m_widgetService.getDefaultAttributeValue(
                      m_attributeName,
                      getSimplePath(valueIndex));
                  I_CmsFormEditWidget widget = m_widgetService.getAttributeFormWidget(m_attributeName);
  
                  boolean insertLast = false;
                  if (reference.getElement().getNextSiblingElement() == null) {
                      m_entity.addAttributeValue(m_attributeName, defaultValue);
                      insertLast = true;
                  } else {
                      valueIndex = reference.getValueIndex() + 1;
                      m_entity.insertAttributeValue(m_attributeName, defaultValue, valueIndex);
                      m_widgetService.addChangedOrderPath(getSimplePath(-1));
                  }
                  CmsAttributeValueView valueWidget = reference;
                  if (reference.hasValue()) {
                      valueWidget = new CmsAttributeValueView(
                          this,
                          m_widgetService.getAttributeLabel(m_attributeName),
                          m_widgetService.getAttributeHelp(m_attributeName));
                      if (m_widgetService.isDisplaySingleLine(m_attributeName)) {
                          valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
                      }
                      if (insertLast) {
                          ((FlowPanel)reference.getParent()).add(valueWidget);
                      } else {
                          ((FlowPanel)reference.getParent()).insert(valueWidget, valueIndex);
                      }
  
                  }
                  valueWidget.setValueWidget(widget, defaultValue, defaultValue, true);
              } else {
                  CmsEntity value = m_entityBackEnd.createEntity(null, getAttributeType().getId());
                  insertValueAfterReference(value, reference);
              }
              CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
              if (handler.isIntitalized()) {
                  handler.addChange(m_entity.getId(), m_attributeName, reference.getValueIndex() + 1, ChangeType.add);
              }
          }
          updateButtonVisisbility();
      }
  
      /**
       * Adds a new attribute value and adds the required widgets to the editor DOM.<p>
       *
       * @param value the value entity
       */
      public void addNewAttributeValue(CmsEntity value) {
  
          // make sure not to add more values than allowed
          int maxOccurrence = getEntityType().getAttributeMaxOccurrence(m_attributeName);
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          boolean mayHaveMore = ((attribute == null) || (attribute.getValueCount() < maxOccurrence));
          if (mayHaveMore && value.getTypeName().equals(m_attributeType.getId())) {
              m_entity.addAttributeValue(m_attributeName, value);
              int valueIndex = m_entity.getAttribute(m_attributeName).getValueCount() - 1;
              CmsAttributeValueView valueView = null;
              if ((m_attributeValueViews.size() == 1) && !m_attributeValueViews.get(0).hasValue()) {
                  valueView = m_attributeValueViews.get(0);
              } else {
                  valueView = new CmsAttributeValueView(
                      this,
                      m_widgetService.getAttributeLabel(m_attributeName),
                      m_widgetService.getAttributeHelp(m_attributeName));
              }
  
              CmsRenderer.setAttributeChoice(m_widgetService, valueView, getAttributeType());
              ((FlowPanel)m_attributeValueViews.get(0).getParent()).add(valueView);
  
              insertHandlers(valueIndex);
              I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(m_attributeName, getAttributeType());
              valueView.setValueEntity(renderer, value);
  
              CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
              if (handler.isIntitalized()) {
                  handler.addChange(m_entity.getId(), m_attributeName, valueIndex, ChangeType.add);
              }
          }
      }
  
      /**
       * Adds a new attribute value and adds the required widgets to the editor DOM.<p>
       *
       * @param value the simple value
       */
      public void addNewAttributeValue(String value) {
  
          // make sure not to add more values than allowed
          int maxOccurrence = getEntityType().getAttributeMaxOccurrence(m_attributeName);
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          boolean mayHaveMore = ((attribute == null) || (attribute.getValueCount() < maxOccurrence));
          if (mayHaveMore && getAttributeType().isSimpleType()) {
              I_CmsFormEditWidget widget = m_widgetService.getAttributeFormWidget(m_attributeName);
              m_entity.addAttributeValue(m_attributeName, value);
              int valueCount = m_entity.getAttribute(m_attributeName).getValueCount();
              String defaultValue = m_widgetService.getDefaultAttributeValue(
                  m_attributeName,
                  getSimplePath(valueCount - 1));
              CmsAttributeValueView valueView = null;
              if ((m_attributeValueViews.size() == 1) && !m_attributeValueViews.get(0).hasValue()) {
                  valueView = m_attributeValueViews.get(0);
                  valueView.setActive();
                  // setActive may have reset the value, so we set it again
                  m_entity.setAttributeValue(m_attributeName, value, valueCount - 1);
                  valueView.getValueWidget().setValue(value);
              } else {
                  valueView = new CmsAttributeValueView(
                      this,
                      m_widgetService.getAttributeLabel(m_attributeName),
                      m_widgetService.getAttributeHelp(m_attributeName));
                  if (m_widgetService.isDisplaySingleLine(m_attributeName)) {
                      valueView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
                  }
                  ((FlowPanel)m_attributeValueViews.get(0).getParent()).add(valueView);
                  valueView.setValueWidget(widget, value, defaultValue, true);
              }
              CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
              if (handler.isIntitalized()) {
                  handler.addChange(
                      m_entity.getId(),
                      m_attributeName,
                      m_entity.getAttribute(m_attributeName).getValueCount() - 1,
                      ChangeType.add);
              }
              updateButtonVisisbility();
          }
      }
  
      /**
       * Adds a new attribute value below the reference index.<p>
       * This will not execute any DOM manipulations.<p>
       *
       * @param referenceIndex the reference value index
       */
      public void addNewAttributeValueToEntity(int referenceIndex) {
  
          // make sure not to add more values than allowed
          int maxOccurrence = getEntityType().getAttributeMaxOccurrence(m_attributeName);
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          boolean mayHaveMore = ((attribute == null) || (attribute.getValueCount() < maxOccurrence));
          if (mayHaveMore) {
              if (getAttributeType().isSimpleType()) {
                  String defaultValue = m_widgetService.getDefaultAttributeValue(
                      m_attributeName,
                      getSimplePath(referenceIndex + 1));
                  if ((attribute == null) || (attribute.getValueCount() == (referenceIndex + 1))) {
                      m_entity.addAttributeValue(m_attributeName, defaultValue);
                  } else {
                      m_entity.insertAttributeValue(m_attributeName, defaultValue, referenceIndex + 1);
                      m_widgetService.addChangedOrderPath(getSimplePath(-1));
                  }
              } else {
                  CmsEntity value = m_entityBackEnd.createEntity(null, m_attributeType.getId());
                  if ((attribute == null) || (attribute.getValueCount() == (referenceIndex + 1))) {
                      m_entity.addAttributeValue(m_attributeName, value);
                  } else {
                      m_entity.insertAttributeValue(m_attributeName, value, referenceIndex + 1);
                      m_widgetService.addChangedOrderPath(getSimplePath(-1));
                  }
                  insertHandlers(referenceIndex + 1);
              }
          }
      }
  
      /**
       * Adds a new choice attribute value.<p>
       *
       * @param reference the reference value view
       * @param choicePath the path of the selected (possibly nested) choice attribute, consisting of attribute names
       */
      public void addNewChoiceAttributeValue(CmsAttributeValueView reference, List<String> choicePath) {
  
          CmsValueFocusHandler.getInstance().clearFocus();
          m_widgetService.addChangedOrderPath(getSimplePath(-1));
          if (isChoiceHandler()) {
              addChoiceOption(reference, choicePath);
          } else {
              addComplexChoiceValue(reference, choicePath);
          }
          updateButtonVisisbility();
          CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
          if (handler.isIntitalized()) {
              handler.addChange(m_entity.getId(), m_attributeName, reference.getValueIndex() + 1, ChangeType.choice);
          }
      }
  
      /**
       * Applies a value change to the entity data as well as to the value view widget.<p>
       *
       * @param value the value
       * @param valueIndex the value index
       */
      public void changeValue(String value, int valueIndex) {
  
          m_attributeValueViews.get(valueIndex).getValueWidget().setValue(value, false);
          changeEntityValue(value, valueIndex);
      }
  
      /**
       * @see org.opencms.acacia.client.CmsRootHandler#collectSimplePath(org.opencms.acacia.client.I_CmsAttributeHandler)
       */
      @Override
      public String collectSimplePath(I_CmsAttributeHandler childHandler) {
  
          int index = -1;
          for (int i = 0; i < m_handlers.size(); i++) {
              if (m_handlers.get(i).get(childHandler.getAttributeName()) == childHandler) {
                  index = i;
                  break;
              }
          }
          if (index == -1) {
              throw new RuntimeException("Child handler is not properly registered.");
          }
          return getSimplePath(index) + "/";
      }
  
      /**
       * Creates a sequence of nested entities according to a given path of choice attribute names.<p>
       *
       * @param value the entity into which the new entities for the given path should be inserted
       * @param choicePath the path of choice attributes
       */
      public void createNestedEntitiesForChoicePath(CmsEntity value, List<String> choicePath) {
  
          CmsEntity parentValue = value;
          for (String attributeChoice : choicePath) {
              CmsType choiceType = m_entityBackEnd.getType(parentValue.getTypeName()).getAttributeType(
                  CmsType.CHOICE_ATTRIBUTE_NAME);
              CmsEntity choice = m_entityBackEnd.createEntity(null, choiceType.getId());
              parentValue.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, choice);
              CmsType choiceOptionType = choiceType.getAttributeType(attributeChoice);
              if (choiceOptionType.isSimpleType()) {
                  String choiceValue = m_widgetService.getDefaultAttributeValue(attributeChoice, getSimplePath(0));
                  choice.addAttributeValue(attributeChoice, choiceValue);
                  break;
              } else {
                  CmsEntity choiceValue = m_entityBackEnd.createEntity(null, choiceOptionType.getId());
                  choice.addAttributeValue(attributeChoice, choiceValue);
                  parentValue = choiceValue;
              }
          }
      }
  
      /**
       * Destroys the attribute handler instance.<p>
       */
      public void destroy() {
  
          m_attributeName = null;
          m_attributeType = null;
          m_attributeValueViews.clear();
          m_attributeValueViews = null;
          m_dndHandler = null;
          m_entity = null;
          m_entityType = null;
          m_entityBackEnd = null;
          m_widgetService = null;
      }
  
      /**
       * Returns the attribute name.<p>
       *
       * @return the attribute name
       */
      @Override
      public String getAttributeName() {
  
          return m_attributeName;
      }
  
      /**
       * Returns the attribute type.<p>
       *
       * @return the attribute type
       */
      public CmsType getAttributeType() {
  
          if (m_attributeType == null) {
              m_attributeType = getEntityType().getAttributeType(m_attributeName);
          }
          return m_attributeType;
      }
  
      /**
       * Returns the drag and drop handler.<p>
       *
       * @return the drag and drop handler
       */
      public CmsDNDHandler getDNDHandler() {
  
          if (m_dndHandler == null) {
              m_dndHandler = new CmsDNDHandler(new CmsAttributeDNDController());
              m_dndHandler.setOrientation(Orientation.VERTICAL);
              m_dndHandler.setScrollEnabled(true);
              m_dndHandler.setScrollElement(m_scrollElement);
          }
          return m_dndHandler;
      }
  
      /**
       * Returns the entity id.<p>
       *
       * @return the entity id
       */
      public String getEntityId() {
  
          return m_entity.getId();
      }
  
      /**
       * Gets the maximum occurrence of the attribute.<p>
       *
       * @return the maximum occurrence
       */
      public int getMaxOccurence() {
  
          return getEntityType().getAttributeMaxOccurrence(m_attributeName);
      }
  
      /**
       * Returns the simple value path for the given index.<p>
       * This will use the last fragment of the attribute name and concatenate it with the parent path.<p>
       * If the given index equals -1 no value index will be appended
       *
       * @param index the value index
       *
       * @return the simple path
       */
      public String getSimplePath(int index) {
  
          String simpleName = m_attributeName.substring(m_attributeName.lastIndexOf("/") + 1);
          String result = m_parentHandler.collectSimplePath(this) + simpleName;
          if (index != -1) {
              result += "[" + (index + 1) + "]";
          }
          return result;
      }
  
      /**
       * Gets the widget service.<p>
       *
       * @return the widget service
       */
      public I_CmsWidgetService getWidgetService() {
  
          return m_widgetService;
      }
  
      /**
       * Handles value changes from the view.<p>
       *
       * @param reference the attribute value reference
       * @param value the value
       */
      public void handleValueChange(CmsAttributeValueView reference, String value) {
  
          handleValueChange(reference.getValueIndex(), value);
      }
  
      /**
       * Handles value changes from the view.<p>
       *
       * @param valueIndex the value index
       * @param value the value
       */
      public void handleValueChange(int valueIndex, String value) {
  
          if (isSingleValueHandler()) {
              valueIndex = m_singleValueIndex;
          }
          changeEntityValue(value, valueIndex);
          CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
          if (handler.isIntitalized()) {
              handler.addChange(m_entity.getId(), m_attributeName, valueIndex, ChangeType.value);
          }
      }
  
      /**
       * Return true if there is a single remaining value, which is optional.<p>
       *
       * @return true if this has only one optional value
       */
      public boolean hasSingleOptionalValue() {
  
          return ((getEntityType().getAttributeMinOccurrence(m_attributeName) == 0)
              && (m_entity.getAttribute(m_attributeName) != null)
              && (m_entity.getAttribute(m_attributeName).getValueCount() == 1));
      }
  
      /**
       * Returns if there is a value view widget registered for the given index.<p>
       *
       * @param valueIndex the value index
       *
       * @return <code>true</code> if there is a value view widget registered for the given index
       */
      public boolean hasValueView(int valueIndex) {
  
          return m_attributeValueViews.size() > valueIndex;
      }
  
      /**
       * Adds a new attribute value and adds the required widgets to the editor DOM.<p>
       *
       * @param value the value entity
       * @param index the position in which to insert the new value
       * @param container the widget containing the attribute value views
       */
      public void insertNewAttributeValue(CmsEntity value, int index, Panel container) {
  
          // make sure not to add more values than allowed
          int maxOccurrence = getEntityType().getAttributeMaxOccurrence(m_attributeName);
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          boolean mayHaveMore = ((attribute == null) || (attribute.getValueCount() < maxOccurrence));
          if (mayHaveMore && value.getTypeName().equals(m_attributeType.getId())) {
              m_entity.insertAttributeValue(m_attributeName, value, index);
              int valueIndex = index;
              CmsAttributeValueView valueView = null;
              if ((m_attributeValueViews.size() == 1) && !m_attributeValueViews.get(0).hasValue()) {
                  valueView = m_attributeValueViews.get(0);
              } else {
                  valueView = new CmsAttributeValueView(
                      this,
                      m_widgetService.getAttributeLabel(m_attributeName),
                      m_widgetService.getAttributeHelp(m_attributeName));
              }
              CmsRenderer.setAttributeChoice(m_widgetService, valueView, getAttributeType());
              m_attributeValueViews.remove(valueView);
              m_attributeValueViews.add(index, valueView);
  
              ((FlowPanel)container).insert(valueView, index);
  
              insertHandlers(valueIndex);
  
              I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(m_attributeName, getAttributeType());
              valueView.setValueEntity(renderer, value);
  
              CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
              if (handler.isIntitalized()) {
                  handler.addChange(m_entity.getId(), m_attributeName, valueIndex, ChangeType.add);
              }
          }
      }
  
      /**
       * Returns if this is a choice handler.<p>
       *
       * @return <code>true</code> if this is a choice handler
       */
      public boolean isChoiceHandler() {
  
          return CmsType.CHOICE_ATTRIBUTE_NAME.equals(m_attributeName);
      }
  
      /**
       * Moves the give attribute value from one position to another.<p>
       *
       * @param valueView the value to move
       * @param currentPosition the current position
       * @param targetPosition the target position
       */
      public void moveAttributeValue(CmsAttributeValueView valueView, int currentPosition, int targetPosition) {
  
          if (currentPosition == targetPosition) {
              return;
          }
          FlowPanel parent = (FlowPanel)valueView.getParent();
          m_widgetService.addChangedOrderPath(getSimplePath(-1));
          valueView.removeFromParent();
          m_attributeValueViews.remove(valueView);
          CmsAttributeValueView valueWidget = null;
          if (isChoiceHandler()) {
              removeHandlers(currentPosition);
              CmsEntity value = m_entity.getAttribute(m_attributeName).getComplexValues().get(currentPosition);
              m_entity.removeAttributeValue(m_attributeName, currentPosition);
              m_entity.insertAttributeValue(m_attributeName, value, targetPosition);
              String attributeChoice = getChoiceName(targetPosition);
              CmsType optionType = getAttributeType().getAttributeType(attributeChoice);
              valueWidget = new CmsAttributeValueView(
                  this,
                  m_widgetService.getAttributeLabel(attributeChoice),
                  m_widgetService.getAttributeHelp(attributeChoice));
              if (optionType.isSimpleType() && m_widgetService.isDisplaySingleLine(attributeChoice)) {
                  valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
              }
              parent.insert(valueWidget, targetPosition);
              insertHandlers(targetPosition);
              if (optionType.isSimpleType()) {
                  valueWidget.setValueWidget(
                      m_widgetService.getAttributeFormWidget(attributeChoice),
                      value.getAttribute(attributeChoice).getSimpleValue(),
                      m_widgetService.getDefaultAttributeValue(attributeChoice, getSimplePath(targetPosition)),
                      true);
              } else {
                  valueWidget.setValueEntity(
                      m_widgetService.getRendererForAttribute(attributeChoice, getAttributeType()),
                      value.getAttribute(attributeChoice).getComplexValue());
              }
  
              List<CmsChoiceMenuEntryBean> menuEntries = CmsRenderer.getChoiceEntries(getAttributeType(), true);
              for (CmsChoiceMenuEntryBean menuEntry : menuEntries) {
                  valueWidget.addChoice(m_widgetService, menuEntry);
              }
          } else if (getAttributeType().isSimpleType()) {
              String value = m_entity.getAttribute(m_attributeName).getSimpleValues().get(currentPosition);
              m_entity.removeAttributeValue(m_attributeName, currentPosition);
              m_entity.insertAttributeValue(m_attributeName, value, targetPosition);
              valueWidget = new CmsAttributeValueView(
                  this,
                  m_widgetService.getAttributeLabel(m_attributeName),
                  m_widgetService.getAttributeHelp(m_attributeName));
              if (m_widgetService.isDisplaySingleLine(m_attributeName)) {
                  valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
              }
              parent.insert(valueWidget, targetPosition);
              valueWidget.setValueWidget(
                  m_widgetService.getAttributeFormWidget(m_attributeName),
                  value,
                  m_widgetService.getDefaultAttributeValue(m_attributeName, getSimplePath(targetPosition)),
                  true);
          } else {
              removeHandlers(currentPosition);
              CmsEntity value = m_entity.getAttribute(m_attributeName).getComplexValues().get(currentPosition);
              m_entity.removeAttributeValue(m_attributeName, currentPosition);
              m_entity.insertAttributeValue(m_attributeName, value, targetPosition);
              valueWidget = new CmsAttributeValueView(
                  this,
                  m_widgetService.getAttributeLabel(m_attributeName),
                  m_widgetService.getAttributeHelp(m_attributeName));
              parent.insert(valueWidget, targetPosition);
              insertHandlers(targetPosition);
              valueWidget.setValueEntity(
                  m_widgetService.getRendererForAttribute(m_attributeName, getAttributeType()),
                  value);
              if (getAttributeType().getAttributeType(CmsType.CHOICE_ATTRIBUTE_NAME) != null) {
                  List<CmsChoiceMenuEntryBean> menuEntries = CmsRenderer.getChoiceEntries(getAttributeType(), false);
                  for (CmsChoiceMenuEntryBean entry : menuEntries) {
                      valueWidget.addChoice(m_widgetService, entry);
                  }
              }
          }
          m_attributeValueViews.remove(valueWidget);
          m_attributeValueViews.add(targetPosition, valueWidget);
          updateButtonVisisbility();
          CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
          if (handler.isIntitalized()) {
              handler.addChange(m_entity.getId(), m_attributeName, 0, ChangeType.sort);
          }
      }
  
      /**
       * Moves the reference value down in the value list.<p>
       *
       * @param reference the reference value
       */
      public void moveAttributeValueDown(final CmsAttributeValueView reference) {
  
          final int index = reference.getValueIndex();
          if (index >= (m_entity.getAttribute(m_attributeName).getValueCount() - 1)) {
              return;
          }
          m_widgetService.addChangedOrderPath(getSimplePath(-1));
          reference.hideAllButtons();
          Element parent = reference.getElement().getParentElement();
          parent.getStyle().setPosition(Position.RELATIVE);
          final Element placeHolder = reference.getPlaceholder(null);
          int top = reference.getElement().getOffsetTop();
          int left = reference.getElement().getOffsetLeft();
          int width = reference.getOffsetWidth();
          reference.getElement().getStyle().setPosition(Position.ABSOLUTE);
          reference.getElement().getStyle().setZIndex(5);
          parent.insertAfter(placeHolder, reference.getElement().getNextSibling());
          reference.getElement().getStyle().setTop(top, Unit.PX);
          reference.getElement().getStyle().setLeft(left, Unit.PX);
          reference.getElement().getStyle().setWidth(width, Unit.PX);
          new CmsMoveAnimation(reference.getElement(), top, left, placeHolder.getOffsetTop(), left, new Command() {
  
              public void execute() {
  
                  clearMoveAnimationStyles(placeHolder, reference);
                  moveAttributeValue(reference, index, index + 1);
  
              }
          }).run(200);
          CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
          if (handler.isIntitalized()) {
              handler.addChange(m_entity.getId(), m_attributeName, 0, ChangeType.sort);
          }
      }
  
      /**
       * Moves the reference value up in the value list.<p>
       *
       * @param reference the reference value
       */
      public void moveAttributeValueUp(final CmsAttributeValueView reference) {
  
          final int index = reference.getValueIndex();
          if (index == 0) {
              return;
          }
          m_widgetService.addChangedOrderPath(getSimplePath(-1));
          reference.hideAllButtons();
          Element parent = reference.getElement().getParentElement();
          parent.getStyle().setPosition(Position.RELATIVE);
          final Element placeHolder = reference.getPlaceholder(null);
          int top = reference.getElement().getOffsetTop();
          int left = reference.getElement().getOffsetLeft();
          int width = reference.getOffsetWidth();
          reference.getElement().getStyle().setPosition(Position.ABSOLUTE);
          reference.getElement().getStyle().setZIndex(5);
          parent.insertBefore(placeHolder, reference.getElement().getPreviousSibling());
          reference.getElement().getStyle().setTop(top, Unit.PX);
          reference.getElement().getStyle().setLeft(left, Unit.PX);
          reference.getElement().getStyle().setWidth(width, Unit.PX);
          new CmsMoveAnimation(reference.getElement(), top, left, placeHolder.getOffsetTop(), left, new Command() {
  
              public void execute() {
  
                  clearMoveAnimationStyles(placeHolder, reference);
                  moveAttributeValue(reference, index, index - 1);
  
              }
          }).run(200);
          CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
          if (handler.isIntitalized()) {
              handler.addChange(m_entity.getId(), m_attributeName, 0, ChangeType.sort);
          }
      }
  
      /**
       * Registers an attribute value view.<p>
       *
       * @param attributeValue the attribute value view
       */
      public void registerAttributeValue(CmsAttributeValueView attributeValue) {
  
          m_attributeValueViews.add(attributeValue);
      }
  
      /**
       * Removes the reference attribute value view.<p>
       *
       * @param reference the reference view
       */
      public void removeAttributeValue(CmsAttributeValueView reference) {
  
          removeAttributeValue(reference, false);
      }
  
      /**
       * Removes the reference attribute value view.<p>
       *
       * @param reference the reference view
       * @param force <code>true</code> if the widget should be removed even if it is the last one
       */
      public void removeAttributeValue(CmsAttributeValueView reference, boolean force) {
  
          CmsAttributeHandler parentHandler = null;
          CmsAttributeValueView parentView = null;
          boolean removeParent = false;
  
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          if (isChoiceHandler() && attribute.isSingleValue()) {
              // removing last choice value, so remove choice itself
              parentHandler = (CmsAttributeHandler)m_parentHandler;
              parentView = reference.getParentView();
              removeParent = true;
          }
  
          if (attribute.isSingleValue() && !force) {
              reference.removeValue();
              if (!attribute.isSimpleValue()) {
                  removeHandlers(0);
                  List<CmsEntity> entityValues = attribute.getComplexValues();
                  if (entityValues.size() >= 1) { // this should be always 1, but make it conditional to be on the safe side
                      CmsEntity entityToRemove = entityValues.get(0);
                      m_entityBackEnd.removeEntity(entityToRemove.getId());
                  }
              }
              m_entity.removeAttribute(m_attributeName);
          } else {
              int index = reference.getValueIndex();
              if (attribute.isComplexValue()) {
                  removeHandlers(index);
                  CmsEntity value = attribute.getComplexValues().get(index);
                  m_entity.removeAttributeValue(m_attributeName, index);
                  m_entityBackEnd.removeEntity(value.getId());
              } else {
                  m_entity.removeAttributeValue(m_attributeName, index);
              }
              reference.removeFromParent();
              m_attributeValueViews.remove(reference);
  
          }
          updateButtonVisisbility();
          if (removeParent && (parentHandler != null) && (parentView != null)) {
              parentHandler.removeAttributeValue(parentView);
              parentView.setCollapsed(false);
          }
          CmsUndoRedoHandler handler = CmsUndoRedoHandler.getInstance();
          if (handler.isIntitalized()) {
              handler.addChange(m_entity.getId(), m_attributeName, 0, ChangeType.remove);
          }
  
      }
  
      /**
       * Removes the attribute value from the given index, also manipulating the editor DOM to display the change.<p>
       *
       * @param valueIndex the value index
       */
      public void removeAttributeValue(int valueIndex) {
  
          if (m_attributeValueViews.size() > valueIndex) {
              removeAttributeValue(m_attributeValueViews.get(valueIndex));
          }
      }
  
      /**
       * Removes the attribute value (and corresponding widget) with the given index, and returns
       * the parent widget.<p>
       *
       * @param valueIndex the value index
       * @param force <code>true</code> if the widget should be removed even if it is the last one
       *
       * @return the parent widget
       */
      public Panel removeAttributeValueAndReturnPrevParent(int valueIndex, boolean force) {
  
          if (m_attributeValueViews.size() > valueIndex) {
              CmsAttributeValueView view = m_attributeValueViews.get(valueIndex);
              Panel result = (Panel)view.getParent();
              removeAttributeValue(view, force);
              return result;
          }
          return null;
  
      }
  
      /**
       * Removes the attribute value with the given index.<p>
       * This will not execute any DOM manipulations.<p>
       *
       * @param valueIndex the index of the attribute value to remove
       */
      public void removeAttributeValueFromEntity(int valueIndex) {
  
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          if (attribute.isSingleValue()) {
              if (attribute.isComplexValue()) {
                  removeHandlers(0);
              }
              m_entity.removeAttribute(m_attributeName);
          } else {
              if (attribute.isComplexValue()) {
                  removeHandlers(valueIndex);
                  CmsEntity value = attribute.getComplexValues().get(valueIndex);
                  m_entity.removeAttributeValue(m_attributeName, valueIndex);
                  m_entityBackEnd.removeEntity(value.getId());
              } else {
                  m_entity.removeAttributeValue(m_attributeName, valueIndex);
              }
          }
      }
  
      /**
       * Removes validation messages for all views associated with this attribute handler.<p>
       */
      public void removeValidationMessages() {
  
          for (CmsAttributeValueView view : m_attributeValueViews) {
              view.removeValidationMessage();
          }
      }
  
      /**
       * Sets the error message for the given value index.<p>
       *
       * @param valueIndex the value index
       * @param message the error message
       * @param tabbedPanel the forms tabbed panel if available
       */
      public void setErrorMessage(int valueIndex, String message, CmsTabbedPanel<?> tabbedPanel) {
  
          if (!m_attributeValueViews.isEmpty()) {
              FlowPanel parent = (FlowPanel)m_attributeValueViews.get(0).getParent();
              CmsAttributeValueView valueView = (CmsAttributeValueView)parent.getWidget(valueIndex);
              valueView.setErrorMessage(message);
              if (tabbedPanel != null) {
                  int tabIndex = tabbedPanel.getTabIndex(valueView.getElement());
                  if (tabIndex > -1) {
                      Widget tab = tabbedPanel.getTabWidget(tabIndex);
                      tab.setTitle("This tab has errors.");
                      tab.getParent().removeStyleName(I_CmsLayoutBundle.INSTANCE.form().hasWarning());
                      tab.getParent().addStyleName(I_CmsLayoutBundle.INSTANCE.form().hasError());
                  }
  
              }
          }
      }
  
      /**
       * @see org.opencms.acacia.client.CmsRootHandler#setHandlerById(java.lang.String, org.opencms.acacia.client.CmsAttributeHandler)
       */
      @Override
      public void setHandlerById(String attributeName, CmsAttributeHandler handler) {
  
          if (m_parentHandler != null) {
              m_parentHandler.setHandlerById(attributeName, handler);
          }
      }
  
      /**
       * Sets the parent attribute handler.<p>
       *
       * @param handler the parent attribute handler
       */
      public void setParentHandler(I_CmsAttributeHandler handler) {
  
          m_parentHandler = handler;
      }
  
      /**
       * Sets the warning message for the given value index.<p>
       *
       * @param valueIndex the value index
       * @param message the warning message
       * @param tabbedPanel the forms tabbed panel if available
       */
      public void setWarningMessage(int valueIndex, String message, CmsTabbedPanel<?> tabbedPanel) {
  
          if (!m_attributeValueViews.isEmpty()) {
              FlowPanel parent = (FlowPanel)m_attributeValueViews.get(0).getParent();
              CmsAttributeValueView valueView = (CmsAttributeValueView)parent.getWidget(valueIndex);
              valueView.setWarningMessage(message);
              if (tabbedPanel != null) {
                  int tabIndex = tabbedPanel.getTabIndex(valueView.getElement());
                  if (tabIndex > -1) {
                      Widget tab = tabbedPanel.getTabWidget(tabIndex);
                      tab.setTitle("This tab has warnings.");
                      tab.getParent().addStyleName(I_CmsLayoutBundle.INSTANCE.form().hasWarning());
                  }
  
              }
          }
      }
  
      /**
       * Updates the add, remove and sort button visibility on the given inline widget or all registered attribute value views.<p>
       *
       * @param inlineWidget the inline widget
       */
      public void updateButtonVisibilty(CmsInlineEntityWidget inlineWidget) {
  
          int minOccurrence = 0;
          int maxOccurrence = 0;
          if (isChoiceHandler()) {
              minOccurrence = 0;
              maxOccurrence = getEntityType().getChoiceMaxOccurrence();
          } else {
              minOccurrence = getEntityType().getAttributeMinOccurrence(m_attributeName);
              maxOccurrence = getEntityType().getAttributeMaxOccurrence(m_attributeName);
          }
          CmsEntityAttribute attribute = m_entity.getAttribute(m_attributeName);
          boolean mayHaveMore = (maxOccurrence > minOccurrence)
              && ((((attribute == null) && (!getAttributeType().isSimpleType() || (inlineWidget != null)))
                  || ((attribute != null) && (attribute.getValueCount() < maxOccurrence))));
          boolean needsRemove = false;
          boolean needsSort = false;
          if ((isChoiceHandler() || !getEntityType().isChoice()) && m_entity.hasAttribute(m_attributeName)) {
              int valueCount = m_entity.getAttribute(m_attributeName).getValueCount();
              needsRemove = (maxOccurrence > minOccurrence) && (valueCount > minOccurrence);
              needsSort = !isSingleValueHandler() && (valueCount > 1);
          }
          if (inlineWidget != null) {
              boolean mayEdit = (attribute != null) && (attribute.getValueCount() > inlineWidget.getAttributeIndex());
              inlineWidget.updateButtonVisibility(mayEdit, mayHaveMore, needsRemove, needsSort);
          } else {
              for (CmsAttributeValueView value : m_attributeValueViews) {
                  value.updateButtonVisibility(mayHaveMore, needsRemove, needsSort);
              }
          }
      }
  
      /**
       * Updates the add, remove and sort button visibility on all registered attribute value views.<p>
       */
      public void updateButtonVisisbility() {
  
          updateButtonVisibilty(null);
      }
  
      /**
       * Returns if the attribute handler is handling a single value only.<p>
       *
       * @return <code>true</code> if the attribute handler is handling a single value only
       */
      protected boolean isSingleValueHandler() {
  
          return m_singleValueIndex > -1;
      }
  
      /**
       * Sets the single value index.<p>
       *
       * @param valueIndex the value index
       */
      protected void setSingleValueIndex(int valueIndex) {
  
          m_singleValueIndex = valueIndex;
      }
  
      /**
       * Clears the inline styles used during move animation.<p>
       *
       * @param placeHolder the animation place holder
       * @param reference the moved attribute widget
       */
      void clearMoveAnimationStyles(Element placeHolder, CmsAttributeValueView reference) {
  
          placeHolder.removeFromParent();
          reference.getElement().getParentElement().getStyle().clearPosition();
          reference.getElement().getStyle().clearPosition();
          reference.getElement().getStyle().clearWidth();
          reference.getElement().getStyle().clearZIndex();
          reference.showButtons();
      }
  
      /**
       * Adds a new choice option.<p>
       *
       * @param reference the reference view
       * @param choicePath the choice attribute path
       */
      private void addChoiceOption(CmsAttributeValueView reference, List<String> choicePath) {
  
          String attributeChoice = choicePath.get(0);
          CmsType optionType = getAttributeType().getAttributeType(attributeChoice);
          int valueIndex = reference.getValueIndex() + 1;
          CmsEntity choiceEntity = m_entityBackEnd.createEntity(null, getAttributeType().getId());
          CmsAttributeValueView valueWidget = reference;
          if (reference.hasValue()) {
              valueWidget = new CmsAttributeValueView(
                  this,
                  m_widgetService.getAttributeLabel(attributeChoice),
                  m_widgetService.getAttributeHelp(attributeChoice));
              if (optionType.isSimpleType() && m_widgetService.isDisplaySingleLine(attributeChoice)) {
                  valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
              }
          }
  
          List<CmsChoiceMenuEntryBean> menuEntries = CmsRenderer.getChoiceEntries(getAttributeType(), true);
          for (CmsChoiceMenuEntryBean menuEntry : menuEntries) {
              valueWidget.addChoice(m_widgetService, menuEntry);
          }
  
          m_entity.insertAttributeValue(m_attributeName, choiceEntity, valueIndex);
          ((FlowPanel)reference.getParent()).insert(valueWidget, valueIndex);
          insertHandlers(valueWidget.getValueIndex());
  
          if (optionType.isSimpleType()) {
              String defaultValue = m_widgetService.getDefaultAttributeValue(attributeChoice, getSimplePath(valueIndex));
              I_CmsFormEditWidget widget = m_widgetService.getAttributeFormWidget(attributeChoice);
              choiceEntity.addAttributeValue(attributeChoice, defaultValue);
              valueWidget.setValueWidget(widget, defaultValue, defaultValue, true);
          } else {
              CmsEntity value = m_entityBackEnd.createEntity(null, optionType.getId());
              choiceEntity.addAttributeValue(attributeChoice, value);
              List<String> remainingAttributeNames = tail(choicePath);
              createNestedEntitiesForChoicePath(value, remainingAttributeNames);
              I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(attributeChoice, optionType);
              valueWidget.setValueEntity(renderer, value);
          }
          updateButtonVisisbility();
  
      }
  
      /**
       * Adds a new complex value which corresponds to a choice element.<p>
       *
       * @param reference the reference view
       * @param choicePath the path of choice attribute names
       */
      private void addComplexChoiceValue(CmsAttributeValueView reference, List<String> choicePath) {
  
          CmsEntity value = m_entityBackEnd.createEntity(null, getAttributeType().getId());
          CmsEntity parentValue = value;
          for (String attributeChoice : choicePath) {
              CmsType choiceType = m_entityBackEnd.getType(parentValue.getTypeName()).getAttributeType(
                  CmsType.CHOICE_ATTRIBUTE_NAME);
              CmsEntity choice = m_entityBackEnd.createEntity(null, choiceType.getId());
              parentValue.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, choice);
              CmsType choiceOptionType = choiceType.getAttributeType(attributeChoice);
              if (choiceOptionType.isSimpleType()) {
                  String choiceValue = m_widgetService.getDefaultAttributeValue(attributeChoice, getSimplePath(0));
                  choice.addAttributeValue(attributeChoice, choiceValue);
                  break;
              } else {
                  CmsEntity choiceValue = m_entityBackEnd.createEntity(null, choiceOptionType.getId());
                  choice.addAttributeValue(attributeChoice, choiceValue);
                  parentValue = choiceValue;
              }
          }
          insertValueAfterReference(value, reference);
          if (getMaxOccurence() == 1) {
              reference.setCollapsed(true);
          }
      }
  
      /**
       * Changes the attribute value.<p>
       *
       * @param valueIndex the attribute value index
       * @param value the value
       */
      private void changeEntityValue(String value, int valueIndex) {
  
          if (getEntityType().isChoice()) {
              CmsEntity choice = m_entity.getAttribute(CmsType.CHOICE_ATTRIBUTE_NAME).getComplexValues().get(valueIndex);
              String attributeName = getChoiceName(valueIndex);
              if (attributeName != null) {
                  choice.setAttributeValue(attributeName, value, 0);
              }
          } else {
              m_entity.setAttributeValue(m_attributeName, value, valueIndex);
          }
      }
  
      /**
       * Returns the attribute choice name for the given index.<p>
       *
       * @param valueIndex the value index
       *
       * @return the attribute choice name
       */
      private String getChoiceName(int valueIndex) {
  
          if (isChoiceHandler()) {
              CmsEntity choice = m_entity.getAttribute(CmsType.CHOICE_ATTRIBUTE_NAME).getComplexValues().get(valueIndex);
              if (choice != null) {
                  for (String option : getAttributeType().getAttributeNames()) {
                      if (choice.hasAttribute(option)) {
                          return option;
  
                      }
                  }
              }
          }
          return null;
      }
  
      /**
       * Returns the entity type.<p>
       *
       * @return the entity type
       */
      private CmsType getEntityType() {
  
          if (m_entityType == null) {
              m_entityType = m_entityBackEnd.getType(m_entity.getTypeName());
          }
          return m_entityType;
      }
  
      /**
       * Inserts an entity value after the given reference.<p>
       *
       * @param value the entity value
       * @param reference the reference
       */
      private void insertValueAfterReference(CmsEntity value, CmsAttributeValueView reference) {
  
          int valueIndex = -1;
          if (reference.getElement().getNextSiblingElement() == null) {
              m_entity.addAttributeValue(m_attributeName, value);
          } else {
              valueIndex = reference.getValueIndex() + 1;
              m_entity.insertAttributeValue(m_attributeName, value, valueIndex);
          }
          CmsAttributeValueView valueWidget = reference;
          if (reference.hasValue()) {
              valueWidget = new CmsAttributeValueView(
                  this,
                  m_widgetService.getAttributeLabel(m_attributeName),
                  m_widgetService.getAttributeHelp(m_attributeName));
              CmsRenderer.setAttributeChoice(m_widgetService, valueWidget, getAttributeType());
              if (valueIndex == -1) {
                  ((FlowPanel)reference.getParent()).add(valueWidget);
                  m_attributeValueViews.remove(valueWidget);
                  m_attributeValueViews.add(valueWidget);
              } else {
                  ((FlowPanel)reference.getParent()).insert(valueWidget, valueIndex);
                  m_attributeValueViews.remove(valueWidget);
                  m_attributeValueViews.add(valueIndex, valueWidget);
                  m_widgetService.addChangedOrderPath(getSimplePath(-1));
              }
  
          }
          valueIndex = valueWidget.getValueIndex();
          insertHandlers(valueIndex);
          I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(m_attributeName, getAttributeType());
          valueWidget.setValueEntity(renderer, value);
      }
  
      /**
       * Creates a list consisting of all but the first element of another list.<p>
       *
       * @param values the list
       *
       * @return the tail of the list
       */
      private List<String> tail(List<String> values) {
  
          List<String> result = new ArrayList<String>();
          boolean first = true;
          for (String value : values) {
              if (!first) {
                  result.add(value);
              }
              first = false;
          }
          return result;
  
      }
  }
  