KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > user > client > ui > SuggestBox


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.user.client.ui;
17
18 import com.google.gwt.user.client.ui.SuggestOracle.Callback;
19 import com.google.gwt.user.client.ui.SuggestOracle.Request;
20 import com.google.gwt.user.client.ui.SuggestOracle.Response;
21 import com.google.gwt.user.client.ui.impl.ItemPickerDropDownImpl;
22 import com.google.gwt.user.client.ui.impl.SuggestPickerImpl;
23
24 import java.util.Collection JavaDoc;
25
26 /**
27  * A {@link SuggestBox} is a text box or text area which displays a
28  * pre-configured set of selections that match the user's input.
29  *
30  * Each {@link SuggestBox} is associated with a single {@link SuggestOracle}.
31  * The {@link SuggestOracle} is used to provide a set of selections given a
32  * specific query string.
33  *
34  * <p>
35  * By default, the {@link SuggestBox} uses a {@link MultiWordSuggestOracle} as
36  * its oracle. Below we show how a {@link MultiWordSuggestOracle} can be
37  * configured:
38  * </p>
39  *
40  * <pre>
41  * MultiWordSuggestOracle oracle = new MultiWordSuggestOracle();
42  * oracle.add("Cat");
43  * oracle.add("Dog");
44  * oracle.add("Horse");
45  * oracle.add("Canary");
46  *
47  * SuggestBox box = new SuggestBox(oracle);
48  * </pre>
49  *
50  * Using the example above, if the user types "C" into the text widget, the
51  * oracle will configure the suggestions with the "Cat" and "Canary"
52  * suggestions. Specifically, whenever the user types a key into the text
53  * widget, the value is submitted to the <code>MultiWordSuggestOracle</code>.
54  *
55  * <p>
56  * <img class='gallery' SRC='SuggestBox.png'/>
57  * </p>
58  *
59  * <h3>CSS Style Rules</h3>
60  * <ul class='css'>
61  * <li>.gwt-SuggestBox { the suggest box itself }</li>
62  * <li>.gwt-SuggestBoxPopup { the suggestion popup }</li>
63  * <li>.gwt-SuggestBoxPopup .item { an unselected suggestion }</li>
64  * <li>.gwt-SuggestBoxPopup .item-selected { a selected suggestion }</li>
65  * </ul>
66  *
67  * @see SuggestOracle, MultiWordSuggestOracle, TextBoxBase
68  */

