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}