/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.client.editor.sugg; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.StyleInjector; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.MenuBar; import com.google.gwt.user.client.ui.MenuItem; import org.waveprotocol.wave.client.common.safehtml.EscapeUtils; import org.waveprotocol.wave.client.common.safehtml.SafeHtml; import org.waveprotocol.wave.client.common.util.SignalEvent; import org.waveprotocol.wave.client.common.util.SignalEventImpl; import org.waveprotocol.wave.client.editor.sugg.InteractiveSuggestionsManager.SuggestionMenuHandler; /** * Reasonable implementation of a suggestion drop down menu. * * @author danilatos@google.com (Daniel Danilatos) */ public class SuggestionMenu extends MenuBar implements Menu { /** * We want to put the (obfuscated) classname for highlighted items on menu * items here. Currently there is no default style (it could be added) but * specific things (like spell suggestions) need to be able to refer to this * style via inheritance for their specific needs (the spelly menu items have * a non-trivial dom & styling) */ public static final SuggestionResources RESOURCES = GWT.create(SuggestionResources.class); static { StyleInjector.inject(RESOURCES.css().getText(), true); } private final SuggestionMenuHandler handler; /** Default Constructor */ public SuggestionMenu(SuggestionMenuHandler handler) { super(true); this.handler = handler; // Sink the context menu even so that we can cancel it and sink key presses // so we can stop propagation. sinkEvents(Event.ONCONTEXTMENU | Event.ONKEYPRESS); } @Override public void clearItems() { super.clearItems(); } @Override public MenuItem addItem(SafeHtml title, final Command callback) { // TODO(danilatos): Make the titles line up //return super.addItem((index++) + ". " + title, callback); return super.addItem(new MenuItem(title.asString(), true, new Command() { @Override public void execute() { handler.beforeItemClicked(); callback.execute(); handler.handleItemSelected(); } }){ /** * Adding a hook so we can add our own type-safe style name to the * highlighted item, as opposed to the one they use, which is private * and not using style injector. */ @Override protected void setSelectionStyle(boolean selected) { super.setSelectionStyle(selected); // TODO(user): remove the dependency to SpellSuggestion.resources if (selected) { getElement().addClassName(RESOURCES.css().hover()); } else { getElement().removeClassName(RESOURCES.css().hover()); } } }); } @Override public MenuItem addItem(String title, final Command callback) { return addItem(EscapeUtils.fromString(title), callback); } @Override public void onBrowserEvent(Event event) { SignalEvent sEvent = SignalEventImpl.create(event, true); if (sEvent != null) { handleEventInner(event); } super.onBrowserEvent(event); } private void handleEventInner(Event event) { switch (DOM.eventGetType(event)) { case Event.ONCONTEXTMENU: event.preventDefault(); break; case Event.ONKEYPRESS: case Event.ONKEYDOWN: { // NOTE(user): It is necessary to stop propagation on the key events to // prevent them from leaking to the blip/wave presenters. int keyCode = DOM.eventGetKeyCode(event); // Move left/right to previous/next drop down widget switch (keyCode) { case KeyCodes.KEY_LEFT: case KeyCodes.KEY_RIGHT: handler.handleLeftRight(keyCode == KeyCodes.KEY_RIGHT); event.stopPropagation(); return; case KeyCodes.KEY_ENTER: event.stopPropagation(); } if (keyCode >= '1' && keyCode <= '9') { // TODO(danilatos): Is this ok? i18n etc? int index = keyCode - '1'; if (index >= 0 && index < getItems().size()) { MenuItem item = getItems().get(index); if (item == null) { return; } item.getCommand().execute(); DOM.eventPreventDefault(event); } } break; } case Event.ONMOUSEOUT: { // Need to check that we really seem to have left the menu, as // mouse-out events get triggered whenever the mouse moves between // selections in the menu. EventTarget target = event.getRelatedEventTarget(); Element targetElement = Element.as(target); if (!getElement().isOrHasChild(targetElement)) { handler.handleMouseOut(); } break; } case Event.ONMOUSEOVER: { handler.handleMouseOver(); break; } } } }