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