Package com.google.gwt.user.client.ui

Source Code of com.google.gwt.user.client.ui.SuggestBox$SuggestionPopup

/*
* Copyright 2008 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 com.google.gwt.user.client.ui;

import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel.AnimationType;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* A {@link SuggestBox} is a text box or text area which displays a
* pre-configured set of selections that match the user's input.
*
* Each {@link SuggestBox} is associated with a single {@link SuggestOracle}.
* The {@link SuggestOracle} is used to provide a set of selections given a
* specific query string.
*
* <p>
* By default, the {@link SuggestBox} uses a {@link MultiWordSuggestOracle} as
* its oracle. Below we show how a {@link MultiWordSuggestOracle} can be
* configured:
* </p>
*
* <pre>
*   MultiWordSuggestOracle oracle = new MultiWordSuggestOracle(); 
*   oracle.add("Cat");
*   oracle.add("Dog");
*   oracle.add("Horse");
*   oracle.add("Canary");
*  
*   SuggestBox box = new SuggestBox(oracle);
* </pre>
*
* Using the example above, if the user types "C" into the text widget, the
* oracle will configure the suggestions with the "Cat" and "Canary"
* suggestions. Specifically, whenever the user types a key into the text
* widget, the value is submitted to the <code>MultiWordSuggestOracle</code>.
*
* <p>
* Note that there is no method to retrieve the "currently selected suggestion"
* in a SuggestBox, because there are points in time where the currently
* selected suggestion is not defined. For example, if the user types in some
* text that does not match any of the SuggestBox's suggestions, then the
* SuggestBox will not have a currently selected suggestion. It is more useful
* to know when a suggestion has been chosen from the SuggestBox's list of
* suggestions. A SuggestBox fires {@link SuggestionEvent SuggestionEvents}
* whenever a suggestion is chosen, and handlers for these events can be added
* using the {@link #addEventHandler(SuggestionHandler)} method.
* </p>
*
* <p>
* <img class='gallery' src='SuggestBox.png'/>
* </p>
*
* <h3>CSS Style Rules</h3>
* <ul class='css'>
* <li>.gwt-SuggestBox { the suggest box itself }</li>
* <li>.gwt-SuggestBoxPopup { the suggestion popup }</li>
* <li>.gwt-SuggestBoxPopup .item { an unselected suggestion }</li>
* <li>.gwt-SuggestBoxPopup .item-selected { a selected suggestion }</li>
* </ul>
*
* @see SuggestOracle
* @see MultiWordSuggestOracle
* @see TextBoxBase
*/
public final class SuggestBox extends Composite implements HasText, HasFocus,
    HasAnimation, SourcesClickEvents, SourcesFocusEvents, SourcesChangeEvents,
    SourcesKeyboardEvents, FiresSuggestionEvents {

  /**
   * The SuggestionMenu class is used for the display and selection of
   * suggestions in the SuggestBox 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 {

    public SuggestionMenu(boolean vertical) {
      super(vertical);
      // Make sure that CSS styles specified for the default Menu classes
      // do not affect this menu
      setStyleName("");
    }

    public void doSelectedItemAction() {
      // In order to perform the action of the item that is currently
      // selected, the menu must be showing.
      MenuItem selectedItem = getSelectedItem();
      if (selectedItem != null) {
        doItemAction(selectedItem, true);
      }
    }

    public int getNumItems() {
      return getItems().size();
    }

    /**
     * Returns the index of the menu item that is currently selected.
     */
    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.
     */
    public void selectItem(int index) {
      List<MenuItem> items = getItems();
      if (index > -1 && index < items.size()) {
        itemOver(items.get(index));
      }
    }
  }

  /**
   * 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 {

    private static final String STYLENAME_DEFAULT = "item";

    private Suggestion suggestion;

    public SuggestionMenuItem(Suggestion suggestion, boolean asHTML) {
      super(suggestion.getDisplayString(), asHTML);
      // 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).
      DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap");
      setStyleName(STYLENAME_DEFAULT);
      setSuggestion(suggestion);
    }

    public Suggestion getSuggestion() {
      return suggestion;
    }

    public void setSuggestion(Suggestion suggestion) {
      this.suggestion = suggestion;
    }
  }

  /**
   * A PopupPanel with a SuggestionMenu as its widget. The SuggestionMenu is
   * placed in a PopupPanel so that it can be displayed at various positions
   * around the SuggestBox's text field. Moreover, the SuggestionMenu needs to
   * appear on top of any other widgets on the page, and the PopupPanel provides
   * this behavior.
   *
   * A non-static member class is used because the popup uses the SuggestBox's
   * SuggestionMenu as its widget, and the position of the SuggestBox's TextBox
   * is needed in order to correctly position the popup.
   */
  private class SuggestionPopup extends PopupPanel {

    private static final String STYLENAME_DEFAULT = "gwt-SuggestBoxPopup";

    public SuggestionPopup() {
      super(true);
      setWidget(suggestionMenu);
      setStyleName(STYLENAME_DEFAULT);
    }

    /**
     * The default position of the SuggestPopup is directly below the
     * SuggestBox's text box, with its left edge aligned with the left edge of
     * the text box. Depending on the width and height of the popup and the
     * distance from the text box to the bottom and right edges of the window,
     * the popup may be displayed directly above the text box, and/or its right
     * edge may be aligned with the right edge of the text box.
     */
    public void showAlignedPopup() {

      // Set the position of the popup right before it is shown.
      setPopupPositionAndShow(new PositionCallback() {
        public void setPosition(int offsetWidth, int offsetHeight) {

          // Calculate left position for the popup. The computation for
          // the left position is bidi-sensitive.

          int textBoxOffsetWidth = box.getOffsetWidth();

          // Compute the difference between the popup's width and the
          // textbox's width
          int offsetWidthDiff = offsetWidth - textBoxOffsetWidth;

          int left;

          if (LocaleInfo.getCurrentLocale().isRTL()) { // RTL case

            int textBoxAbsoluteLeft = box.getAbsoluteLeft();

            // Right-align the popup. Note that this computation is
            // valid in the case where offsetWidthDiff is negative.
            left = textBoxAbsoluteLeft - offsetWidthDiff;

            // If the suggestion popup is not as wide as the text box, always
            // align
            // to the right edge of the text box. Otherwise, figure out whether
            // to
            // right-align or left-align the popup.
            if (offsetWidthDiff > 0) {

              // Make sure scrolling is taken into account, since
              // box.getAbsoluteLeft()
              // takes scrolling into account.
              int windowRight = Window.getClientWidth()
                  + Window.getScrollLeft();
              int windowLeft = Window.getScrollLeft();

              // Compute the left value for the right edge of the textbox
              int textBoxLeftValForRightEdge = textBoxAbsoluteLeft
                  + textBoxOffsetWidth;

              // Distance from the right edge of the text box to the right edge
              // of the
              // window
              int distanceToWindowRight = windowRight
                  - textBoxLeftValForRightEdge;

              // Distance from the right edge of the text box to the left edge
              // of the
              // window
              int distanceFromWindowLeft = textBoxLeftValForRightEdge
                  - windowLeft;

              // If there is not enough space for the overflow of the popup's
              // width to
              // the right of the text box and there IS enough space for the
              // overflow
              // to the right of the text box, then left-align the popup.
              // However, if
              // there is not enough space on either side, stick with
              // right-alignment.
              if (distanceFromWindowLeft < offsetWidth
                  && distanceToWindowRight >= offsetWidthDiff) {
                // Align with the left edge of the text box.
                left = textBoxAbsoluteLeft;
              }
            }
          } else { // LTR case

            // Left-align the popup.
            left = box.getAbsoluteLeft();

            // If the suggestion popup is not as wide as the text box, always
            // align
            // to the left edge of the text box. Otherwise, figure out whether
            // to
            // left-align or right-align the popup.
            if (offsetWidthDiff > 0) {
              // Make sure scrolling is taken into account, since
              // box.getAbsoluteLeft()
              // takes scrolling into account.
              int windowRight = Window.getClientWidth()
                  + Window.getScrollLeft();
              int windowLeft = Window.getScrollLeft();

              // Distance from the left edge of the text box to the right edge
              // of the
              // window
              int distanceToWindowRight = windowRight - left;

              // Distance from the left edge of the text box to the left edge of
              // the
              // window
              int distanceFromWindowLeft = left - windowLeft;

              // If there is not enough space for the overflow of the popup's
              // width to
              // the right of hte text box, and there IS enough space for the
              // overflow
              // to the left of the text box, then right-align the popup.
              // However, if
              // there is not enough space on either side, then stick with
              // left-alignment.
              if (distanceToWindowRight < offsetWidth
                  && distanceFromWindowLeft >= offsetWidthDiff) {
                // Align with the right edge of the text box.
                left -= offsetWidthDiff;
              }
            }
          }

          // Calculate top position for the popup

          int top = box.getAbsoluteTop();

          // Make sure scrolling is taken into account, since
          // box.getAbsoluteTop()
          // takes scrolling into account.
          int windowTop = Window.getScrollTop();
          int windowBottom = Window.getScrollTop() + Window.getClientHeight();

          // Distance from the top edge of the window to the top edge of the
          // text box
          int distanceFromWindowTop = top - windowTop;

          // Distance from the bottom edge of the window to the bottom edge of
          // the
          // text box
          int distanceToWindowBottom = windowBottom
              - (top + box.getOffsetHeight());

          // If there is not enough space for the popup's height below the text
          // box
          // and there IS enough space for the popup's height above the text
          // box,
          // then then position the popup above the text box. However, if there
          // is
          // not enough space on either side, then stick with displaying the
          // popup
          // below the text box.
          if (distanceToWindowBottom < offsetHeight
              && distanceFromWindowTop >= offsetHeight) {
            top -= offsetHeight;
          } else {
            // Position above the text box
            top += box.getOffsetHeight();
          }

          setPopupPosition(left, top);
        }
      });
    }
  }

  private static final String STYLENAME_DEFAULT = "gwt-SuggestBox";

  private int limit = 20;
  private SuggestOracle oracle;
  private String currentText;
  private final SuggestionMenu suggestionMenu;
  private final SuggestionPopup suggestionPopup;
  private final TextBoxBase box;
  private ArrayList<SuggestionHandler> suggestionHandlers = null;
  private DelegatingClickListenerCollection clickListeners;
  private DelegatingChangeListenerCollection changeListeners;
  private DelegatingFocusListenerCollection focusListeners;
  private DelegatingKeyboardListenerCollection keyboardListeners;

  private final Callback callBack = new Callback() {
    public void onSuggestionsReady(Request request, Response response) {
      showSuggestions(response.getSuggestions());
    }
  };

  /**
   * Constructor for {@link SuggestBox}. Creates a
   * {@link MultiWordSuggestOracle} and {@link TextBox} to use with this
   * {@link SuggestBox}.
   */
  public SuggestBox() {
    this(new MultiWordSuggestOracle());
  }

  /**
   * Constructor for {@link SuggestBox}. Creates a {@link TextBox} to use with
   * this {@link SuggestBox}.
   *
   * @param oracle the oracle for this <code>SuggestBox</code>
   */
  public SuggestBox(SuggestOracle oracle) {
    this(oracle, new TextBox());
  }

  /**
   * Constructor for {@link SuggestBox}. The text box will be removed from it's
   * current location and wrapped by the {@link SuggestBox}.
   *
   * @param oracle supplies suggestions based upon the current contents of the
   *          text widget
   * @param box the text widget
   */
  public SuggestBox(SuggestOracle oracle, TextBoxBase box) {
    this.box = box;
    initWidget(box);

    // suggestionMenu must be created before suggestionPopup, because
    // suggestionMenu is suggestionPopup's widget
    suggestionMenu = new SuggestionMenu(true);
    suggestionPopup = new SuggestionPopup();
    suggestionPopup.setAnimationType(AnimationType.ONE_WAY_CORNER);

    addKeyboardSupport();
    setOracle(oracle);
    setStyleName(STYLENAME_DEFAULT);
  }

  /**
   * Adds a listener to recieve change events on the SuggestBox's text box. The
   * source Widget for these events will be the SuggestBox.
   *
   * @param listener the listener interface to add
   */
  public void addChangeListener(ChangeListener listener) {
    if (changeListeners == null) {
      changeListeners = new DelegatingChangeListenerCollection(this, box);
    }
    changeListeners.add(listener);
  }

  /**
   * Adds a listener to recieve click events on the SuggestBox's text box. The
   * source Widget for these events will be the SuggestBox.
   *
   * @param listener the listener interface to add
   */
  public void addClickListener(ClickListener listener) {
    if (clickListeners == null) {
      clickListeners = new DelegatingClickListenerCollection(this, box);
    }
    clickListeners.add(listener);
  }

  public void addEventHandler(SuggestionHandler handler) {
    if (suggestionHandlers == null) {
      suggestionHandlers = new ArrayList<SuggestionHandler>();
    }
    suggestionHandlers.add(handler);
  }

  /**
   * Adds a listener to recieve focus events on the SuggestBox's text box. The
   * source Widget for these events will be the SuggestBox.
   *
   * @param listener the listener interface to add
   */
  public void addFocusListener(FocusListener listener) {
    if (focusListeners == null) {
      focusListeners = new DelegatingFocusListenerCollection(this, box);
    }
    focusListeners.add(listener);
  }

  /**
   * Adds a listener to recieve keyboard events on the SuggestBox's text box.
   * The source Widget for these events will be the SuggestBox.
   *
   * @param listener the listener interface to add
   */
  public void addKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners == null) {
      keyboardListeners = new DelegatingKeyboardListenerCollection(this, box);
    }
    keyboardListeners.add(listener);
  }

  /**
   * 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;
  }

  /**
   * Gets the suggest box's {@link com.google.gwt.user.client.ui.SuggestOracle}.
   *
   * @return the {@link SuggestOracle}
   */
  public SuggestOracle getSuggestOracle() {
    return oracle;
  }

  public int getTabIndex() {
    return box.getTabIndex();
  }

  public String getText() {
    return box.getText();
  }

  public boolean isAnimationEnabled() {
    return suggestionPopup.isAnimationEnabled();
  }

  public void removeChangeListener(ChangeListener listener) {
    if (changeListeners != null) {
      changeListeners.remove(listener);
    }
  }

  public void removeClickListener(ClickListener listener) {
    if (clickListeners != null) {
      clickListeners.remove(listener);
    }
  }

  public void removeEventHandler(SuggestionHandler handler) {
    if (suggestionHandlers == null) {
      return;
    }
    suggestionHandlers.remove(handler);
  }

  public void removeFocusListener(FocusListener listener) {
    if (focusListeners != null) {
      focusListeners.remove(listener);
    }
  }

  public void removeKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners != null) {
      keyboardListeners.remove(listener);
    }
  }

  public void setAccessKey(char key) {
    box.setAccessKey(key);
  }

  public void setAnimationEnabled(boolean enable) {
    suggestionPopup.setAnimationEnabled(enable);
  }

  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 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);
  }

  public void setTabIndex(int index) {
    box.setTabIndex(index);
  }

  public void setText(String text) {
    box.setText(text);
  }

  /**
   * <b>Affected Elements:</b>
   * <ul>
   * <li>-popup = The popup that appears with suggestions.</li>
   * <li>-items-item# = The suggested item at the specified index.</li>
   * </ul>
   *
   * @see UIObject#onEnsureDebugId(String)
   */
  @Override
  protected void onEnsureDebugId(String baseID) {
    super.onEnsureDebugId(baseID);
    suggestionPopup.ensureDebugId(baseID + "-popup");
    suggestionMenu.setMenuItemDebugIds(baseID);
  }

  /**
   * Show the given collection of suggestions.
   *
   * @param suggestions suggestions to show
   */
  private void showSuggestions(Collection<? extends Suggestion> suggestions) {
    if (suggestions.size() > 0) {

      /*
       * 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.
       *
       * As an optimization, setVisible(false) is used in place of the hide()
       * method. hide() removes the popup from the DOM, whereas
       * setVisible(false) does not. Since the popup is going to be shown again
       * as soon as the menu is rebuilt, it makes more sense to leave the popup
       * attached to the DOM.
       *
       * Notice that setVisible(true) is never called. This is because the call
       * to showAlignedPopup() will cause show() to be called, which in turn
       * calls setVisible(true).
       */
      suggestionPopup.setVisible(false);

      suggestionMenu.clearItems();

      for (Suggestion curSuggestion : suggestions) {
        final SuggestionMenuItem menuItem = new SuggestionMenuItem(
            curSuggestion, oracle.isDisplayStringHTML());
        menuItem.setCommand(new Command() {
          public void execute() {
            SuggestBox.this.setNewSelection(menuItem);
          }
        });

        suggestionMenu.addItem(menuItem);
      }

      // Select the first item in the suggestion menu.
      suggestionMenu.selectItem(0);

      suggestionPopup.showAlignedPopup();
    } else {
      suggestionPopup.hide();
    }
  }

  private void addKeyboardSupport() {
    box.addKeyboardListener(new KeyboardListenerAdapter() {

      @Override
      public void onKeyDown(Widget sender, char keyCode, int modifiers) {
        // Make sure that the menu is actually showing. These keystrokes
        // are only relevant when choosing a suggestion.
        if (suggestionPopup.isAttached()) {
          switch (keyCode) {
            case KeyboardListener.KEY_DOWN:
              suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() + 1);
              break;
            case KeyboardListener.KEY_UP:
              suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() - 1);
              break;
            case KeyboardListener.KEY_ENTER:
            case KeyboardListener.KEY_TAB:
              suggestionMenu.doSelectedItemAction();
              break;
          }
        }
      }

      @Override
      public void onKeyUp(Widget sender, char keyCode, int modifiers) {
        // After every user key input, refresh the popup's suggestions.
        refreshSuggestions();
      }

      private void refreshSuggestions() {
        // Get the raw text.
        String text = box.getText();
        if (text.equals(currentText)) {
          return;
        } else {
          currentText = text;
        }

        if (text.length() == 0) {
          // Optimization to avoid calling showSuggestions with an empty
          // string
          suggestionPopup.hide();
          suggestionMenu.clearItems();
        } else {
          showSuggestions(text);
        }
      }
    });
  }

  private void fireSuggestionEvent(Suggestion selectedSuggestion) {
    if (suggestionHandlers != null) {
      SuggestionEvent event = new SuggestionEvent(this, selectedSuggestion);
      for (SuggestionHandler handler : suggestionHandlers) {
        handler.onSuggestionSelected(event);
      }
    }
  }

  private void setNewSelection(SuggestionMenuItem menuItem) {
    Suggestion curSuggestion = menuItem.getSuggestion();
    currentText = curSuggestion.getReplacementString();
    box.setText(currentText);
    suggestionPopup.hide();
    fireSuggestionEvent(curSuggestion);
  }

  /**
   * Sets the suggestion oracle used to create suggestions.
   *
   * @param oracle the oracle
   */
  private void setOracle(SuggestOracle oracle) {
    this.oracle = oracle;
  }

  private void showSuggestions(String query) {
    oracle.requestSuggestions(new Request(query, limit), callBack);
  }
}
TOP

Related Classes of com.google.gwt.user.client.ui.SuggestBox$SuggestionPopup

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.