69 public final class SuggestBox extends Composite implements HasText, HasFocus,
70     SourcesClickEvents, SourcesFocusEvents, SourcesChangeEvents,
71     SourcesKeyboardEvents {
72
73   private static final String JavaDoc STYLENAME_DEFAULT = "gwt-SuggestBox";
74
75   private int limit = 20;
76   private int selectStart;
77   private int selectEnd;
78   private SuggestOracle oracle;
79   private char[] separators;
80   private String JavaDoc currentValue;
81   private final PopupPanel popup;
82   private final SuggestPickerImpl picker;
83   private final TextBoxBase box;
84   private DelegatingClickListenerCollection clickListeners;
85   private DelegatingChangeListenerCollection changeListeners;
86   private DelegatingFocusListenerCollection focusListeners;
87   private DelegatingKeyboardListenerCollection keyboardListeners;
88   private String JavaDoc separatorPadding = "";
89
90   private final Callback callBack = new Callback() {
91     public void onSuggestionsReady(Request request, Response response) {
92       showSuggestions(response.getSuggestions());
93     }
94   };
95
96   /**
97    * Constructor for {@link SuggestBox}. Creates a
98    * {@link MultiWordSuggestOracle} and {@link TextBox} to use with this
99    * {@link SuggestBox}.
100    */

101   public SuggestBox() {
102     this(new MultiWordSuggestOracle());
103   }
104
105   /**
106    * Constructor for {@link SuggestBox}. Creates a {@link TextBox} to use with
107    * this {@link SuggestBox}.
108    *
109    * @param oracle the oracle for this <code>SuggestBox</code>
110    */

111   public SuggestBox(SuggestOracle oracle) {
112     this(oracle, new TextBox());
113   }
114
115   /**
116    * Constructor for {@link SuggestBox}. The text box will be removed from it's
117    * current location and wrapped by the {@link SuggestBox}.
118    *
119    * @param oracle supplies suggestions based upon the current contents of the
120    * text widget
121    * @param box the text widget
122    */

123   public SuggestBox(SuggestOracle oracle, TextBoxBase box) {
124     this.box = box;
125     initWidget(box);
126     this.picker = new SuggestPickerImpl(oracle.isDisplayStringHTML());
127     this.popup = new ItemPickerDropDownImpl(this, picker);
128     addPopupChangeListener();
129     addKeyboardSupport();
130     setOracle(oracle);
131     setStyleName(STYLENAME_DEFAULT);
132   }
133
134   public final void addChangeListener(ChangeListener listener) {
135     if (changeListeners == null) {
136       changeListeners = new DelegatingChangeListenerCollection(this, box);
137     }
138     changeListeners.add(listener);
139   }
140
141   public final void addClickListener(ClickListener listener) {
142     if (clickListeners == null) {
143       clickListeners = new DelegatingClickListenerCollection(this, box);
144     }
145     clickListeners.add(listener);
146   }
147
148   public final void addFocusListener(FocusListener listener) {
149     if (focusListeners == null) {
150       focusListeners = new DelegatingFocusListenerCollection(this, box);
151     }
152     focusListeners.add(listener);
153   }
154
155   public final void addKeyboardListener(KeyboardListener listener) {
156     if (keyboardListeners == null) {
157       keyboardListeners = new DelegatingKeyboardListenerCollection(this, box);
158     }
159     keyboardListeners.add(listener);
160   }
161
162   /**
163    * Gets the limit for the number of suggestions that should be displayed for
164    * this box. It is up to the current {@link SuggestOracle} to enforce this
165    * limit.
166    *
167    * @return the limit for the number of suggestions
168    */

169   public final int getLimit() {
170     return limit;
171   }
172
173   /**
174    * Gets the suggest box's {@link com.google.gwt.user.client.ui.SuggestOracle}.
175    *
176    * @return the {@link SuggestOracle}
177    */

178   public final SuggestOracle getSuggestOracle() {
179     return oracle;
180   }
181
182   public final int getTabIndex() {
183     return box.getTabIndex();
184   }
185
186   public final String JavaDoc getText() {
187     return box.getText();
188   }
189
190   public final void removeChangeListener(ChangeListener listener) {
191     if (clickListeners != null) {
192       clickListeners.remove(listener);
193     }
194   }
195
196   public final void removeClickListener(ClickListener listener) {
197     if (clickListeners != null) {
198       clickListeners.remove(listener);
199     }
200   }
201
202   public final void removeFocusListener(FocusListener listener) {
203     if (focusListeners != null) {
204       focusListeners.remove(listener);
205     }
206   }
207
208   public final void removeKeyboardListener(KeyboardListener listener) {
209     if (keyboardListeners != null) {
210       keyboardListeners.remove(listener);
211     }
212   }
213
214   public final void setAccessKey(char key) {
215     box.setAccessKey(key);
216   }
217
218   public final void setFocus(boolean focused) {
219     box.setFocus(focused);
220   }
221
222   /**
223    * Sets the limit to the number of suggestions the oracle should provide. It
224    * is up to the oracle to enforce this limit.
225    *
226    * @param limit the limit to the number of suggestions provided
227    */

228   public final void setLimit(int limit) {
229     this.limit = limit;
230   }
231
232   public final void setTabIndex(int index) {
233     box.setTabIndex(index);
234   }
235
236   public final void setText(String JavaDoc text) {
237     box.setText(text);
238   }
239
240   /**
241    * Show the given collection of suggestions.
242    *
243    * @param suggestions suggestions to show
244    */

245   private void showSuggestions(Collection JavaDoc suggestions) {
246     if (suggestions.size() > 0) {
247       picker.setItems(suggestions);
248       popup.show();
249     } else {
250       popup.hide();
251     }
252   }
253
254   private void addKeyboardSupport() {
255     box.addKeyboardListener(new KeyboardListenerAdapter() {
256       private boolean pendingCancel;
257
258       public void onKeyDown(Widget sender, char keyCode, int modifiers) {
259         pendingCancel = picker.delegateKeyDown(keyCode);
260       }
261
262       public void onKeyPress(Widget sender, char keyCode, int modifiers) {
263         if (pendingCancel) {
264           // IE does not allow cancel key on key down, so we have delayed the
265
// cancellation of the key until the associated key press.
266
box.cancelKey();
267           pendingCancel = false;
268         } else if (popup.isAttached()) {
269           if (separators != null && isSeparator(keyCode)) {
270             // onKeyDown/onKeyUps's keyCode for ',' comes back '1/4', so unlike
271
// navigation, we use key press events to determine when the user
272
// wants to simulate clicking on the popup.
273
picker.commitSelection();
274
275             // The separator will be added after the popup is activated, so the
276
// popup will have already added a new separator. Therefore, the
277
// original separator should not be added as well.
278
box.cancelKey();
279           }
280         }
281       }
282
283       public void onKeyUp(Widget sender, char keyCode, int modifiers) {
284         // After every user key input, refresh the popup's suggestions.
285
refreshSuggestions();
286       }
287
288       /**
289        * In the presence of separators, returns the active search selection.
290        */

291       private String JavaDoc getActiveSelection(String JavaDoc text) {
292         selectEnd = box.getCursorPos();
293
294         // Find the last instance of a separator.
295
selectStart = -1;
296         for (int i = 0; i < separators.length; i++) {
297           selectStart = Math.max(
298               text.lastIndexOf(separators[i], selectEnd - 1), selectStart);
299         }
300         ++selectStart;
301
302         return text.substring(selectStart, selectEnd).trim();
303       }
304
305       private void refreshSuggestions() {
306         // Get the raw text.
307
String JavaDoc text = box.getText();
308         if (text.equals(currentValue)) {
309           return;
310         } else {
311           currentValue = text;
312         }
313
314         // Find selection to replace.
315
String JavaDoc selection;
316         if (separators == null) {
317           selection = text;
318         } else {
319           selection = getActiveSelection(text);
320         }
321         // If we have no text, let's not show the suggestions.
322
if (selection.length() == 0) {
323           popup.hide();
324         } else {
325           showSuggestions(selection);
326         }
327       }
328     });
329   }
330
331   /**
332    * Adds a standard popup listener to the suggest box's popup.
333    */

334   private void addPopupChangeListener() {
335     picker.addChangeListener(new ChangeListener() {
336       public void onChange(Widget sender) {
337         if (separators != null) {
338           onChangeWithSeparators();
339         } else {
340           currentValue = picker.getSelectedValue().toString();
341           box.setText(currentValue);
342         }
343         if (changeListeners != null) {
344           changeListeners.fireChange(SuggestBox.this);
345         }
346       }
347
348       private void onChangeWithSeparators() {
349         String JavaDoc newValue = (String JavaDoc) picker.getSelectedValue();
350
351         StringBuffer JavaDoc accum = new StringBuffer JavaDoc();
352         String JavaDoc text = box.getText();
353
354         // Add all text up to the selection start.
355
accum.append(text.substring(0, selectStart));
356
357         // Add one space if not at start.
358
if (selectStart > 0) {
359           accum.append(separatorPadding);
360         }
361         // Add the new value.
362
accum.append(newValue);
363
364         // Find correct cursor position.
365
int savedCursorPos = accum.length();
366
367         // Add all text after the selection end
368
String JavaDoc ender = text.substring(selectEnd).trim();
369         if (ender.length() == 0 || !isSeparator(ender.charAt(0))) {
370           // Add a separator if the first char of the ender is not already a
371
// separator.
372
accum.append(separators[0]).append(separatorPadding);
373           savedCursorPos = accum.length();
374         }
375         accum.append(ender);
376
377         // Set the text and cursor pos to correct location.
378
String JavaDoc replacement = accum.toString();
379         currentValue = replacement.trim();
380         box.setText(replacement);
381         box.setCursorPos(savedCursorPos);
382       }
383     });
384   }
385
386   /**
387    * Convenience method for identifying if a character is a separator.
388    */

389   private boolean isSeparator(char candidate) {
390     // An int map would be very handy right here...
391
for (int i = 0; i < separators.length; i++) {
392       if (candidate == separators[i]) {
393         return true;
394       }
395     }
396     return false;
397   }
398
399   /**
400    * Sets the suggestion oracle used to create suggestions.
401    *
402    * @param oracle the oracle
403    */

404   private void setOracle(SuggestOracle oracle) {
405     this.oracle = oracle;
406   }
407
408   private void showSuggestions(String JavaDoc query) {
409     oracle.requestSuggestions(new Request(query, limit), callBack);
410   }
411 }
412
Popular Tags