001
002package org.opencms.ui.client.contextmenu;
003
004import com.google.gwt.user.client.DOM;
005import com.google.gwt.user.client.Event;
006import com.google.gwt.user.client.ui.FlowPanel;
007import com.google.gwt.user.client.ui.FocusWidget;
008import com.google.gwt.user.client.ui.Label;
009import com.vaadin.client.ui.Icon;
010
011/**
012 * ContextMenuItemWidget is client side widget that represents one menu item in
013 * context menu.<p>
014 *
015 * Adapted from ContextMenu by Peter Lehto / Vaadin Ltd.<p>
016 */
017public class CmsContextMenuItemWidget extends FocusWidget {
018
019    /** The icon. */
020    protected Icon m_icon;
021
022    /** The icon container. */
023    private final FlowPanel m_iconContainer;
024
025    /** The id. */
026    private String m_id;
027
028    /** The overlay. */
029    private CmsContextMenuOverlay m_overlay;
030
031    /** The parent item. */
032    private CmsContextMenuItemWidget m_parentItem;
033
034    /** The root panel. */
035    private final FlowPanel m_root;
036
037    /** The root component. */
038    private CmsContextMenuWidget m_rootComponent;
039
040    /** The sub menu. */
041    private CmsContextMenuOverlay m_subMenu;
042
043    /** The text label. */
044    private final Label m_text;
045
046    /**
047     * Constructor.<p>
048     */
049    @SuppressWarnings("deprecation")
050    public CmsContextMenuItemWidget() {
051
052        m_root = new FlowPanel();
053        m_root.setStylePrimaryName("v-context-menu-item-basic");
054
055        setElement(m_root.getElement());
056
057        m_root.addStyleName("v-context-submenu");
058
059        m_iconContainer = new FlowPanel();
060        m_iconContainer.setStyleName("v-context-menu-item-basic-icon-container");
061
062        m_text = new Label();
063        m_text.setStyleName("v-context-menu-item-basic-text");
064
065        m_root.add(m_iconContainer);
066        m_root.add(m_text);
067    }
068
069    /**
070     * Adds given context menu item into the sub menu of this item.<p>
071     *
072     * @param contextMenuItem the menu item
073     */
074    public void addSubMenuItem(CmsContextMenuItemWidget contextMenuItem) {
075
076        if (!hasSubMenu()) {
077            m_subMenu = new CmsContextMenuOverlay();
078            m_subMenu.setOwner(m_rootComponent.getExtensionTarget());
079            setStylePrimaryName("v-context-menu-item-basic-submenu");
080        }
081
082        contextMenuItem.setParentItem(this);
083        m_subMenu.addMenuItem(contextMenuItem);
084    }
085
086    /**
087     * Removes all the items from the submenu of this item. If this menu item
088     * does not have a sub menu, this call has no effect.<p>
089     */
090    public void clearItems() {
091
092        if (hasSubMenu()) {
093            m_subMenu.clearItems();
094        }
095    }
096
097    /**
098     * Closes the sibling menu.<p>
099     */
100    public void closeSiblingMenus() {
101
102        m_overlay.closeSubMenus();
103    }
104
105    /**
106     * Checks whether the given event targets the menu popup.<p>
107     *
108     * @param nativeEvent the event to check
109     *
110     * @return <code>true</code> if given event targets the overlay of this menu item or
111     *         overlay of any of this item's child item.
112     */
113    public boolean eventTargetsPopup(Event nativeEvent) {
114
115        if (m_overlay.eventTargetsPopup(nativeEvent)) {
116            return true;
117        }
118
119        if (hasSubMenu()) {
120            for (CmsContextMenuItemWidget item : m_subMenu.getMenuItems()) {
121                if (item.eventTargetsPopup(nativeEvent)) {
122                    return true;
123                }
124            }
125        }
126
127        return false;
128    }
129
130    /**
131     * Returns the id.<p>
132     *
133     * @return the id
134     */
135    public String getId() {
136
137        return m_id;
138    }
139
140    /**
141     * Returns the parent item.<p>
142     *
143     * @return menu item that opened the menu to which this item belongs
144     */
145    public CmsContextMenuItemWidget getParentItem() {
146
147        return m_parentItem;
148    }
149
150    /**
151     * Returns if the item has a sub menu.<p>
152     *
153     * @return <code>true</code> if this item has a sub menu
154     */
155    public boolean hasSubMenu() {
156
157        return (m_subMenu != null) && (m_subMenu.getNumberOfItems() > 0);
158    }
159
160    /**
161     * Hides the sub menu that's been opened from this item.<p>
162     */
163    public void hideSubMenu() {
164
165        if (hasSubMenu()) {
166            m_subMenu.hide();
167            removeStyleName("v-context-menu-item-basic-open");
168        }
169    }
170
171    /**
172     * Returns whether this is the root item.<p>
173     *
174     * @return <code>true</code> if this item is an item in the root menu
175     */
176    public boolean isRootItem() {
177
178        return m_parentItem == null;
179    }
180
181    /**
182     * Returns if the sub menu is open.<p>
183     *
184     * @return <code>true</code> if this menu has a sub menu and it's open
185     */
186    public boolean isSubmenuOpen() {
187
188        return hasSubMenu() && m_subMenu.isShowing();
189    }
190
191    /**
192     * Sets the caption.<p>
193     *
194     * @param caption the caption to set
195     */
196    public void setCaption(String caption) {
197
198        m_text.setText(caption);
199    }
200
201    /**
202     * @see com.google.gwt.user.client.ui.FocusWidget#setEnabled(boolean)
203     */
204    @Override
205    public void setEnabled(boolean enabled) {
206
207        super.setEnabled(enabled);
208        if (enabled) {
209            m_root.removeStyleName("v-context-menu-item-disabled");
210        } else {
211            m_root.addStyleName("v-context-menu-item-disabled");
212        }
213    }
214
215    /**
216     * @see com.google.gwt.user.client.ui.FocusWidget#setFocus(boolean)
217     */
218    @Override
219    public void setFocus(boolean focused) {
220
221        if (hasSubMenu()) {
222            m_subMenu.setFocus(false);
223        }
224
225        super.setFocus(focused);
226
227        if (!focused) {
228            DOM.releaseCapture(getElement());
229        }
230    }
231
232    /**
233     * Sets the icon.<p>
234     *
235     * @param icon the icon
236     */
237    public void setIcon(Icon icon) {
238
239        if (icon == null) {
240            m_iconContainer.clear();
241            this.m_icon = null;
242        } else {
243            m_iconContainer.getElement().appendChild(icon.getElement());
244            this.m_icon = icon;
245        }
246    }
247
248    /**
249     * Sets the id.<p>
250     *
251     * @param id the id
252     */
253    public void setId(String id) {
254
255        this.m_id = id;
256    }
257
258    /**
259     * Sets the menu component to which this item belongs to.<p>
260     *
261     * @param owner the owner
262     */
263    public void setOverlay(CmsContextMenuOverlay owner) {
264
265        this.m_overlay = owner;
266    }
267
268    /**
269     * Sets parent item meaning that this item is in the sub menu of given parent item.<p>
270     *
271     * @param parentItem the parent item
272     */
273    public void setParentItem(CmsContextMenuItemWidget parentItem) {
274
275        this.m_parentItem = parentItem;
276    }
277
278    /**
279     * Sets the root component.<p>
280     *
281     * @param rootComponent the root component
282     */
283    public void setRootComponent(CmsContextMenuWidget rootComponent) {
284
285        this.m_rootComponent = rootComponent;
286    }
287
288    /**
289     * Sets the separator visibility.<p>
290     *
291     * @param separatorVisible <code>true</code> to set the separator visible
292     */
293    public void setSeparatorVisible(boolean separatorVisible) {
294
295        if (separatorVisible) {
296            m_root.addStyleName("v-context-menu-item-separator");
297        } else {
298            m_root.removeStyleName("v-context-menu-item-separator");
299        }
300    }
301
302    /**
303     * Closes this item and selects the parent.<p>
304     */
305    protected void closeThisAndSelectParent() {
306
307        if (!isRootItem()) {
308            setFocus(false);
309            m_parentItem.hideSubMenu();
310            m_parentItem.setFocus(true);
311        }
312    }
313
314    /**
315     * Called when context menu item is clicked or is focused and enter is
316     * pressed.<p>
317     *
318     * @return <code>true</code> if context menu was closed after the click
319     */
320    protected boolean onItemClicked() {
321
322        if (isEnabled()) {
323            m_overlay.closeSubMenus();
324
325            if (hasSubMenu()) {
326                openSubMenu();
327                return false;
328            } else {
329                if (m_rootComponent.isHideAutomatically()) {
330                    closeContextMenu();
331                    return true;
332                }
333                return false;
334            }
335        }
336
337        return false;
338    }
339
340    /**
341     * Selects the next sibling.<p>
342     */
343    protected void selectLowerSibling() {
344
345        setFocus(false);
346        m_overlay.selectItemAfter(CmsContextMenuItemWidget.this);
347
348    }
349
350    /**
351     * Selects the previous sibling.<p>
352     */
353    protected void selectUpperSibling() {
354
355        setFocus(false);
356        m_overlay.selectItemBefore(CmsContextMenuItemWidget.this);
357    }
358
359    /**
360     * Closes the menu.<p>
361     */
362    private void closeContextMenu() {
363
364        if (isRootItem()) {
365            m_rootComponent.hide();
366        } else {
367            m_parentItem.closeContextMenu();
368        }
369    }
370
371    /**
372     * Programmatically opens the sub menu of this item.<p>
373     */
374    private void openSubMenu() {
375
376        if (isEnabled() && hasSubMenu() && !m_subMenu.isShowing()) {
377            m_overlay.closeSubMenus();
378
379            setFocus(false);
380            addStyleName("v-context-menu-item-basic-open");
381            m_subMenu.openNextTo(this);
382        }
383    }
384}