001
002package org.opencms.ui.client.contextmenu;
003
004import org.opencms.ui.shared.CmsContextMenuState.ContextMenuItemState;
005
006import java.util.Set;
007
008import com.google.gwt.core.client.GWT;
009import com.google.gwt.dom.client.Element;
010import com.google.gwt.event.dom.client.KeyCodes;
011import com.google.gwt.event.logical.shared.CloseHandler;
012import com.google.gwt.event.shared.HandlerRegistration;
013import com.google.gwt.user.client.DOM;
014import com.google.gwt.user.client.Event;
015import com.google.gwt.user.client.Event.NativePreviewEvent;
016import com.google.gwt.user.client.Event.NativePreviewHandler;
017import com.google.gwt.user.client.ui.PopupPanel;
018import com.google.gwt.user.client.ui.Widget;
019
020/**
021 * Client side implementation for ContextMenu component.<p>
022 *
023 * Adapted from ContextMenu by Peter Lehto / Vaadin Ltd.<p>
024 */
025public class CmsContextMenuWidget extends Widget {
026
027    /** The menu overlay. */
028    private final CmsContextMenuOverlay m_menuOverlay;
029
030    /** The event preview handler. */
031    private final NativePreviewHandler m_nativeEventHandler = new NativePreviewHandler() {
032
033        @Override
034        public void onPreviewNativeEvent(NativePreviewEvent event) {
035
036            if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
037                // Always close the context menu on esc, no matter the focus
038                hide();
039            }
040
041            Event nativeEvent = Event.as(event.getNativeEvent());
042            boolean targetsContextMenu = eventTargetContextMenu(nativeEvent);
043
044            if (!targetsContextMenu && (nativeEvent.getTypeInt() == Event.ONMOUSEDOWN) && isHideAutomatically()) {
045                hide();
046            }
047        }
048    };
049
050    /** The preview handler registration. */
051    private final HandlerRegistration m_nativeEventHandlerRegistration;
052
053    /** The hide automatically flag. */
054    private boolean m_hideAutomatically;
055
056    /** The extension target widget. */
057    private Widget m_extensionTarget;
058
059    /**
060     * Constructor.<p>
061     */
062    public CmsContextMenuWidget() {
063
064        Element element = DOM.createDiv();
065        setElement(element);
066
067        m_nativeEventHandlerRegistration = Event.addNativePreviewHandler(m_nativeEventHandler);
068
069        m_menuOverlay = new CmsContextMenuOverlay();
070    }
071
072    /**
073     * Adds a menu popup close handler.<p>
074     *
075     * @param popupCloseHandler the close handler
076     *
077     * @return the handler registration
078     */
079    public HandlerRegistration addCloseHandler(CloseHandler<PopupPanel> popupCloseHandler) {
080
081        return m_menuOverlay.addCloseHandler(popupCloseHandler);
082    }
083
084    /**
085     * Adds new item as context menu root item.<p>
086     *
087     * @param rootItem the root item
088     * @param connector the connector
089     */
090    public void addRootMenuItem(ContextMenuItemState rootItem, CmsContextMenuConnector connector) {
091
092        CmsContextMenuItemWidget itemWidget = createEmptyItemWidget(
093            rootItem.getId(),
094            rootItem.getCaption(),
095            rootItem.getDescription(),
096            connector);
097        itemWidget.setEnabled(rootItem.isEnabled());
098        itemWidget.setSeparatorVisible(rootItem.isSeparator());
099
100        setStyleNames(itemWidget, rootItem.getStyles());
101
102        m_menuOverlay.addMenuItem(itemWidget);
103
104        for (ContextMenuItemState childState : rootItem.getChildren()) {
105            createSubMenu(itemWidget, childState, connector);
106        }
107    }
108
109    /**
110     * Clears the menu items.<p>
111     */
112    public void clearItems() {
113
114        m_menuOverlay.clearItems();
115    }
116
117    /**
118     * Returns the extension target widget.<p>
119     *
120     * @return the extension target widget
121     */
122    public Widget getExtensionTarget() {
123
124        return m_extensionTarget;
125    }
126
127    /**
128     * Hides the menu popup.<p>
129     */
130    public void hide() {
131
132        m_menuOverlay.hide();
133    }
134
135    /**
136     * Returns whether the menu is set to hide automatically.<p>
137     *
138     * @return <code>true</code> if the menu is set to hide automatically
139     */
140    public boolean isHideAutomatically() {
141
142        return m_hideAutomatically;
143    }
144
145    /**
146     * Sets the extension target.<p>
147     *
148     * @param extensionTarget the etension target
149     */
150    public void setExtensionTarget(Widget extensionTarget) {
151
152        this.m_extensionTarget = extensionTarget;
153        m_menuOverlay.setOwner(extensionTarget);
154    }
155
156    /**
157     * Sets the hide automatically flag.<p>
158     *
159     * @param hideAutomatically the hide automatically flag
160     */
161    public void setHideAutomatically(boolean hideAutomatically) {
162
163        this.m_hideAutomatically = hideAutomatically;
164    }
165
166    /**
167     * Shows the context menu at the given position.<p>
168     *
169     * @param rootMenuX the client x position
170     * @param rootMenuY the client y position
171     */
172    public void showContextMenu(int rootMenuX, int rootMenuY) {
173
174        m_menuOverlay.showAt(rootMenuX, rootMenuY);
175    }
176
177    /**
178     * Shows the context menu relative to the given widget.<p>
179     *
180     * @param widget the widget
181     */
182    public void showContextMenu(Widget widget) {
183
184        m_menuOverlay.showRelativeTo(widget);
185    }
186
187    /**
188     * Unregisters the menu.<p>
189     */
190    public void unregister() {
191
192        m_nativeEventHandlerRegistration.removeHandler();
193        m_menuOverlay.unregister();
194    }
195
196    /**
197     * Returns whether the given event targets the context menu.<p>
198     *
199     * @param nativeEvent the event to check
200     *
201     * @return <code>true</code> if the event targets the menu
202     */
203    protected boolean eventTargetContextMenu(Event nativeEvent) {
204
205        for (CmsContextMenuItemWidget item : m_menuOverlay.getMenuItems()) {
206            if (item.eventTargetsPopup(nativeEvent)) {
207                return true;
208            }
209        }
210
211        return false;
212    }
213
214    /**
215     * Returns whether the menu is showing.<p>
216     *
217     * @return <code>true</code> if the menu is showing
218     */
219    protected boolean isShowing() {
220
221        return m_menuOverlay.isShowing();
222    }
223
224    /**
225     * Creates new empty menu item.<p>
226     *
227     * @param id the id
228     * @param caption the caption
229     * @param description the item description used as tool-tip
230     * @param contextMenuConnector the connector
231     *
232     * @return the menu item
233     */
234    private CmsContextMenuItemWidget createEmptyItemWidget(
235        String id,
236        String caption,
237        String description,
238        CmsContextMenuConnector contextMenuConnector) {
239
240        CmsContextMenuItemWidget widget = GWT.create(CmsContextMenuItemWidget.class);
241        widget.setId(id);
242        widget.setCaption(caption);
243        widget.setTitle(description);
244        widget.setIcon(contextMenuConnector.getConnection().getIcon(contextMenuConnector.getResourceUrl(id)));
245
246        CmsContextMenuItemWidgetHandler handler = new CmsContextMenuItemWidgetHandler(widget, contextMenuConnector);
247        widget.addClickHandler(handler);
248        widget.addMouseOutHandler(handler);
249        widget.addMouseOverHandler(handler);
250        widget.addKeyUpHandler(handler);
251        widget.setRootComponent(this);
252
253        return widget;
254    }
255
256    /**
257     * Creates a new sub menu.<p>
258     *
259     * @param parentWidget the parent widget
260     * @param childState the child state
261     * @param connector the connector
262     */
263    private void createSubMenu(
264        CmsContextMenuItemWidget parentWidget,
265        ContextMenuItemState childState,
266        CmsContextMenuConnector connector) {
267
268        CmsContextMenuItemWidget childWidget = createEmptyItemWidget(
269            childState.getId(),
270            childState.getCaption(),
271            childState.getDescription(),
272            connector);
273        childWidget.setEnabled(childState.isEnabled());
274        childWidget.setSeparatorVisible(childState.isSeparator());
275        setStyleNames(childWidget, childState.getStyles());
276        parentWidget.addSubMenuItem(childWidget);
277
278        for (ContextMenuItemState child : childState.getChildren()) {
279            createSubMenu(childWidget, child, connector);
280        }
281    }
282
283    /**
284     * Adds the given style names to the item widget.<p>
285     *
286     * @param item the item
287     * @param styles the style names
288     */
289    private void setStyleNames(CmsContextMenuItemWidget item, Set<String> styles) {
290
291        for (String style : styles) {
292            item.addStyleName(style);
293        }
294    }
295}