/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (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.gwt.client.ui.contextmenu; import org.opencms.gwt.client.ui.CmsPopup; import org.opencms.gwt.client.ui.I_CmsAutoHider; import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; import org.opencms.gwt.client.ui.input.CmsLabel; import java.util.Iterator; import java.util.List; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; /** * A implementation for a context menu.<p> * * @since version 8.0.0 */ public class CmsContextMenu extends Composite implements ResizeHandler, I_CmsAutoHider { /** The menu auto hide parent. */ private I_CmsAutoHider m_autoHideParent; /** A Flag indicating if the position of the menu should be fixed. */ private boolean m_isFixed; /** The panel for the menu items. */ private FlowPanel m_panel = new FlowPanel(); /** The popup for a sub menu. */ private CmsPopup m_popup; /** Stores the selected item. */ private A_CmsContextMenuItem m_selectedItem; /** * Constructor.<p> * * @param menuData the data structure for the context menu * @param isFixed indicating if the position of the menu should be fixed. * @param autoHideParent the menu auto hide parent */ public CmsContextMenu(List<I_CmsContextMenuEntry> menuData, boolean isFixed, I_CmsAutoHider autoHideParent) { initWidget(m_panel); m_isFixed = isFixed; m_popup = new CmsPopup(); // clear the width and the padding of the popup content (needed for sub menus) m_popup.setWidth(0); m_popup.removePadding(); createContextMenu(menuData); setStyleName(I_CmsLayoutBundle.INSTANCE.contextmenuCss().cmsMenuBar()); m_autoHideParent = autoHideParent; } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#addAutoHidePartner(com.google.gwt.dom.client.Element) */ public void addAutoHidePartner(com.google.gwt.dom.client.Element partner) { m_autoHideParent.addAutoHidePartner(partner); } /** * Adds a menu item to this menu. * * @param item the item to be added */ public void addItem(A_CmsContextMenuItem item) { m_panel.add(item); item.setParentMenu(this); } /** * Adds a separator to this menu.<p> */ public void addSeparator() { CmsLabel sparator = new CmsLabel(); sparator.setStyleName(I_CmsLayoutBundle.INSTANCE.contextmenuCss().menuItemSeparator()); m_panel.add(sparator); } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#hide() */ public void hide() { m_autoHideParent.hide(); } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#isAutoHideEnabled() */ public boolean isAutoHideEnabled() { return m_autoHideParent.isAutoHideEnabled(); } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#isAutoHideOnHistoryEventsEnabled() */ public boolean isAutoHideOnHistoryEventsEnabled() { return m_autoHideParent.isAutoHideOnHistoryEventsEnabled(); } /** * If the browser's window is resized this method rearranges the sub menus of the selected item.<p> * * @see com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google.gwt.event.logical.shared.ResizeEvent) */ public void onResize(final ResizeEvent event) { if ((m_selectedItem != null) && m_selectedItem.hasSubmenu()) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { /** * @see com.google.gwt.user.client.Command#execute() */ public void execute() { getSelectedItem().getSubMenu().setSubMenuPosition(getSelectedItem()); getSelectedItem().getSubMenu().onResize(event); } }); } } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#removeAutoHidePartner(com.google.gwt.dom.client.Element) */ public void removeAutoHidePartner(com.google.gwt.dom.client.Element partner) { m_autoHideParent.removeAutoHidePartner(partner); } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#setAutoHideEnabled(boolean) */ public void setAutoHideEnabled(boolean autoHide) { m_autoHideParent.setAutoHideEnabled(autoHide); } /** * @see org.opencms.gwt.client.ui.I_CmsAutoHider#setAutoHideOnHistoryEventsEnabled(boolean) */ public void setAutoHideOnHistoryEventsEnabled(boolean enabled) { m_autoHideParent.setAutoHideOnHistoryEventsEnabled(enabled); } /** * Returns the selected item of this menu.<p> * * @return the selected item of this menu */ protected A_CmsContextMenuItem getSelectedItem() { return m_selectedItem; } /** * Action on close.<p> * * On close all sub menus should be hidden, the currently selected item should be deselected * and the popup will be closed.<p> */ protected void onClose() { if ((m_selectedItem != null)) { if (m_selectedItem.hasSubmenu()) { m_selectedItem.getSubMenu().onClose(); } m_selectedItem.deselectItem(); } m_popup.hide(); } /** * Opens a sub menu and sets its position.<p> * * @param item the item to show the sub menu of */ protected void openPopup(final A_CmsContextMenuItem item) { m_popup.add(item.getSubMenu()); m_popup.addAutoHidePartner(item.getElement()); m_popup.setModal(false); m_popup.show(); setSubMenuPosition(item); if (m_isFixed) { m_popup.setPositionFixed(); } } /** * Sets the selected item of this menu.<p> * * @param selectedItem the item to select */ protected void setSelectedItem(A_CmsContextMenuItem selectedItem) { m_selectedItem = selectedItem; } /** * Sets the position of the sub menu popup.<p> * * First calculates the best space where to show the popup.<p> * * The following list shows the possibilities, beginning * with the best and ending with the worst.<p> * * <ul> * <li>bottom-right</li> * <li>bottom-left</li> * <li>top-right</li> * <li>top-left</li> * </ul> * * Then the position (top and left coordinate) are calculated.<p> * * Finally the position of the sub menu popup is set to the calculated values.<p> * * @param item the item to show the sub menu of */ protected void setSubMenuPosition(final A_CmsContextMenuItem item) { // calculate the left space // add 10 because of the shadow and for avoiding that the browser's right window border touches the sub menu int leftSpace = item.getAbsoluteLeft() - (Window.getScrollLeft() + 10); // calculate the right space // add 10 because of the shadow and for avoiding that the browser's left window border touches the sub menu int rightSpace = Window.getClientWidth() - (item.getAbsoluteLeft() + item.getOffsetWidth() + 10); // if the width of the sub menu is smaller than the right space, show the sub menu on the right boolean showRight = item.getSubMenu().getOffsetWidth() < rightSpace; if (!showRight) { // if the width of the sub menu is larger than the right space, compare the left space with the right space // and show the sub menu on the right if on the right is more space than on the left showRight = leftSpace < rightSpace; } // calculate the top space // add 10 because of the shadow and for avoiding that the browser's top window border touches the sub menu int topSpace = item.getAbsoluteTop() - Window.getScrollTop() + 10; // calculate the bottom space // add 10 because of the shadow and for avoiding that the browser's bottom window border touches the sub menu int bottomSpace = Window.getClientHeight() + Window.getScrollTop() - (item.getAbsoluteTop() + 10); // if the height of the sub menu is smaller than the bottom space, show the sub menu on the bottom boolean showBottom = item.getSubMenu().getOffsetHeight() < bottomSpace; if (!showBottom) { // if the height of the sub menu is larger than the bottom space, compare the top space with // the bottom space and show the sub menu on the bottom if on the bottom is more space than on the top showBottom = topSpace < bottomSpace; } int left; int top; if (showRight && showBottom) { // bottom-right // if to show the sub menu on the right the left coordinate is on the right end of the item, so take the // item's absolute left and add the width of the item / by subtracting 4 the overlay it created left = item.getAbsoluteLeft() + item.getOffsetWidth() - 4; // if to show the sub menu on the bottom the top coordinate is on the top end of the item, so take the // item's absolute top and subtract the scroll top of Window / by subtracting 4 the shadow is balanced top = item.getAbsoluteTop() - Window.getScrollTop() - 4; } else if (!showRight && showBottom) { // bottom-left // if to show the sub menu on the left, the left coordinate is on the left end of the item plus the width // of the sub menu, so take the item's absolute left, subtract the sub menu's width and the scroll left // by subtracting 4 the overlay it created left = item.getAbsoluteLeft() - item.getSubMenu().getOffsetWidth() - Window.getScrollLeft() - 4; // if to show the sub menu on the bottom the top coordinate is on the top end of the item, so take the // item's absolute top and subtract the scroll top of Window / by subtracting 4 the shadow is balanced top = item.getAbsoluteTop() - Window.getScrollTop() - 4; } else if (showRight && !showBottom) { // top-right // if to show the sub menu on the right the left coordinate is on the right end of the item, so take the // item's absolute left and add the width of the item / by subtracting 4 the overlay it created left = item.getAbsoluteLeft() + item.getOffsetWidth() - 4; // if to show the sub menu on the top, the bottom-left corner of the item plus the height of the sub menu // is the top coordinate, so take the item's absolute top subtract the scroll top and the height of the sub // menu and add the height of the item / by subtracting 4 the shadow is balanced top = item.getAbsoluteTop() - Window.getScrollTop() - item.getSubMenu().getOffsetHeight() + item.getOffsetHeight() - 4; } else { // top-left // if to show the sub menu on the left, the left coordinate is on the left end of the item plus the width // of the sub menu, so take the item's absolute left, subtract the sub menu's width and the scroll left // by subtracting 4 the overlay it created left = item.getAbsoluteLeft() - Window.getScrollLeft() - item.getSubMenu().getOffsetWidth() - 4; // if to show the sub menu on the top, the bottom-left corner of the item plus the height of the sub menu // is the top coordinate, so take the item's absolute top subtract the scroll top and the height of the sub // menu and add the height of the item / by subtracting 4 the shadow is balanced top = item.getAbsoluteTop() - Window.getScrollTop() - item.getSubMenu().getOffsetHeight() + item.getOffsetHeight() - 4; } // finally set the position of the popup m_popup.setPopupPosition(left, top); } /** * Creates the context menu.<p> * * @param entries a list with all entries for the context menu */ private void createContextMenu(List<I_CmsContextMenuEntry> entries) { Iterator<I_CmsContextMenuEntry> it = entries.iterator(); while (it.hasNext()) { I_CmsContextMenuEntry entry = it.next(); if (!entry.isVisible()) { continue; } if (entry.isSeparator()) { addSeparator(); } else { if (entry.hasSubMenu()) { CmsContextMenuItem item = new CmsContextMenuItem(entry); item.setSubMenu(new CmsContextMenu(entry.getSubMenu(), m_isFixed, m_popup)); addItem(item); } else { addItem(new CmsContextMenuItem(entry)); } } } } }