| /** |
| * |
| * Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany) |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Florian Pirchner <florian.pirchner@gmail.com> - Initial implementation |
| */ |
| package org.eclipse.osbp.vaadin.addons.suggesttext.client; |
| |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import com.google.gwt.core.client.Scheduler.ScheduledCommand; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.editor.client.IsEditor; |
| import com.google.gwt.editor.client.LeafValueEditor; |
| import com.google.gwt.editor.client.adapters.TakesValueEditor; |
| import com.google.gwt.event.dom.client.HasAllKeyHandlers; |
| import com.google.gwt.event.dom.client.KeyCodes; |
| import com.google.gwt.event.dom.client.KeyDownEvent; |
| import com.google.gwt.event.dom.client.KeyDownHandler; |
| import com.google.gwt.event.dom.client.KeyPressEvent; |
| import com.google.gwt.event.dom.client.KeyPressHandler; |
| import com.google.gwt.event.dom.client.KeyUpEvent; |
| import com.google.gwt.event.dom.client.KeyUpHandler; |
| import com.google.gwt.event.logical.shared.HasSelectionHandlers; |
| import com.google.gwt.event.logical.shared.SelectionEvent; |
| import com.google.gwt.event.logical.shared.SelectionHandler; |
| import com.google.gwt.event.logical.shared.ValueChangeEvent; |
| import com.google.gwt.event.logical.shared.ValueChangeHandler; |
| import com.google.gwt.event.shared.HandlerRegistration; |
| import com.google.gwt.user.client.DOM; |
| import com.google.gwt.user.client.Event; |
| import com.google.gwt.user.client.EventListener; |
| import com.google.gwt.user.client.ui.DecoratedPopupPanel; |
| import com.google.gwt.user.client.ui.Focusable; |
| import com.google.gwt.user.client.ui.HasAnimation; |
| import com.google.gwt.user.client.ui.HasEnabled; |
| import com.google.gwt.user.client.ui.HasText; |
| import com.google.gwt.user.client.ui.HasValue; |
| import com.google.gwt.user.client.ui.MenuBar; |
| import com.google.gwt.user.client.ui.MenuItem; |
| import com.google.gwt.user.client.ui.PopupPanel; |
| import com.google.gwt.user.client.ui.PopupPanel.AnimationType; |
| import com.google.gwt.user.client.ui.RootPanel; |
| import com.google.gwt.user.client.ui.SimplePanel; |
| import com.google.gwt.user.client.ui.SuggestBox; |
| import com.google.gwt.user.client.ui.SuggestOracle; |
| import com.google.gwt.user.client.ui.SuggestOracle.Callback; |
| import com.google.gwt.user.client.ui.SuggestOracle.Request; |
| import com.google.gwt.user.client.ui.SuggestOracle.Response; |
| import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; |
| import com.google.gwt.user.client.ui.UIObject; |
| import com.google.gwt.user.client.ui.ValueBoxBase; |
| import com.google.gwt.user.client.ui.Widget; |
| import com.vaadin.client.ApplicationConnection; |
| import com.vaadin.client.ComponentConnector; |
| import com.vaadin.client.Util; |
| import com.vaadin.client.ui.VOverlay; |
| import com.vaadin.event.ShortcutAction; |
| |
| // TODO: Auto-generated Javadoc |
| /** |
| * Copy from {@link SuggestBox} but {@link #box} can be initialized after new |
| * instance was created. Copy was needed since {@link #box} is final in |
| * {@link com.google.gwt.user.client.ui.SuggestBox}. |
| */ |
| public class OSuggestBox extends SimplePanel implements HasText, Focusable, HasEnabled, HasAllKeyHandlers, |
| HasValue<String>, HasSelectionHandlers<Suggestion>, IsEditor<LeafValueEditor<String>> { |
| |
| private static final String SELECTED_CLASSNAME = "selected"; |
| |
| /** |
| * The callback used when a user selects a {@link Suggestion}. |
| */ |
| public static interface SuggestionCallback { |
| |
| /** |
| * On suggestion selected. |
| * |
| * @param suggestion |
| * the suggestion |
| */ |
| void onSuggestionSelected(Suggestion suggestion); |
| } |
| |
| /** |
| * Used to display suggestions to the user. |
| */ |
| public abstract static class SuggestionDisplay { |
| |
| /** |
| * Get the currently selected {@link Suggestion} in the display. |
| * |
| * @return the current suggestion, or null if none selected |
| */ |
| protected abstract Suggestion getCurrentSelection(); |
| |
| /** |
| * Hide the list of suggestions from view. |
| */ |
| protected abstract void hideSuggestions(); |
| |
| /** |
| * Returns true, if the popup is showing. |
| * |
| * @return |
| */ |
| protected abstract boolean isShowing(); |
| |
| /** |
| * Highlight the suggestion directly below the current selection in the |
| * list. |
| */ |
| protected abstract void moveSelectionDown(); |
| |
| /** |
| * Highlight the suggestion directly above the current selection in the |
| * list. |
| */ |
| protected abstract void moveSelectionUp(); |
| |
| /** |
| * Set the debug id of widgets used in the SuggestionDisplay. |
| * |
| * @param suggestBoxBaseID |
| * the baseID of the {@link OSuggestBox} |
| * @see UIObject#onEnsureDebugId(String) |
| */ |
| protected void onEnsureDebugId(String suggestBoxBaseID) { |
| } |
| |
| /** |
| * Accepts information about whether there were more suggestions |
| * matching than were provided to {@link #showSuggestions}. |
| * |
| * @param hasMoreSuggestions |
| * true if more matches were available |
| * @param numMoreSuggestions |
| * number of more matches available. If the specific number |
| * is unknown, 0 will be passed. |
| */ |
| protected void setMoreSuggestions(boolean hasMoreSuggestions, int numMoreSuggestions) { |
| // Subclasses may optionally implement. |
| } |
| |
| /** |
| * Update the list of visible suggestions. |
| * |
| * Use care when using isDisplayStringHtml; it is an easy way to expose |
| * script-based security problems. |
| * |
| * @param suggestBox |
| * the suggest box where the suggestions originated |
| * @param suggestions |
| * the suggestions to show |
| * @param isDisplayStringHTML |
| * should the suggestions be displayed as HTML |
| * @param isAutoSelectEnabled |
| * if true, the first item should be selected automatically |
| * @param callback |
| * the callback used when the user makes a suggestion |
| */ |
| protected abstract void showSuggestions(OSuggestBox suggestBox, Collection<? extends Suggestion> suggestions, |
| boolean isDisplayStringHTML, boolean isAutoSelectEnabled, SuggestionCallback callback); |
| |
| /** |
| * If true, then the popup will hide when the user clicks outside the |
| * popup. |
| * |
| * @param autoHide |
| */ |
| protected abstract void setAutoHide(boolean autoHide); |
| |
| /** |
| * The owner of the popup. |
| * |
| * @param oSuggestBox |
| */ |
| protected abstract void setOwner(OSuggestBox oSuggestBox); |
| |
| } |
| |
| /** |
| * <p> |
| * The default implementation of {@link SuggestionDisplay} displays |
| * suggestions in a {@link PopupPanel} beneath the {@link OSuggestBox}. |
| * |
| * |
| * <h3>CSS Style Rules</h3> |
| * <dl> |
| * <dt>.o-SuggestBoxPopup</dt> |
| * <dd>the suggestion popup</dd> |
| * <dt>.o-SuggestBoxPopup .item</dt> |
| * <dd>an unselected suggestion</dd> |
| * <dt>.o-SuggestBoxPopup .item-selected</dt> |
| * <dd>a selected suggestion</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupTopLeft</dt> |
| * <dd>the top left cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupTopLeftInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupTopCenter</dt> |
| * <dd>the top center cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupTopCenterInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupTopRight</dt> |
| * <dd>the top right cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupTopRightInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupMiddleLeft</dt> |
| * <dd>the middle left cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupMiddleLeftInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupMiddleCenter</dt> |
| * <dd>the middle center cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupMiddleCenterInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupMiddleRight</dt> |
| * <dd>the middle right cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupMiddleRightInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupBottomLeft</dt> |
| * <dd>the bottom left cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupBottomLeftInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupBottomCenter</dt> |
| * <dd>the bottom center cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupBottomCenterInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupBottomRight</dt> |
| * <dd>the bottom right cell</dd> |
| * <dt>.o-SuggestBoxPopup .suggestPopupBottomRightInner</dt> |
| * <dd>the inner element of the cell</dd> |
| * </dl> |
| */ |
| public static class DefaultSuggestionDisplay extends SuggestionDisplay implements HasAnimation { |
| |
| /** The suggestion menu. */ |
| private final SuggestionMenu suggestionMenu; |
| |
| /** The suggestion popup. */ |
| private final PopupPanel suggestionPopup; |
| |
| /** |
| * The suggest box this display was prepared for. Is used to determine |
| * the {@link ApplicationConnection} and the v-overlay-container. |
| */ |
| private OSuggestBox oSuggestBox; |
| |
| /** |
| * We need to keep track of the last {@link OSuggestBox} because it acts |
| * as an autoHide partner for the {@link PopupPanel}. If we use the same |
| * display for multiple {@link OSuggestBox}, we need to switch the |
| * autoHide partner. |
| */ |
| private OSuggestBox lastSuggestBox = null; |
| |
| /** |
| * Sub-classes making use of {@link decorateSuggestionList} to add |
| * elements to the suggestion popup _may_ want those elements to show |
| * even when there are 0 suggestions. An example would be showing a "No |
| * matches" message. |
| */ |
| private boolean hideWhenEmpty = true; |
| |
| /** |
| * Object to position the suggestion display next to, instead of the |
| * associated suggest box. |
| */ |
| private UIObject positionRelativeTo; |
| |
| /** |
| * Construct a new {@link DefaultSuggestionDisplay}. |
| */ |
| public DefaultSuggestionDisplay() { |
| suggestionMenu = new SuggestionMenu(true); |
| suggestionPopup = createPopup(); |
| suggestionPopup.setWidget(decorateSuggestionList(suggestionMenu)); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.osbp.vaadin.addons.suggesttext.client.OSuggestBox. |
| * SuggestionDisplay#hideSuggestions() |
| */ |
| @Override |
| public void hideSuggestions() { |
| suggestionPopup.hide(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasAnimation#isAnimationEnabled() |
| */ |
| public boolean isAnimationEnabled() { |
| return suggestionPopup.isAnimationEnabled(); |
| } |
| |
| /** |
| * Check whether or not the suggestion list is hidden when there are no |
| * suggestions to display. |
| * |
| * @return true if hidden when empty, false if not |
| */ |
| public boolean isSuggestionListHiddenWhenEmpty() { |
| return hideWhenEmpty; |
| } |
| |
| /** |
| * Check whether or not the list of suggestions is being shown. |
| * |
| * @return true if the suggestions are visible, false if not |
| */ |
| public boolean isSuggestionListShowing() { |
| return suggestionPopup.isShowing(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasAnimation#setAnimationEnabled( |
| * boolean) |
| */ |
| public void setAnimationEnabled(boolean enable) { |
| suggestionPopup.setAnimationEnabled(enable); |
| } |
| |
| /** |
| * Sets the style name of the suggestion popup. |
| * |
| * @param style |
| * the new primary style name |
| * @see UIObject#setStyleName(String) |
| */ |
| public void setPopupStyleName(String style) { |
| suggestionPopup.setStyleName(style); |
| } |
| |
| /** |
| * Sets the UI object where the suggestion display should appear next |
| * to. |
| * |
| * @param uiObject |
| * the uiObject used for positioning, or null to position |
| * relative to the suggest box |
| */ |
| public void setPositionRelativeTo(UIObject uiObject) { |
| positionRelativeTo = uiObject; |
| } |
| |
| /** |
| * Set whether or not the suggestion list should be hidden when there |
| * are no suggestions to display. Defaults to true. |
| * |
| * @param hideWhenEmpty |
| * true to hide when empty, false not to |
| */ |
| public void setSuggestionListHiddenWhenEmpty(boolean hideWhenEmpty) { |
| this.hideWhenEmpty = hideWhenEmpty; |
| } |
| |
| /** |
| * Create the PopupPanel that will hold the list of suggestions. |
| * |
| * @return the popup panel |
| */ |
| protected PopupPanel createPopup() { |
| PopupPanel p = new DecoratedPopupPanel(true, false) { |
| @Override |
| protected void onAttach() { |
| // Move the overlay to the appropriate overlay container |
| getOverlayContainer().appendChild(getElement()); |
| |
| addStyleName(oSuggestBox.getStyleName()); |
| |
| super.onAttach(); |
| } |
| }; |
| p.setStyleName("o-SuggestBoxPopup"); |
| p.setPreviewingAllNativeEvents(true); |
| p.setAnimationType(AnimationType.ROLL_DOWN); |
| p.setAutoHideEnabled(true); |
| return p; |
| } |
| |
| /** |
| * Wrap the list of suggestions before adding it to the popup. You can |
| * override this method if you want to wrap the suggestion list in a |
| * decorator. |
| * |
| * @param suggestionList |
| * the widget that contains the list of suggestions |
| * @return the suggestList, optionally inside of a wrapper |
| */ |
| protected Widget decorateSuggestionList(Widget suggestionList) { |
| return suggestionList; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.osbp.vaadin.addons.suggesttext.client.OSuggestBox. |
| * SuggestionDisplay#getCurrentSelection() |
| */ |
| @Override |
| protected Suggestion getCurrentSelection() { |
| if (!isSuggestionListShowing()) { |
| return null; |
| } |
| MenuItem item = suggestionMenu.getSelectedItem(); |
| return item == null ? null : ((SuggestionMenuItem) item).getSuggestion(); |
| } |
| |
| /** |
| * Get the {@link PopupPanel} used to display suggestions. |
| * |
| * @return the popup panel |
| */ |
| protected PopupPanel getPopupPanel() { |
| return suggestionPopup; |
| } |
| |
| /** |
| * Get the {@link MenuBar} used to display suggestions. |
| * |
| * @return the suggestions menu |
| */ |
| protected MenuBar getSuggestionMenu() { |
| return suggestionMenu; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.osbp.vaadin.addons.suggesttext.client.OSuggestBox. |
| * SuggestionDisplay#moveSelectionDown() |
| */ |
| @Override |
| protected void moveSelectionDown() { |
| // Make sure that the menu is actually showing. These keystrokes |
| // are only relevant when choosing a suggestion. |
| if (isSuggestionListShowing()) { |
| // If nothing is selected, getSelectedItemIndex will return -1 |
| // and we |
| // will select index 0 (the first item) by default. |
| suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() + 1); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.osbp.vaadin.addons.suggesttext.client.OSuggestBox. |
| * SuggestionDisplay#moveSelectionUp() |
| */ |
| @Override |
| protected void moveSelectionUp() { |
| // Make sure that the menu is actually showing. These keystrokes |
| // are only relevant when choosing a suggestion. |
| if (isSuggestionListShowing()) { |
| // if nothing is selected, then we should select the last |
| // suggestion by |
| // default. This is because, in some cases, the suggestions menu |
| // will |
| // appear above the text box rather than below it (for example, |
| // if the |
| // text box is at the bottom of the window and the suggestions |
| // will not |
| // fit below the text box). In this case, users would expect to |
| // be able |
| // to use the up arrow to navigate to the suggestions. |
| if (suggestionMenu.getSelectedItemIndex() == -1) { |
| suggestionMenu.selectItem(suggestionMenu.getNumItems() - 1); |
| } else { |
| suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() - 1); |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.osbp.vaadin.addons.suggesttext.client.OSuggestBox. |
| * SuggestionDisplay#showSuggestions(org.eclipse.osbp.vaadin.addons. |
| * suggesttext.client.OSuggestBox, java.util.Collection, boolean, |
| * boolean, |
| * org.eclipse.osbp.vaadin.addons.suggesttext.client.OSuggestBox. |
| * SuggestionCallback) |
| */ |
| @Override |
| protected void showSuggestions(final OSuggestBox suggestBox, Collection<? extends Suggestion> suggestions, |
| boolean isDisplayStringHTML, boolean isAutoSelectEnabled, final SuggestionCallback callback) { |
| // Hide the popup if there are no suggestions to display. |
| boolean anySuggestions = (suggestions != null && suggestions.size() > 0); |
| if (!anySuggestions && hideWhenEmpty) { |
| hideSuggestions(); |
| return; |
| } |
| |
| // Hide the popup before we manipulate the menu within it. If we do |
| // not |
| // do this, some browsers will redraw the popup as items are removed |
| // and added to the menu. |
| if (suggestionPopup.isAttached()) { |
| suggestionPopup.hide(); |
| } |
| |
| suggestionMenu.clearItems(); |
| |
| for (final Suggestion curSuggestion : suggestions) { |
| final SuggestionMenuItem menuItem = new SuggestionMenuItem(curSuggestion); |
| menuItem.setScheduledCommand(new ScheduledCommand() { |
| public void execute() { |
| callback.onSuggestionSelected(curSuggestion); |
| } |
| }); |
| |
| suggestionMenu.addItem(menuItem); |
| } |
| |
| if (isAutoSelectEnabled && anySuggestions) { |
| // Select the first item in the suggestion menu. |
| suggestionMenu.selectItem(0); |
| } |
| |
| // Link the popup autoHide to the TextBox. |
| if (lastSuggestBox != suggestBox) { |
| // If the suggest box has changed, free the old one first. |
| if (lastSuggestBox != null) { |
| suggestionPopup.removeAutoHidePartner(lastSuggestBox.getElement()); |
| } |
| lastSuggestBox = suggestBox; |
| suggestionPopup.addAutoHidePartner(suggestBox.getElement()); |
| } |
| |
| // Show the popup under the TextBox. |
| suggestionPopup.showRelativeTo(positionRelativeTo != null ? positionRelativeTo : suggestBox); |
| } |
| |
| @Override |
| protected void setAutoHide(boolean autoHide) { |
| suggestionPopup.setAutoHideEnabled(autoHide); |
| } |
| |
| @Override |
| protected void setOwner(OSuggestBox oSuggestBox) { |
| this.oSuggestBox = oSuggestBox; |
| } |
| |
| /** |
| * Gets the 'overlay container' element. Tries to find the current |
| * {@link ApplicationConnection} using |
| * {@link #getApplicationConnection()}. |
| * |
| * @return the overlay container element for the current |
| * {@link ApplicationConnection} or another element if the |
| * current {@link ApplicationConnection} cannot be determined. |
| */ |
| public Element getOverlayContainer() { |
| ApplicationConnection ac = getApplicationConnection(); |
| if (ac == null) { |
| // could not figure out which one we belong to, styling will |
| // probably fail |
| Logger.getLogger(getClass().getSimpleName()).log(Level.WARNING, |
| "Could not determine ApplicationConnection for Overlay. Overlay will be attached directly to the root panel"); |
| return RootPanel.get().getElement(); |
| } else { |
| return getOverlayContainer(ac); |
| } |
| } |
| |
| /** |
| * Get the {@link ApplicationConnection} that this overlay belongs to. |
| * |
| * @return |
| */ |
| protected ApplicationConnection getApplicationConnection() { |
| ComponentConnector c = Util.findConnectorFor(oSuggestBox); |
| if (c != null) { |
| return c.getConnection(); |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the 'overlay container' element pertaining to the given |
| * {@link ApplicationConnection}. Each overlay should be created in a |
| * overlay container element, so that the correct theme and styles can |
| * be applied. |
| * |
| * @param ac |
| * A reference to {@link ApplicationConnection} |
| * @return The overlay container |
| */ |
| @SuppressWarnings("deprecation") |
| public Element getOverlayContainer(ApplicationConnection ac) { |
| String id = ac.getConfiguration().getRootPanelId(); |
| id = id += "-overlays"; |
| Element container = DOM.getElementById(id); |
| if (container == null) { |
| container = DOM.createDiv(); |
| container.setId(id); |
| String styles = ac.getUIConnector().getWidget().getParent().getStyleName(); |
| if (styles != null && !styles.equals("")) { |
| container.addClassName(styles); |
| } |
| container.addClassName(VOverlay.CLASSNAME_CONTAINER); |
| RootPanel.get().getElement().appendChild(container); |
| } |
| return DOM.asOld(container); |
| } |
| |
| @Override |
| protected boolean isShowing() { |
| return suggestionPopup.isShowing(); |
| } |
| } |
| |
| /** |
| * The SuggestionMenu class is used for the display and selection of |
| * suggestions in the XSuggestBox widget. SuggestionMenu differs from |
| * MenuBar in that it always has a vertical orientation, and it has no |
| * submenus. It also allows for programmatic selection of items in the menu, |
| * and programmatically performing the action associated with the selected |
| * item. In the MenuBar class, items cannot be selected programatically - |
| * they can only be selected when the user places the mouse over a particlar |
| * item. Additional methods in SuggestionMenu provide information about the |
| * number of items in the menu, and the index of the currently selected |
| * item. |
| */ |
| private static class SuggestionMenu extends MenuBar { |
| |
| /** |
| * Instantiates a new suggestion menu. |
| * |
| * @param vertical |
| * the vertical |
| */ |
| public SuggestionMenu(boolean vertical) { |
| super(vertical); |
| // Make sure that CSS styles specified for the default Menu classes |
| // do not affect this menu |
| setStyleName(""); |
| setFocusOnHoverEnabled(false); |
| } |
| |
| /** |
| * Gets the num items. |
| * |
| * @return the num items |
| */ |
| public int getNumItems() { |
| return getItems().size(); |
| } |
| |
| /** |
| * Returns the index of the menu item that is currently selected. |
| * |
| * @return returns the selected item |
| */ |
| public int getSelectedItemIndex() { |
| // The index of the currently selected item can only be |
| // obtained if the menu is showing. |
| MenuItem selectedItem = getSelectedItem(); |
| if (selectedItem != null) { |
| return getItems().indexOf(selectedItem); |
| } |
| return -1; |
| } |
| |
| /** |
| * Selects the item at the specified index in the menu. Selecting the |
| * item does not perform the item's associated action; it only changes |
| * the style of the item and updates the value of |
| * SuggestionMenu.selectedItem. |
| * |
| * @param index |
| * index |
| */ |
| public void selectItem(int index) { |
| List<MenuItem> items = getItems(); |
| if (index > -1 && index < items.size()) { |
| selectItem(items.get(index)); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.MenuBar#getSelectedItem() |
| */ |
| @Override |
| protected MenuItem getSelectedItem() { |
| return super.getSelectedItem(); |
| } |
| } |
| |
| /** |
| * Class for menu items in a SuggestionMenu. A SuggestionMenuItem differs |
| * from a MenuItem in that each item is backed by a Suggestion object. The |
| * text of each menu item is derived from the display string of a Suggestion |
| * object, and each item stores a reference to its Suggestion object. |
| */ |
| private static class SuggestionMenuItem extends MenuItem { |
| |
| /** The Constant STYLENAME_DEFAULT. */ |
| private static final String STYLENAME_DEFAULT = "item"; |
| |
| /** The suggestion. */ |
| private Suggestion suggestion; |
| |
| /** |
| * Instantiates a new suggestion menu item. |
| * |
| * @param suggestion |
| * the suggestion |
| */ |
| public SuggestionMenuItem(Suggestion suggestion) { |
| super(suggestion.getDisplayString(), (SuggestionMenu) null); |
| // Each suggestion should be placed in a single row in the |
| // suggestion |
| // menu. If the window is resized and the suggestion cannot fit on a |
| // single row, it should be clipped (instead of wrapping around and |
| // taking up a second row). |
| getElement().getStyle().setProperty("whiteSpace", "nowrap"); |
| setStyleName(STYLENAME_DEFAULT); |
| setSuggestion(suggestion); |
| } |
| |
| /** |
| * Gets the suggestion. |
| * |
| * @return the suggestion |
| */ |
| public Suggestion getSuggestion() { |
| return suggestion; |
| } |
| |
| /** |
| * Sets the suggestion. |
| * |
| * @param suggestion |
| * the new suggestion |
| */ |
| public void setSuggestion(Suggestion suggestion) { |
| this.suggestion = suggestion; |
| } |
| } |
| |
| /** The Constant STYLENAME_DEFAULT. */ |
| private static final String STYLENAME_DEFAULT = "o-XSuggestBox"; |
| |
| /** The limit. */ |
| private int limit = 20; |
| |
| /** The selects first item. */ |
| private boolean selectsFirstItem = true; |
| |
| /** The oracle. */ |
| private SuggestOracle oracle; |
| |
| /** The current text. */ |
| private String currentText; |
| |
| /** The editor. */ |
| private LeafValueEditor<String> editor; |
| |
| /** The display. */ |
| private final SuggestionDisplay display; |
| |
| /** The box. */ |
| private ValueBoxBase<String> box; |
| |
| /** The callback. */ |
| private final Callback callback = new Callback() { |
| public void onSuggestionsReady(Request request, Response response) { |
| // If disabled while request was in-flight, drop it |
| if (!isEnabled()) { |
| return; |
| } |
| display.setMoreSuggestions(response.hasMoreSuggestions(), response.getMoreSuggestionsCount()); |
| display.showSuggestions(OSuggestBox.this, response.getSuggestions(), oracle.isDisplayStringHTML(), |
| isAutoSelectEnabled(), suggestionCallback); |
| } |
| }; |
| |
| /** The suggestion callback. */ |
| private final SuggestionCallback suggestionCallback = new SuggestionCallback() { |
| public void onSuggestionSelected(Suggestion suggestion) { |
| box.setFocus(true); |
| setNewSelection(suggestion); |
| } |
| }; |
| |
| private int openPopupKey; |
| |
| private int openPopupModifier; |
| |
| private Element suggestModeDiv; |
| |
| private String disableSuggestionTooltip; |
| |
| private SuggestTextFieldServerRpc serverRpc; |
| |
| /** |
| * Constructor for {@link OSuggestBox}. The text box will be removed from |
| * it's current location and wrapped by the {@link OSuggestBox}. |
| * |
| * @param oracle |
| * supplies suggestions based upon the current contents of the |
| * text widget |
| */ |
| public OSuggestBox(SuggestOracle oracle) { |
| this(oracle, new DefaultSuggestionDisplay()); |
| } |
| |
| /** |
| * Constructor for {@link OSuggestBox}. The text box will be removed from |
| * it's current location and wrapped by the {@link OSuggestBox}. |
| * |
| * @param oracle |
| * supplies suggestions based upon the current contents of the |
| * text widget |
| * @param suggestDisplay |
| * the class used to display suggestions |
| */ |
| public OSuggestBox(SuggestOracle oracle, SuggestionDisplay suggestDisplay) { |
| this.display = suggestDisplay; |
| this.display.setOwner(this); |
| |
| setOracle(oracle); |
| |
| ((ODelegatingOracle) getSuggestOracle()).setSuggestBox(this); |
| } |
| |
| /** |
| * Sets the popup delay. |
| * |
| * @param popupDelay |
| * the new popup delay |
| */ |
| public void setPopupDelay(int popupDelay) { |
| ((ODelegatingOracle) getSuggestOracle()).setPopupDelay(popupDelay); |
| } |
| |
| /** |
| * Sets the single suggestion and close popup. |
| * |
| * @param selectedSuggestion |
| * the new single suggestion and close popup |
| */ |
| public void setSingleSuggestionAndClosePopup(Suggestion selectedSuggestion) { |
| assert selectedSuggestion != null : "suggestion cannot be null"; |
| String currentText = selectedSuggestion.getReplacementString(); |
| setTextInternal(currentText); |
| ((DefaultSuggestionDisplay) getSuggestionDisplay()).hideSuggestions(); |
| SelectionEvent.fire(this, selectedSuggestion); |
| } |
| |
| /** |
| * Initialize. |
| * |
| * @param box |
| * the box |
| */ |
| public void initialize(ValueBoxBase<String> box) { |
| assert this.box == null : "box must not be set twice"; |
| this.box = box; |
| |
| setWidget(box); |
| addEventsToTextBox(); |
| |
| Element mainElement = getElement(); |
| |
| suggestModeDiv = DOM.createDiv(); |
| suggestModeDiv.addClassName("enableSuggestions"); |
| mainElement.appendChild(suggestModeDiv); |
| suggestModeDiv.setTitle(disableSuggestionTooltip); |
| |
| if (!isSuggestMode()) { |
| suggestModeDiv.removeClassName(SELECTED_CLASSNAME); |
| } else { |
| suggestModeDiv.addClassName(SELECTED_CLASSNAME); |
| } |
| |
| Event.setEventListener(suggestModeDiv, new EventListener() { |
| @Override |
| public void onBrowserEvent(Event event) { |
| setSuggestionEnabled(!isSuggestMode()); |
| } |
| }); |
| Event.sinkEvents(suggestModeDiv, Event.ONCLICK); |
| |
| setStyleName(STYLENAME_DEFAULT); |
| } |
| |
| public boolean isSuggestMode() { |
| return suggestModeDiv.hasClassName(SELECTED_CLASSNAME); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler(com. |
| * google.gwt.event.dom.client.KeyDownHandler) |
| */ |
| public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { |
| return addDomHandler(handler, KeyDownEvent.getType()); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler( |
| * com.google.gwt.event.dom.client.KeyPressHandler) |
| */ |
| public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) { |
| return addDomHandler(handler, KeyPressEvent.getType()); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * com.google.gwt.event.dom.client.HasKeyUpHandlers#addKeyUpHandler(com. |
| * google.gwt.event.dom.client.KeyUpHandler) |
| */ |
| public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) { |
| return addDomHandler(handler, KeyUpEvent.getType()); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.event.logical.shared.HasSelectionHandlers# |
| * addSelectionHandler(com.google.gwt.event.logical.shared.SelectionHandler) |
| */ |
| public HandlerRegistration addSelectionHandler(SelectionHandler<Suggestion> handler) { |
| return addHandler(handler, SelectionEvent.getType()); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers# |
| * addValueChangeHandler(com.google.gwt.event.logical.shared. |
| * ValueChangeHandler) |
| */ |
| public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) { |
| return addHandler(handler, ValueChangeEvent.getType()); |
| } |
| |
| /** |
| * Returns a {@link TakesValueEditor} backed by the XSuggestBox. |
| * |
| * @return the leaf value editor |
| */ |
| public LeafValueEditor<String> asEditor() { |
| if (editor == null) { |
| editor = TakesValueEditor.of(this); |
| } |
| return editor; |
| } |
| |
| /** |
| * Gets the limit for the number of suggestions that should be displayed for |
| * this box. It is up to the current {@link SuggestOracle} to enforce this |
| * limit. |
| * |
| * @return the limit for the number of suggestions |
| */ |
| public int getLimit() { |
| return limit; |
| } |
| |
| /** |
| * Get the {@link SuggestionDisplay} used to display suggestions. |
| * |
| * @return the {@link SuggestionDisplay} |
| */ |
| public SuggestionDisplay getSuggestionDisplay() { |
| return display; |
| } |
| |
| /** |
| * Gets the suggest box's |
| * {@link com.google.gwt.user.client.ui.SuggestOracle}. |
| * |
| * @return the {@link SuggestOracle} |
| */ |
| public SuggestOracle getSuggestOracle() { |
| return oracle; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.Focusable#getTabIndex() |
| */ |
| public int getTabIndex() { |
| return box.getTabIndex(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasText#getText() |
| */ |
| public String getText() { |
| return box.getText(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasValue#getValue() |
| */ |
| public String getValue() { |
| return box.getValue(); |
| } |
| |
| /** |
| * Get the ValueBoxBase associated with this suggest box. |
| * |
| * @return this suggest box's value box |
| */ |
| public ValueBoxBase<String> getValueBox() { |
| return box; |
| } |
| |
| /** |
| * Returns whether or not the first suggestion will be automatically |
| * selected. This behavior is on by default. |
| * |
| * @return true if the first suggestion will be automatically selected |
| */ |
| public boolean isAutoSelectEnabled() { |
| return selectsFirstItem; |
| } |
| |
| /** |
| * Gets whether this widget is enabled. |
| * |
| * @return <code>true</code> if the widget is enabled |
| */ |
| public boolean isEnabled() { |
| return box.isEnabled(); |
| } |
| |
| /** |
| * Refreshes the current list of suggestions. |
| */ |
| public void refreshSuggestionList() { |
| if (isAttached()) { |
| refreshSuggestions(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.Focusable#setAccessKey(char) |
| */ |
| public void setAccessKey(char key) { |
| box.setAccessKey(key); |
| } |
| |
| /** |
| * Turns on or off the behavior that automatically selects the first |
| * suggested item. This behavior is on by default. |
| * |
| * @param selectsFirstItem |
| * Whether or not to automatically select the first suggestion |
| */ |
| public void setAutoSelectEnabled(boolean selectsFirstItem) { |
| this.selectsFirstItem = selectsFirstItem; |
| } |
| |
| /** |
| * Opens the popup and shows the suggestions. |
| */ |
| public void openPopup() { |
| showSuggestionList(); |
| } |
| |
| /** |
| * Closes the popup. |
| */ |
| public void closePopup() { |
| display.hideSuggestions(); |
| } |
| |
| /** |
| * Navigates the selected suggestion to the next one available. |
| */ |
| public void navigateToNext() { |
| display.moveSelectionDown(); |
| } |
| |
| /** |
| * Navigates the selected suggestion to the previous one available. |
| */ |
| public void navigateToPrevious() { |
| display.moveSelectionUp(); |
| } |
| |
| public void selectCurrent() { |
| acceptSuggestion(); |
| } |
| |
| /** |
| * If true, then the popup hides if the user clicks outside the popup. |
| * |
| * @param autoHide |
| */ |
| public void setAutoHide(boolean autoHide) { |
| display.setAutoHide(autoHide); |
| } |
| |
| /** |
| * Sets whether this widget is enabled. |
| * |
| * @param enabled |
| * <code>true</code> to enable the widget, <code>false</code> to |
| * disable it |
| */ |
| public void setEnabled(boolean enabled) { |
| box.setEnabled(enabled); |
| if (!enabled) { |
| display.hideSuggestions(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.Focusable#setFocus(boolean) |
| */ |
| public void setFocus(boolean focused) { |
| box.setFocus(focused); |
| } |
| |
| /** |
| * Sets the limit to the number of suggestions the oracle should provide. It |
| * is up to the oracle to enforce this limit. |
| * |
| * @param limit |
| * the limit to the number of suggestions provided |
| */ |
| public void setLimit(int limit) { |
| this.limit = limit; |
| } |
| |
| /** |
| * Sets the key which need to be pressed to open the popup.<br> |
| * See {@link ShortcutAction.KeyCode} |
| * |
| * @param key |
| * the new key |
| */ |
| public void setOpenPopupKey(int key) { |
| openPopupKey = key; |
| } |
| |
| /** |
| * Sets the key modifier which need to be pressed to open the popup.<br> |
| * See {@link ShortcutAction.ModifierKey} |
| * |
| * @param modifier |
| * the new modifier |
| */ |
| public void setOpenPopupKeyModifier(int modifier) { |
| openPopupModifier = modifier; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.Focusable#setTabIndex(int) |
| */ |
| public void setTabIndex(int index) { |
| box.setTabIndex(index); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasText#setText(java.lang.String) |
| */ |
| public void setText(String text) { |
| setTextInternal(text); |
| // refresh the suggestions |
| refreshSuggestions(); |
| } |
| |
| public void setTextInternal(String text) { |
| box.setText(text); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object) |
| */ |
| public void setValue(String newValue) { |
| box.setValue(newValue); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, |
| * boolean) |
| */ |
| public void setValue(String value, boolean fireEvents) { |
| box.setValue(value, fireEvents); |
| } |
| |
| /** |
| * Show the current list of suggestions. |
| */ |
| public void showSuggestionList() { |
| if (isAttached()) { |
| currentText = null; |
| refreshSuggestions(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * com.google.gwt.user.client.ui.UIObject#onEnsureDebugId(java.lang.String) |
| */ |
| @Override |
| protected void onEnsureDebugId(String baseID) { |
| super.onEnsureDebugId(baseID); |
| display.onEnsureDebugId(baseID); |
| } |
| |
| /** |
| * Show suggestions. |
| * |
| * @param query |
| * the query |
| */ |
| void showSuggestions(String query) { |
| if (query.length() == 0) { |
| oracle.requestDefaultSuggestions(new Request(null, limit), callback); |
| } else { |
| oracle.requestSuggestions(new Request(query, limit), callback); |
| } |
| } |
| |
| protected void acceptSuggestion() { |
| Suggestion suggestion = display.getCurrentSelection(); |
| if (suggestion == null) { |
| display.hideSuggestions(); |
| } else { |
| setNewSelection(suggestion); |
| } |
| } |
| |
| /** |
| * Adds the events to text box. |
| */ |
| private void addEventsToTextBox() { |
| class TextBoxEvents implements KeyDownHandler, KeyUpHandler, ValueChangeHandler<String> { |
| public void onKeyDown(KeyDownEvent event) { |
| if (event.isAnyModifierKeyDown()) { |
| if (isOpenPopupKey(event) && isOpenPopupModifier(event)) { |
| if (!display.isShowing()) { |
| showSuggestionList(); |
| } |
| } |
| } else { |
| switch (event.getNativeKeyCode()) { |
| case KeyCodes.KEY_DOWN: |
| if (display.isShowing()) { |
| display.moveSelectionDown(); |
| } |
| break; |
| case KeyCodes.KEY_UP: |
| if (display.isShowing()) { |
| display.moveSelectionUp(); |
| } |
| break; |
| case KeyCodes.KEY_ESCAPE: |
| if (display.isShowing()) { |
| display.hideSuggestions(); |
| } |
| break; |
| case KeyCodes.KEY_ENTER: |
| case KeyCodes.KEY_TAB: |
| if (display.isShowing()) { |
| acceptSuggestion(); |
| } |
| break; |
| } |
| } |
| } |
| |
| public void onKeyUp(KeyUpEvent event) { |
| if (!event.isAnyModifierKeyDown()) { |
| // After every user key input, refresh the popup's |
| // suggestions. |
| refreshSuggestions(); |
| } |
| } |
| |
| public void onValueChange(ValueChangeEvent<String> event) { |
| // reset the current handle to avoid wrong suggestions which |
| // arrive late |
| ((ODelegatingOracle) oracle).resetCurrentHandle(); |
| |
| delegateEvent(OSuggestBox.this, event); |
| } |
| } |
| |
| TextBoxEvents events = new TextBoxEvents(); |
| box.addKeyDownHandler(events); |
| box.addKeyUpHandler(events); |
| box.addValueChangeHandler(events); |
| } |
| |
| public boolean isOpenPopupKey(KeyDownEvent event) { |
| return event.getNativeEvent().getKeyCode() == openPopupKey; |
| } |
| |
| public boolean isOpenPopupModifier(KeyDownEvent event) { |
| switch (openPopupModifier) { |
| case KeyCodes.KEY_ALT: |
| return event.isAltKeyDown(); |
| case KeyCodes.KEY_CTRL: |
| return event.isControlKeyDown(); |
| } |
| return false; |
| } |
| |
| /** |
| * Fire suggestion event. |
| * |
| * @param selectedSuggestion |
| * the selected suggestion |
| */ |
| private void fireSuggestionEvent(Suggestion selectedSuggestion) { |
| SelectionEvent.fire(this, selectedSuggestion); |
| } |
| |
| /** |
| * Refresh suggestions. |
| */ |
| private void refreshSuggestions() { |
| // Get the raw text. |
| String text = getText(); |
| if (text.equals(currentText)) { |
| return; |
| } else { |
| currentText = text; |
| } |
| showSuggestions(text); |
| } |
| |
| /** |
| * Set the new suggestion in the text box. |
| * |
| * @param curSuggestion |
| * the new suggestion |
| */ |
| private void setNewSelection(Suggestion curSuggestion) { |
| assert curSuggestion != null : "suggestion cannot be null"; |
| currentText = curSuggestion.getReplacementString(); |
| setTextInternal(currentText); |
| display.hideSuggestions(); |
| fireSuggestionEvent(curSuggestion); |
| } |
| |
| /** |
| * Sets the suggestion oracle used to create suggestions. |
| * |
| * @param oracle |
| * the oracle |
| */ |
| private void setOracle(SuggestOracle oracle) { |
| this.oracle = oracle; |
| } |
| |
| /** |
| * Sets the suggestion enabled. |
| * |
| * @param suggestionEnabled |
| * the new suggestion enabled |
| */ |
| public void setSuggestionEnabled(boolean suggestionEnabled) { |
| if (((ODelegatingOracle) oracle).isSuggestionEnabled() == suggestionEnabled) { |
| return; |
| } |
| |
| ((ODelegatingOracle) oracle).setSuggestionEnabled(suggestionEnabled); |
| |
| serverRpc.setSuggestionEnabled(suggestionEnabled); |
| |
| if (!suggestionEnabled) { |
| suggestModeDiv.removeClassName(SELECTED_CLASSNAME); |
| } else { |
| suggestModeDiv.addClassName(SELECTED_CLASSNAME); |
| } |
| } |
| |
| public void setDisableSuggestionTooltip(String disableSuggestionTooltip) { |
| this.disableSuggestionTooltip = disableSuggestionTooltip; |
| |
| if (suggestModeDiv != null) { |
| suggestModeDiv.setTitle(disableSuggestionTooltip); |
| } |
| } |
| |
| public void setServerRPC(SuggestTextFieldServerRpc serverRpc) { |
| this.serverRpc = serverRpc; |
| } |
| |
| } |