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        Element element = DOM.createDiv();
064        setElement(element);
065
066        m_nativeEventHandlerRegistration = Event.addNativePreviewHandler(m_nativeEventHandler);
067
068        m_menuOverlay = new CmsContextMenuOverlay();
069    }
070
071    /**
072     * Adds a menu popup close handler.<p>
073     *
074     * @param popupCloseHandler the close handler
075     *
076     * @return the handler registration
077     */
078    public HandlerRegistration addCloseHandler(CloseHandler<PopupPanel> popupCloseHandler) {
079
080        return m_menuOverlay.addCloseHandler(popupCloseHandler);
081    }
082
083    /**
084     * Adds new item as context menu root item.<p>
085     *
086     * @param rootItem the root item
087     * @param connector the connector
088     */
089    public void addRootMenuItem(ContextMenuItemState rootItem, CmsContextMenuConnector connector) {
090
091        CmsContextMenuItemWidget itemWidget = createEmptyItemWidget(
092            rootItem.getId(),
093            rootItem.getCaption(),
094            rootItem.getDescription(),
095            connector);
096        itemWidget.setEnabled(rootItem.isEnabled());
097        itemWidget.setSeparatorVisible(rootItem.isSeparator());
098
099        setStyleNames(itemWidget, rootItem.getStyles());
100
101        m_menuOverlay.addMenuItem(itemWidget);
102
103        for (ContextMenuItemState childState : rootItem.getChildren()) {
104            createSubMenu(itemWidget, childState, connector);
105        }
106    }
107
108    /**
109     * Clears the menu items.<p>
110     */
111    public void clearItems() {
112
113        m_menuOverlay.clearItems();
114    }
115
116    /**
117     * Returns the extension target widget.<p>
118     *
119     * @return the extension target widget
120     */
121    public Widget getExtensionTarget() {
122
123        return m_extensionTarget;
124    }
125
126    /**
127     * Hides the menu popup.<p>
128     */
129    public void hide() {
130
131        m_menuOverlay.hide();
132    }
133
134    /**
135     * Returns whether the menu is set to hide automatically.<p>
136     *
137     * @return <code>true</code> if the menu is set to hide automatically
138     */
139    public boolean isHideAutomatically() {
140
141        return m_hideAutomatically;
142    }
143
144    /**
145     * Sets the extension target.<p>
146     *
147     * @param extensionTarget the etension target
148     */
149    public void setExtensionTarget(Widget extensionTarget) {
150
151        this.m_extensionTarget = extensionTarget;
152        m_menuOverlay.setOwner(extensionTarget);
153    }
154
155    /**
156     * Sets the hide automatically flag.<p>
157     *
158     * @param hideAutomatically the hide automatically flag
159     */
160    public void setHideAutomatically(boolean hideAutomatically) {
161
162        this.m_hideAutomatically = hideAutomatically;
163    }
164
165    /**
166     * Shows the context menu at the given position.<p>
167     *
168     * @param rootMenuX the client x position
169     * @param rootMenuY the client y position
170     */
171    public void showContextMenu(int rootMenuX, int rootMenuY) {
172
173        m_menuOverlay.showAt(rootMenuX, rootMenuY);
174    }
175
176    /**
177     * Shows the context menu relative to the given widget.<p>
178     *
179     * @param widget the widget
180     */
181    public void showContextMenu(Widget widget) {
182
183        m_menuOverlay.showRelativeTo(widget);
184    }
185
186    /**
187     * Unregisters the menu.<p>
188     */
189    public void unregister() {
190
191        m_nativeEventHandlerRegistration.removeHandler();
192        m_menuOverlay.unregister();
193    }
194
195    /**
196     * Returns whether the given event targets the context menu.<p>
197     *
198     * @param nativeEvent the event to check
199     *
200     * @return <code>true</code> if the event targets the menu
201     */
202    protected boolean eventTargetContextMenu(Event nativeEvent) {
203
204        for (CmsContextMenuItemWidget item : m_menuOverlay.getMenuItems()) {
205            if (item.eventTargetsPopup(nativeEvent)) {
206                return true;
207            }
208        }
209
210        return false;
211    }
212
213    /**
214     * Returns whether the menu is showing.<p>
215     *
216     * @return <code>true</code> if the menu is showing
217     */
218    protected boolean isShowing() {
219
220        return m_menuOverlay.isShowing();
221    }
222
223    /**
224     * Creates new empty menu item.<p>
225     *
226     * @param id the id
227     * @param caption the caption
228     * @param description the item description used as tool-tip
229     * @param contextMenuConnector the connector
230     *
231     * @return the menu item
232     */
233    private CmsContextMenuItemWidget createEmptyItemWidget(
234        String id,
235        String caption,
236        String description,
237        CmsContextMenuConnector contextMenuConnector) {
238
239        CmsContextMenuItemWidget widget = GWT.create(CmsContextMenuItemWidget.class);
240        widget.setId(id);
241        widget.setCaption(caption);
242        widget.setTitle(description);
243        widget.setIcon(contextMenuConnector.getConnection().getIcon(contextMenuConnector.getResourceUrl(id)));
244
245        CmsContextMenuItemWidgetHandler handler = new CmsContextMenuItemWidgetHandler(widget, contextMenuConnector);
246        widget.addClickHandler(handler);
247        widget.addMouseOutHandler(handler);
248        widget.addMouseOverHandler(handler);
249        widget.addKeyUpHandler(handler);
250        widget.setRootComponent(this);
251
252        return widget;
253    }
254
255    /**
256     * Creates a new sub menu.<p>
257     *
258     * @param parentWidget the parent widget
259     * @param childState the child state
260     * @param connector the connector
261     */
262    private void createSubMenu(
263        CmsContextMenuItemWidget parentWidget,
264        ContextMenuItemState childState,
265        CmsContextMenuConnector connector) {
266
267        CmsContextMenuItemWidget childWidget = createEmptyItemWidget(
268            childState.getId(),
269            childState.getCaption(),
270            childState.getDescription(),
271            connector);
272        childWidget.setEnabled(childState.isEnabled());
273        childWidget.setSeparatorVisible(childState.isSeparator());
274        setStyleNames(childWidget, childState.getStyles());
275        parentWidget.addSubMenuItem(childWidget);
276
277        for (ContextMenuItemState child : childState.getChildren()) {
278            createSubMenu(childWidget, child, connector);
279        }
280    }
281
282    /**
283     * Adds the given style names to the item widget.<p>
284     *
285     * @param item the item
286     * @param styles the style names
287     */
288    private void setStyleNames(CmsContextMenuItemWidget item, Set<String> styles) {
289
290        for (String style : styles) {
291            item.addStyleName(style);
292        }
293    }
294}