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.css.I_CmsLayoutBundle;
  import org.opencms.acacia.client.ui.CmsAttributeChoiceWidget;
  import org.opencms.acacia.client.ui.CmsAttributeValueView;
  import org.opencms.acacia.client.ui.CmsChoiceMenuEntryWidget;
  import org.opencms.acacia.client.ui.CmsChoiceSubmenu;
  import org.opencms.acacia.client.ui.CmsInlineEntityWidget;
  import org.opencms.gwt.client.CmsCoreProvider;
  import org.opencms.gwt.client.util.CmsDomUtil;
  
  
  import java.util.ArrayList;
  import java.util.List;
  
  import com.google.gwt.dom.client.Element;
  import com.google.gwt.dom.client.EventTarget;
  import com.google.gwt.dom.client.NativeEvent;
  import com.google.gwt.event.dom.client.MouseOutEvent;
  import com.google.gwt.event.dom.client.MouseOutHandler;
  import com.google.gwt.event.dom.client.MouseOverEvent;
  import com.google.gwt.event.dom.client.MouseOverHandler;
  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.EventListener;
  import com.google.gwt.user.client.Timer;
  import com.google.gwt.user.client.rpc.AsyncCallback;
  import com.google.gwt.user.client.ui.Widget;
  
  /**
   * Helper class for controlling visibility of button hover bars of attribute value views.<p>
   */
  public final class CmsButtonBarHandler implements MouseOverHandler, MouseOutHandler {
  
      /** CSS class used for identifying widgets which normally react to hover events in touch-only mode. */
      public static final String HOVERABLE_MARKER = "oc-editor-hoverable-marker";
  
      /** Global instance of the button bar handler. */
      public static final CmsButtonBarHandler INSTANCE = new CmsButtonBarHandler();
  
      /** The timeout for hiding the buttons. */
      public static final int TIMEOUT = 900;
  
      /** The visible button bar.*/
      Widget m_buttonBar;
  
      /** The timer for hiding the button bar. */
      private Timer m_buttonBarTimer;
  
      /** The visible choice menu. */
      private CmsAttributeChoiceWidget m_choice;
  
      /** The timer for hiding the choice menu. */
      private Timer m_choiceTimer;
  
      /** The currently active submenus. */
      private List<CmsChoiceSubmenu> m_submenus = new ArrayList<CmsChoiceSubmenu>();
  
      /** The widget service. */
      private I_CmsWidgetService m_widgetService;
  
      /**
       * Constructor.<p>
       */
      private CmsButtonBarHandler() {
  
          Event.addNativePreviewHandler(new NativePreviewHandler() {
  
              public void onPreviewNativeEvent(NativePreviewEvent event) {
  
                  NativeEvent nativeEvent = event.getNativeEvent();
                  if ((event.getTypeInt() != Event.ONMOUSEDOWN) && (event.getTypeInt() != Event.ONCLICK)) {
                      return;
                  }
                  if (nativeEvent == null) {
                      return;
                  }
  
                  if (handleSimulatedHoverForTouchOnlyDevices(event)) {
                      return;
                  }
  
                  if (m_buttonBar == null) {
                      return;
                  }
                  EventTarget target = nativeEvent.getEventTarget();
  
                  if (Element.is(target)) {
                      Element targetElement = Element.as(target);
                      boolean clickedOnMenu = m_buttonBar.getElement().isOrHasChild(targetElement);
                      if (!clickedOnMenu) {
                          closeAll();
  
                      }
                  }
              }
  
              private boolean handleSimulatedHoverForTouchOnlyDevices(NativePreviewEvent previewEvent) {
  
                  NativeEvent nativeEvent = previewEvent.getNativeEvent();
                  boolean isMouseDown = previewEvent.getTypeInt() == Event.ONMOUSEDOWN;
  
                  if (!CmsCoreProvider.isTouchOnly()) {
                      return false;
                  }
  
                  EventTarget target = nativeEvent.getEventTarget();
                  if (Element.is(target)) {
                      Element element = Element.as(target);
                      Element widgetElement = CmsDomUtil.getAncestor(element, HOVERABLE_MARKER);
                      if (widgetElement != null) {
                          EventListener widget = com.google.gwt.user.client.DOM.getEventListener(widgetElement);
                          if (useClickAsFakeHover(widget, isMouseDown)) {
                              previewEvent.cancel();
                          }
                      }
                  }
                  return false;
              }
          });
          m_choiceTimer = new Timer() {
  
              @Override
              public void run() {
  
                  closeAllChoices();
              }
          };
          m_buttonBarTimer = new Timer() {
  
              @Override
              public void run() {
  
                  closeAll();
              }
          };
      }
  
      /**
       * Closes all visible button bars and menus.<p>
       */
      public void closeAll() {
  
          if (m_buttonBar != null) {
              setButtonBarVisibility(m_buttonBar, false);
              m_buttonBar = null;
          }
          closeAllChoices();
      }
  
      /**
       * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent)
       */
      public void onMouseOut(MouseOutEvent event) {
  
          if (CmsCoreProvider.isTouchOnly()) {
              return;
          }
  
          Object source = event.getSource();
          if ((source instanceof CmsAttributeChoiceWidget) || (source instanceof CmsChoiceMenuEntryWidget)) {
              rescheduleChoiceTimer();
          } else {
              rescheduleButtonBarTimer();
          }
      }
  
      /**
       * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent)
       */
      public void onMouseOver(MouseOverEvent event) {
  
          if (CmsCoreProvider.isTouchOnly()) {
              return;
          }
          cancelButtonBarTimer();
          Object source = event.getSource();
          if (source instanceof CmsAttributeChoiceWidget) {
              overAttributeChoice((CmsAttributeChoiceWidget)source);
          } else if (source instanceof CmsChoiceMenuEntryWidget) {
              overChoiceEntry((CmsChoiceMenuEntryWidget)source);
          } else {
              overButtonBar((Widget)source);
          }
      }
  
      /**
       * Adds a new submenu.<p>
       *
       * @param entryWidget the entry widget whose children should be added to the submenu
       */
      protected void addSubmenu(CmsChoiceMenuEntryWidget entryWidget) {
  
          CmsChoiceMenuEntryBean menuEntry = entryWidget.getEntryBean();
          AsyncCallback<CmsChoiceMenuEntryBean> selectHandler = entryWidget.getSelectHandler();
          CmsAttributeChoiceWidget choiceWidget = entryWidget.getAttributeChoiceWidget();
          CmsChoiceSubmenu submenu = new CmsChoiceSubmenu(menuEntry);
          submenu.positionDeferred(entryWidget);
          choiceWidget.getSubmenuPanel().add(submenu);
          m_submenus.add(submenu);
          for (CmsChoiceMenuEntryBean subEntry : menuEntry.getChildren()) {
              submenu.addChoice(
                  new CmsChoiceMenuEntryWidget(
                      m_widgetService.getAttributeLabel(subEntry.getPathComponent()),
                      m_widgetService.getAttributeHelp(subEntry.getPathComponent()),
                      subEntry,
                      selectHandler,
                      choiceWidget,
                      submenu));
          }
      }
  
      /**
       * Removes unnecessary submenus when the user hovers over a given menu entry.<p>
       *
       * @param entryWidget the menu entry over which the user is hovering
       */
      protected void cleanUpSubmenus(CmsChoiceMenuEntryWidget entryWidget) {
  
          CmsChoiceSubmenu submenu = entryWidget.getSubmenu();
          // First remove all submenus which are deeper than the submenu in which the current entry is located
          while (!m_submenus.isEmpty() && (getLastSubmenu() != submenu)) {
              removeSubmenu(getLastSubmenu());
          }
          // if it is a root entry, switch the attribute choice widget
          if (submenu == null) {
              CmsAttributeChoiceWidget choiceWidget = entryWidget.getAttributeChoiceWidget();
              if (choiceWidget != m_choice) {
                  closeAllChoices();
                  m_choice = choiceWidget;
              }
          }
      }
  
      /**
       * Gets the last entry in the current list of active submenus.<p>
       *
       * @return the last submenu
       */
      protected CmsChoiceSubmenu getLastSubmenu() {
  
          return m_submenus.get(m_submenus.size() - 1);
      }
  
      /**
       * Removes a submenu and hides it.<p>
       *
       * @param submenu the submenu to remove
       */
      protected void removeSubmenu(CmsChoiceSubmenu submenu) {
  
          submenu.removeFromParent();
          m_submenus.remove(submenu);
      }
  
      /**
       * Sets the widget service.<p>
       *
       * @param widgetService the widget service
       */
      protected void setWidgetService(I_CmsWidgetService widgetService) {
  
          m_widgetService = widgetService;
      }
  
      /**
       * In touch-only mode, this is used to process some clicks to trigger actions that would be normally triggered by hovering over the same
       * GUI element.
       *
       * @param source the event source
       * @param isMouseDown true if the event is a mousedown event
       *
       * @return true if the event should be cancelled
       */
      protected boolean useClickAsFakeHover(EventListener source, boolean isMouseDown) {
  
          boolean cancel = false;
          if (source instanceof CmsAttributeChoiceWidget) {
              if (isMouseDown) {
                  return false;
              }
              cancel = source != m_choice;
              overAttributeChoice((CmsAttributeChoiceWidget)source);
          } else if (source instanceof CmsChoiceMenuEntryWidget) {
              if (isMouseDown) {
                  return false;
              }
              overChoiceEntry((CmsChoiceMenuEntryWidget)source);
          } else {
              if (m_buttonBar != source) {
                  // We either have a real menu bar or just a single button.
                  // In the first case we have to show the menu bar and cancel the click event, in the second case we don't.
                  // We distinguish between the two cases with a special attribute.
                  String hasHoverStr = ((Widget)source).getElement().getAttribute(CmsAttributeValueView.ATTR_HAS_HOVER);
                  if (Boolean.parseBoolean(hasHoverStr)) {
                      overButtonBar((Widget)source);
                      cancel = true;
                  }
              }
          }
          return cancel;
      }
  
      /**
       * Closes all currently active submenus and the root menu.<p>
       */
      void closeAllChoices() {
  
          if (m_choice != null) {
              m_choice.hide();
          }
          m_choice = null;
          for (CmsChoiceSubmenu submenu : new ArrayList<CmsChoiceSubmenu>(m_submenus)) {
              removeSubmenu(submenu);
          }
      }
  
      /**
       * Cancels the timer.<p>
       */
      private void cancelButtonBarTimer() {
  
          m_buttonBarTimer.cancel();
      }
  
      /**
       * Cancels the timer.<p>
       */
      private void cancelChoiceTimer() {
  
          m_choiceTimer.cancel();
      }
  
      /**
       * Handles the mouse over event for a choice widget.<p>
       *
       * @param choice the event source
       */
      private void overAttributeChoice(CmsAttributeChoiceWidget choice) {
  
          cancelChoiceTimer();
          if (choice.getParent() != m_buttonBar) {
              closeAll();
              m_buttonBar = choice.getParent();
              setButtonBarVisibility(m_buttonBar, true);
          }
          if (m_choice != choice) {
              closeAllChoices();
              m_choice = choice;
              m_choice.show();
          }
      }
  
      /**
       * Handles the mouse over event for a button bar.<p>
       *
       * @param buttonBar the event source
       */
      private void overButtonBar(Widget buttonBar) {
  
          if ((m_buttonBar == null) || (buttonBar.getElement() != m_buttonBar.getElement())) {
              closeAll();
              m_buttonBar = buttonBar;
              setButtonBarVisibility(m_buttonBar, true);
          }
      }
  
      /**
       * Handles the mouse over event for a choice menu entry.<p>
       *
       * @param entryWidget the event source
       */
      private void overChoiceEntry(CmsChoiceMenuEntryWidget entryWidget) {
  
          cancelChoiceTimer();
          cleanUpSubmenus(entryWidget);
          CmsChoiceMenuEntryBean entryBean = entryWidget.getEntryBean();
          if (!entryBean.isLeaf()) {
              addSubmenu(entryWidget);
          }
      }
  
      /**
       * Reschedules the timer that hides the currently visible button bar.<p>
       */
      private void rescheduleButtonBarTimer() {
  
          m_buttonBarTimer.cancel();
          m_buttonBarTimer.schedule(TIMEOUT);
      }
  
      /**
       * Reschedules the timer that hides the currently visible choice menu.<p>
       */
      private void rescheduleChoiceTimer() {
  
          m_choiceTimer.cancel();
          m_choiceTimer.schedule(TIMEOUT);
      }
  
      /**
       * Sets the button bar visibility.<p>
       *
       * @param buttonBar the button bar
       * @param visible <code>true</code> to show the button bar
       */
      private void setButtonBarVisibility(Widget buttonBar, boolean visible) {
  
          String hoverStyle = I_CmsLayoutBundle.INSTANCE.form().hoverButton();
          if (visible) {
              buttonBar.addStyleName(hoverStyle);
          } else {
              buttonBar.removeStyleName(hoverStyle);
          }
          if (buttonBar instanceof CmsInlineEntityWidget) {
              ((CmsInlineEntityWidget)buttonBar).setContentHighlightingVisible(visible);
          }
          if (buttonBar.getParent() instanceof CmsInlineEntityWidget) {
              ((CmsInlineEntityWidget)buttonBar.getParent()).setContentHighlightingVisible(visible);
          }
      }
  }
  