blob: 62dbeec5c4d17635bbb5935cac18f34140885a85 [file] [log] [blame]
/**
*
* 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;
}
}