001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.ui.contextmenu; 029 030import org.opencms.gwt.client.ui.CmsPopup; 031import org.opencms.gwt.client.ui.I_CmsAutoHider; 032import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 033import org.opencms.gwt.client.ui.input.CmsLabel; 034 035import java.util.Iterator; 036import java.util.List; 037 038import com.google.gwt.core.client.Scheduler; 039import com.google.gwt.core.client.Scheduler.ScheduledCommand; 040import com.google.gwt.event.logical.shared.ResizeEvent; 041import com.google.gwt.event.logical.shared.ResizeHandler; 042import com.google.gwt.user.client.Window; 043import com.google.gwt.user.client.ui.Composite; 044import com.google.gwt.user.client.ui.FlowPanel; 045 046/** 047 * A implementation for a context menu.<p> 048 * 049 * @since version 8.0.0 050 */ 051public class CmsContextMenu extends Composite implements ResizeHandler, I_CmsAutoHider { 052 053 /** The menu auto hide parent. */ 054 private I_CmsAutoHider m_autoHideParent; 055 056 /** A Flag indicating if the position of the menu should be fixed. */ 057 private boolean m_isFixed; 058 059 /** The panel for the menu items. */ 060 private FlowPanel m_panel = new FlowPanel(); 061 062 /** The parent item. */ 063 private A_CmsContextMenuItem m_parentItem; 064 065 /** The popup for a sub menu. */ 066 private CmsPopup m_popup; 067 068 /** Stores the selected item. */ 069 private A_CmsContextMenuItem m_selectedItem; 070 071 /** 072 * Constructor.<p> 073 * 074 * @param menuData the data structure for the context menu 075 * @param isFixed indicating if the position of the menu should be fixed. 076 * @param autoHideParent the menu auto hide parent 077 */ 078 public CmsContextMenu(List<I_CmsContextMenuEntry> menuData, boolean isFixed, I_CmsAutoHider autoHideParent) { 079 080 initWidget(m_panel); 081 m_isFixed = isFixed; 082 m_popup = new CmsPopup(); 083 // clear the width and the padding of the popup content (needed for sub menus) 084 m_popup.setWidth(0); 085 m_popup.removePadding(); 086 m_popup.addStyleName(I_CmsLayoutBundle.INSTANCE.dialogCss().contextMenu()); 087 createContextMenu(menuData); 088 setStyleName(I_CmsLayoutBundle.INSTANCE.contextmenuCss().cmsMenuBar()); 089 m_autoHideParent = autoHideParent; 090 } 091 092 /** 093 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#addAutoHidePartner(com.google.gwt.dom.client.Element) 094 */ 095 public void addAutoHidePartner(com.google.gwt.dom.client.Element partner) { 096 097 m_autoHideParent.addAutoHidePartner(partner); 098 } 099 100 /** 101 * Adds a menu item to this menu. 102 * 103 * @param item the item to be added 104 */ 105 public void addItem(A_CmsContextMenuItem item) { 106 107 m_panel.add(item); 108 item.setParentMenu(this); 109 } 110 111 /** 112 * Adds a separator to this menu.<p> 113 */ 114 public void addSeparator() { 115 116 CmsLabel sparator = new CmsLabel(); 117 sparator.setStyleName(I_CmsLayoutBundle.INSTANCE.contextmenuCss().menuItemSeparator()); 118 m_panel.add(sparator); 119 } 120 121 /** 122 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#hide() 123 */ 124 public void hide() { 125 126 m_autoHideParent.hide(); 127 } 128 129 /** 130 * Hides this menu and all its parent menus.<p> 131 */ 132 public void hideAll() { 133 134 CmsContextMenu currentMenu = this; 135 int i = 0; 136 while ((currentMenu != null) && (i < 10)) { 137 currentMenu.hide(); 138 currentMenu = currentMenu.getParentMenu(); 139 i += 1; 140 } 141 } 142 143 /** 144 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#isAutoHideEnabled() 145 */ 146 public boolean isAutoHideEnabled() { 147 148 return m_autoHideParent.isAutoHideEnabled(); 149 } 150 151 /** 152 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#isAutoHideOnHistoryEventsEnabled() 153 */ 154 public boolean isAutoHideOnHistoryEventsEnabled() { 155 156 return m_autoHideParent.isAutoHideOnHistoryEventsEnabled(); 157 } 158 159 /** 160 * If the browser's window is resized this method rearranges the sub menus of the selected item.<p> 161 * 162 * @see com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google.gwt.event.logical.shared.ResizeEvent) 163 */ 164 public void onResize(final ResizeEvent event) { 165 166 if ((m_selectedItem != null) && m_selectedItem.hasSubmenu()) { 167 168 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 169 170 /** 171 * @see com.google.gwt.user.client.Command#execute() 172 */ 173 public void execute() { 174 175 getSelectedItem().getSubMenu().setSubMenuPosition(getSelectedItem()); 176 getSelectedItem().getSubMenu().onResize(event); 177 } 178 }); 179 } 180 } 181 182 /** 183 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#removeAutoHidePartner(com.google.gwt.dom.client.Element) 184 */ 185 public void removeAutoHidePartner(com.google.gwt.dom.client.Element partner) { 186 187 m_autoHideParent.removeAutoHidePartner(partner); 188 189 } 190 191 /** 192 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#setAutoHideEnabled(boolean) 193 */ 194 public void setAutoHideEnabled(boolean autoHide) { 195 196 m_autoHideParent.setAutoHideEnabled(autoHide); 197 } 198 199 /** 200 * @see org.opencms.gwt.client.ui.I_CmsAutoHider#setAutoHideOnHistoryEventsEnabled(boolean) 201 */ 202 public void setAutoHideOnHistoryEventsEnabled(boolean enabled) { 203 204 m_autoHideParent.setAutoHideOnHistoryEventsEnabled(enabled); 205 } 206 207 /** 208 * Sets the parent item.<p> 209 * 210 * @param parentItem the parent item 211 */ 212 public void setParentItem(A_CmsContextMenuItem parentItem) { 213 214 m_parentItem = parentItem; 215 } 216 217 /** 218 * Returns the selected item of this menu.<p> 219 * 220 * @return the selected item of this menu 221 */ 222 protected A_CmsContextMenuItem getSelectedItem() { 223 224 return m_selectedItem; 225 } 226 227 /** 228 * Action on close.<p> 229 * 230 * On close all sub menus should be hidden, the currently selected item should be deselected 231 * and the popup will be closed.<p> 232 */ 233 protected void onClose() { 234 235 if ((m_selectedItem != null)) { 236 if (m_selectedItem.hasSubmenu()) { 237 m_selectedItem.getSubMenu().onClose(); 238 } 239 m_selectedItem.deselectItem(); 240 } 241 m_popup.hide(); 242 } 243 244 /** 245 * Opens a sub menu and sets its position.<p> 246 * 247 * @param item the item to show the sub menu of 248 */ 249 protected void openPopup(final A_CmsContextMenuItem item) { 250 251 m_popup.add(item.getSubMenu()); 252 m_popup.addAutoHidePartner(item.getElement()); 253 m_popup.setModal(false); 254 m_popup.show(); 255 256 setSubMenuPosition(item); 257 258 if (m_isFixed) { 259 m_popup.setPositionFixed(); 260 } 261 } 262 263 /** 264 * Sets the selected item of this menu.<p> 265 * 266 * @param selectedItem the item to select 267 */ 268 protected void setSelectedItem(A_CmsContextMenuItem selectedItem) { 269 270 m_selectedItem = selectedItem; 271 } 272 273 /** 274 * Sets the position of the sub menu popup.<p> 275 * 276 * First calculates the best space where to show the popup.<p> 277 * 278 * The following list shows the possibilities, beginning 279 * with the best and ending with the worst.<p> 280 * 281 * <ul> 282 * <li>bottom-right</li> 283 * <li>bottom-left</li> 284 * <li>top-right</li> 285 * <li>top-left</li> 286 * </ul> 287 * 288 * Then the position (top and left coordinate) are calculated.<p> 289 * 290 * Finally the position of the sub menu popup is set to the calculated values.<p> 291 * 292 * @param item the item to show the sub menu of 293 */ 294 protected void setSubMenuPosition(final A_CmsContextMenuItem item) { 295 296 int scrollLeft = Window.getScrollLeft(); 297 int scrollTop = Window.getScrollTop(); 298 299 // calculate the left space 300 // add 10 because of the shadow and for avoiding that the browser's right window border touches the sub menu 301 int leftSpace = item.getAbsoluteLeft() - (scrollLeft + 10); 302 // calculate the right space 303 // add 10 because of the shadow and for avoiding that the browser's left window border touches the sub menu 304 int rightSpace = Window.getClientWidth() - (item.getAbsoluteLeft() + item.getOffsetWidth() + 10); 305 // if the width of the sub menu is smaller than the right space, show the sub menu on the right 306 boolean showRight = item.getSubMenu().getOffsetWidth() < rightSpace; 307 if (!showRight) { 308 // if the width of the sub menu is larger than the right space, compare the left space with the right space 309 // and show the sub menu on the right if on the right is more space than on the left 310 showRight = leftSpace < rightSpace; 311 } 312 313 // calculate the top space 314 // add 10 because of the shadow and for avoiding that the browser's top window border touches the sub menu 315 int topSpace = (item.getAbsoluteTop() - scrollTop) + 10; 316 // calculate the bottom space 317 // add 10 because of the shadow and for avoiding that the browser's bottom window border touches the sub menu 318 int bottomSpace = (Window.getClientHeight() + scrollTop) - (item.getAbsoluteTop() + 10); 319 // if the height of the sub menu is smaller than the bottom space, show the sub menu on the bottom 320 boolean showBottom = item.getSubMenu().getOffsetHeight() < bottomSpace; 321 if (!showBottom) { 322 // if the height of the sub menu is larger than the bottom space, compare the top space with 323 // the bottom space and show the sub menu on the bottom if on the bottom is more space than on the top 324 showBottom = topSpace < bottomSpace; 325 } 326 327 int left; 328 int top; 329 330 if (showBottom) { 331 top = item.getAbsoluteTop() - 4; 332 } else { 333 top = ((item.getAbsoluteTop() - item.getSubMenu().getOffsetHeight()) + item.getOffsetHeight()) - 4; 334 } 335 336 if (showRight) { 337 left = (item.getAbsoluteLeft() + item.getOffsetWidth()) - 4; 338 } else { 339 left = item.getAbsoluteLeft() - item.getSubMenu().getOffsetWidth() - 4; 340 } 341 342 // in case of fixed popup position, subtract the scroll position 343 if (m_isFixed) { 344 left -= scrollLeft; 345 top -= scrollTop; 346 } 347 348 // finally set the position of the popup 349 m_popup.setPopupPosition(left, top); 350 } 351 352 /** 353 * Gets the parent menu.<p> 354 * 355 * @return the parent menu 356 */ 357 CmsContextMenu getParentMenu() { 358 359 if (m_parentItem != null) { 360 return m_parentItem.getParentMenu(); 361 } else { 362 return null; 363 } 364 } 365 366 /** 367 * Creates the context menu.<p> 368 * 369 * @param entries a list with all entries for the context menu 370 */ 371 private void createContextMenu(List<I_CmsContextMenuEntry> entries) { 372 373 Iterator<I_CmsContextMenuEntry> it = entries.iterator(); 374 while (it.hasNext()) { 375 I_CmsContextMenuEntry entry = it.next(); 376 if (!entry.isVisible()) { 377 continue; 378 } 379 if (entry.isSeparator()) { 380 addSeparator(); 381 } else { 382 A_CmsContextMenuItem item = entry.generateMenuItem(); 383 if (entry.hasSubMenu()) { 384 CmsContextMenu submenu = new CmsContextMenu(entry.getSubMenu(), m_isFixed, m_popup); 385 item.setSubMenu(submenu); 386 addItem(item); 387 } else { 388 addItem(item); 389 } 390 } 391 392 } 393 } 394}