You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IFilterSelect.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.Collection;
  7. import java.util.Date;
  8. import java.util.Iterator;
  9. import com.google.gwt.user.client.Command;
  10. import com.google.gwt.user.client.DOM;
  11. import com.google.gwt.user.client.Element;
  12. import com.google.gwt.user.client.Event;
  13. import com.google.gwt.user.client.Window;
  14. import com.google.gwt.user.client.ui.ClickListener;
  15. import com.google.gwt.user.client.ui.Composite;
  16. import com.google.gwt.user.client.ui.FlowPanel;
  17. import com.google.gwt.user.client.ui.FocusListener;
  18. import com.google.gwt.user.client.ui.HTML;
  19. import com.google.gwt.user.client.ui.Image;
  20. import com.google.gwt.user.client.ui.KeyboardListener;
  21. import com.google.gwt.user.client.ui.PopupListener;
  22. import com.google.gwt.user.client.ui.PopupPanel;
  23. import com.google.gwt.user.client.ui.TextBox;
  24. import com.google.gwt.user.client.ui.Widget;
  25. import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
  26. import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
  27. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  28. import com.itmill.toolkit.terminal.gwt.client.Focusable;
  29. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  30. import com.itmill.toolkit.terminal.gwt.client.ITooltip;
  31. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  32. import com.itmill.toolkit.terminal.gwt.client.Util;
  33. /**
  34. *
  35. * TODO needs major refactoring (to be extensible etc)
  36. */
  37. public class IFilterSelect extends Composite implements Paintable, Field,
  38. KeyboardListener, ClickListener, FocusListener, Focusable {
  39. public class FilterSelectSuggestion implements Suggestion, Command {
  40. private final String key;
  41. private final String caption;
  42. private String iconUri;
  43. public FilterSelectSuggestion(UIDL uidl) {
  44. key = uidl.getStringAttribute("key");
  45. caption = uidl.getStringAttribute("caption");
  46. if (uidl.hasAttribute("icon")) {
  47. iconUri = client.translateToolkitUri(uidl
  48. .getStringAttribute("icon"));
  49. }
  50. }
  51. public String getDisplayString() {
  52. final StringBuffer sb = new StringBuffer();
  53. if (iconUri != null) {
  54. sb.append("<img src=\"");
  55. sb.append(iconUri);
  56. sb.append("\" alt=\"icon\" class=\"i-icon\" />");
  57. }
  58. sb.append(Util.escapeHTML(caption));
  59. return sb.toString();
  60. }
  61. public String getReplacementString() {
  62. return caption;
  63. }
  64. public int getOptionKey() {
  65. return Integer.parseInt(key);
  66. }
  67. public String getIconUri() {
  68. return iconUri;
  69. }
  70. public void execute() {
  71. onSuggestionSelected(this);
  72. }
  73. }
  74. /**
  75. * @author mattitahvonen
  76. *
  77. */
  78. public class SuggestionPopup extends IToolkitOverlay implements
  79. PositionCallback, PopupListener {
  80. private static final int EXTRASPACE = 8;
  81. private static final String Z_INDEX = "30000";
  82. private final SuggestionMenu menu;
  83. private final Element up = DOM.createDiv();
  84. private final Element down = DOM.createDiv();
  85. private final Element status = DOM.createDiv();
  86. private boolean isPagingEnabled = true;
  87. private long lastAutoClosed;
  88. SuggestionPopup() {
  89. super(true, false, true);
  90. menu = new SuggestionMenu();
  91. setWidget(menu);
  92. setStyleName(CLASSNAME + "-suggestpopup");
  93. DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX);
  94. final Element root = getContainerElement();
  95. DOM.setInnerHTML(up, "<span>Prev</span>");
  96. DOM.sinkEvents(up, Event.ONCLICK);
  97. DOM.setInnerHTML(down, "<span>Next</span>");
  98. DOM.sinkEvents(down, Event.ONCLICK);
  99. DOM.insertChild(root, up, 0);
  100. DOM.appendChild(root, down);
  101. DOM.appendChild(root, status);
  102. DOM.setElementProperty(status, "className", CLASSNAME + "-status");
  103. addPopupListener(this);
  104. }
  105. public void showSuggestions(Collection currentSuggestions,
  106. int currentPage, int totalSuggestions) {
  107. if (ApplicationConnection.isTestingMode()) {
  108. // Add TT anchor point
  109. DOM.setElementProperty(getElement(), "id", paintableId
  110. + "_OPTIONLIST");
  111. }
  112. menu.setSuggestions(currentSuggestions);
  113. final int x = IFilterSelect.this.getAbsoluteLeft();
  114. int y = tb.getAbsoluteTop();
  115. y += tb.getOffsetHeight();
  116. setPopupPosition(x, y);
  117. final int first = currentPage * PAGELENTH
  118. + (nullSelectionAllowed && currentPage > 0 ? 0 : 1);
  119. final int last = first + currentSuggestions.size() - 1;
  120. final int matches = totalSuggestions
  121. - (nullSelectionAllowed ? 1 : 0);
  122. if (last > 0) {
  123. // nullsel not counted, as requested by user
  124. DOM.setInnerText(status, (matches == 0 ? 0 : first)
  125. + "-"
  126. + ("".equals(lastFilter) && nullSelectionAllowed
  127. && currentPage == 0 ? last - 1 : last) + "/"
  128. + matches);
  129. } else {
  130. DOM.setInnerText(status, "");
  131. }
  132. // We don't need to show arrows or statusbar if there is only one
  133. // page
  134. if (matches <= PAGELENTH) {
  135. setPagingEnabled(false);
  136. } else {
  137. setPagingEnabled(true);
  138. }
  139. setPrevButtonActive(first > 1);
  140. setNextButtonActive(last < matches);
  141. // clear previously fixed width
  142. menu.setWidth("");
  143. DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
  144. "width", "");
  145. setPopupPositionAndShow(this);
  146. }
  147. private void setNextButtonActive(boolean b) {
  148. if (b) {
  149. DOM.sinkEvents(down, Event.ONCLICK);
  150. DOM.setElementProperty(down, "className", CLASSNAME
  151. + "-nextpage");
  152. } else {
  153. DOM.sinkEvents(down, 0);
  154. DOM.setElementProperty(down, "className", CLASSNAME
  155. + "-nextpage-off");
  156. }
  157. }
  158. private void setPrevButtonActive(boolean b) {
  159. if (b) {
  160. DOM.sinkEvents(up, Event.ONCLICK);
  161. DOM
  162. .setElementProperty(up, "className", CLASSNAME
  163. + "-prevpage");
  164. } else {
  165. DOM.sinkEvents(up, 0);
  166. DOM.setElementProperty(up, "className", CLASSNAME
  167. + "-prevpage-off");
  168. }
  169. }
  170. public void selectNextItem() {
  171. final MenuItem cur = menu.getSelectedItem();
  172. final int index = 1 + menu.getItems().indexOf(cur);
  173. if (menu.getItems().size() > index) {
  174. final MenuItem newSelectedItem = (MenuItem) menu.getItems()
  175. .get(index);
  176. menu.selectItem(newSelectedItem);
  177. tb.setText(newSelectedItem.getText());
  178. tb.setSelectionRange(lastFilter.length(), newSelectedItem
  179. .getText().length()
  180. - lastFilter.length());
  181. } else if (hasNextPage()) {
  182. filterOptions(currentPage + 1, lastFilter);
  183. }
  184. }
  185. public void selectPrevItem() {
  186. final MenuItem cur = menu.getSelectedItem();
  187. final int index = -1 + menu.getItems().indexOf(cur);
  188. if (index > -1) {
  189. final MenuItem newSelectedItem = (MenuItem) menu.getItems()
  190. .get(index);
  191. menu.selectItem(newSelectedItem);
  192. tb.setText(newSelectedItem.getText());
  193. tb.setSelectionRange(lastFilter.length(), newSelectedItem
  194. .getText().length()
  195. - lastFilter.length());
  196. } else if (index == -1) {
  197. if (currentPage > 0) {
  198. filterOptions(currentPage - 1, lastFilter);
  199. }
  200. } else {
  201. final MenuItem newSelectedItem = (MenuItem) menu.getItems()
  202. .get(menu.getItems().size() - 1);
  203. menu.selectItem(newSelectedItem);
  204. tb.setText(newSelectedItem.getText());
  205. tb.setSelectionRange(lastFilter.length(), newSelectedItem
  206. .getText().length()
  207. - lastFilter.length());
  208. }
  209. }
  210. public void onBrowserEvent(Event event) {
  211. final Element target = DOM.eventGetTarget(event);
  212. if (DOM.compare(target, up)
  213. || DOM.compare(target, DOM.getChild(up, 0))) {
  214. filterOptions(currentPage - 1, lastFilter);
  215. } else if (DOM.compare(target, down)
  216. || DOM.compare(target, DOM.getChild(down, 0))) {
  217. filterOptions(currentPage + 1, lastFilter);
  218. }
  219. tb.setFocus(true);
  220. }
  221. public void setPagingEnabled(boolean paging) {
  222. if (isPagingEnabled == paging) {
  223. return;
  224. }
  225. if (paging) {
  226. DOM.setStyleAttribute(down, "display", "");
  227. DOM.setStyleAttribute(up, "display", "");
  228. DOM.setStyleAttribute(status, "display", "");
  229. } else {
  230. DOM.setStyleAttribute(down, "display", "none");
  231. DOM.setStyleAttribute(up, "display", "none");
  232. DOM.setStyleAttribute(status, "display", "none");
  233. }
  234. isPagingEnabled = paging;
  235. }
  236. /*
  237. * (non-Javadoc)
  238. *
  239. * @see com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition(int,
  240. * int)
  241. */
  242. public void setPosition(int offsetWidth, int offsetHeight) {
  243. int top = -1;
  244. int left = -1;
  245. // reset menu size and retrieve its "natural" size
  246. menu.setHeight("");
  247. if (currentPage > 0) {
  248. // fix height to avoid height change when getting to last page
  249. menu.fixHeightTo(PAGELENTH);
  250. }
  251. offsetHeight = getOffsetHeight();
  252. final int desiredWidth = IFilterSelect.this.getOffsetWidth();
  253. int naturalMenuWidth = DOM.getElementPropertyInt(DOM
  254. .getFirstChild(menu.getElement()), "offsetWidth");
  255. if (naturalMenuWidth < desiredWidth) {
  256. menu.setWidth(desiredWidth + "px");
  257. DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()),
  258. "width", "100%");
  259. naturalMenuWidth = desiredWidth;
  260. }
  261. if (Util.isIE()) {
  262. DOM.setStyleAttribute(getElement(), "width", naturalMenuWidth
  263. + "px");
  264. }
  265. if (offsetHeight + getPopupTop() > Window.getClientHeight()
  266. + Window.getScrollTop()) {
  267. // popup on top of input instead
  268. top = getPopupTop() - offsetHeight
  269. - IFilterSelect.this.getOffsetHeight();
  270. if (top < 0) {
  271. top = 0;
  272. }
  273. } else {
  274. top = getPopupTop();
  275. }
  276. // fetch real width (mac FF bugs here due GWT popups overflow:auto )
  277. offsetWidth = DOM.getElementPropertyInt(DOM.getFirstChild(menu
  278. .getElement()), "offsetWidth");
  279. if (offsetWidth + getPopupLeft() > Window.getClientWidth()
  280. + Window.getScrollLeft()) {
  281. left = IFilterSelect.this.getAbsoluteLeft()
  282. + IFilterSelect.this.getOffsetWidth()
  283. + Window.getScrollLeft() - offsetWidth;
  284. if (left < 0) {
  285. left = 0;
  286. }
  287. } else {
  288. left = getPopupLeft();
  289. }
  290. setPopupPosition(left, top);
  291. }
  292. /**
  293. * @return true if popup was just closed
  294. */
  295. public boolean isJustClosed() {
  296. final long now = (new Date()).getTime();
  297. return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
  298. }
  299. public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
  300. if (autoClosed) {
  301. lastAutoClosed = (new Date()).getTime();
  302. }
  303. }
  304. }
  305. public class SuggestionMenu extends MenuBar {
  306. SuggestionMenu() {
  307. super(true);
  308. setStyleName(CLASSNAME + "-suggestmenu");
  309. }
  310. /**
  311. * Fixes menus height to use same space as full page would use. Needed
  312. * to avoid height changes when quickly "scrolling" to last page
  313. */
  314. public void fixHeightTo(int pagelenth) {
  315. if (currentSuggestions.size() > 0) {
  316. final int pixels = pagelenth * (getOffsetHeight() - 2)
  317. / currentSuggestions.size();
  318. setHeight((pixels + 2) + "px");
  319. }
  320. }
  321. public void setSuggestions(Collection suggestions) {
  322. clearItems();
  323. final Iterator it = suggestions.iterator();
  324. while (it.hasNext()) {
  325. final FilterSelectSuggestion s = (FilterSelectSuggestion) it
  326. .next();
  327. final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
  328. this.addItem(mi);
  329. if (s == currentSuggestion) {
  330. selectItem(mi);
  331. }
  332. }
  333. }
  334. public void doSelectedItemAction() {
  335. final MenuItem item = getSelectedItem();
  336. final String enteredItemValue = tb.getText();
  337. // check for exact match in menu
  338. int p = getItems().size();
  339. if (p > 0) {
  340. for (int i = 0; i < p; i++) {
  341. final MenuItem potentialExactMatch = (MenuItem) getItems()
  342. .get(i);
  343. if (potentialExactMatch.getText().equals(enteredItemValue)) {
  344. selectItem(potentialExactMatch);
  345. doItemAction(potentialExactMatch, true);
  346. suggestionPopup.hide();
  347. return;
  348. }
  349. }
  350. }
  351. if (allowNewItem) {
  352. if (!enteredItemValue.equals(emptyText)) {
  353. client.updateVariable(paintableId, "newitem",
  354. enteredItemValue, immediate);
  355. }
  356. } else if (item != null
  357. && !"".equals(lastFilter)
  358. && item.getText().toLowerCase().startsWith(
  359. lastFilter.toLowerCase())) {
  360. doItemAction(item, true);
  361. } else {
  362. if (currentSuggestion != null) {
  363. String text = currentSuggestion.getReplacementString();
  364. tb.setText((text.equals("") ? emptyText : text));
  365. // TODO add/remove class CLASSNAME_EMPTY
  366. selectedOptionKey = currentSuggestion.key;
  367. } else {
  368. tb.setText(emptyText);
  369. // TODO add class CLASSNAME_EMPTY
  370. selectedOptionKey = null;
  371. }
  372. }
  373. suggestionPopup.hide();
  374. }
  375. }
  376. public static final int FILTERINGMODE_OFF = 0;
  377. public static final int FILTERINGMODE_STARTSWITH = 1;
  378. public static final int FILTERINGMODE_CONTAINS = 2;
  379. private static final String CLASSNAME = "i-filterselect";
  380. public static final int PAGELENTH = 10;
  381. private final FlowPanel panel = new FlowPanel();
  382. private final TextBox tb = new TextBox() {
  383. public void onBrowserEvent(Event event) {
  384. super.onBrowserEvent(event);
  385. if (client != null) {
  386. client.handleTooltipEvent(event, IFilterSelect.this);
  387. }
  388. }
  389. };
  390. private final SuggestionPopup suggestionPopup = new SuggestionPopup();
  391. private final HTML popupOpener = new HTML("");
  392. private final Image selectedItemIcon = new Image();
  393. private ApplicationConnection client;
  394. private String paintableId;
  395. private int currentPage;
  396. private final Collection currentSuggestions = new ArrayList();
  397. private boolean immediate;
  398. private String selectedOptionKey;
  399. private boolean filtering = false;
  400. private String lastFilter = "";
  401. private FilterSelectSuggestion currentSuggestion;
  402. private int totalMatches;
  403. private boolean allowNewItem;
  404. private boolean nullSelectionAllowed;
  405. private boolean enabled;
  406. // shown in unfocused empty field, disappears on focus (e.g "Search here")
  407. private String emptyText = "";
  408. private static final String CLASSNAME_EMPTY = "empty";
  409. private static final String ATTR_EMPTYTEXT = "emptytext";
  410. public IFilterSelect() {
  411. selectedItemIcon.setVisible(false);
  412. panel.add(selectedItemIcon);
  413. tb.sinkEvents(ITooltip.TOOLTIP_EVENTS);
  414. panel.add(tb);
  415. panel.add(popupOpener);
  416. initWidget(panel);
  417. setStyleName(CLASSNAME);
  418. tb.addKeyboardListener(this);
  419. tb.setStyleName(CLASSNAME + "-input");
  420. tb.addFocusListener(this);
  421. popupOpener.setStyleName(CLASSNAME + "-button");
  422. popupOpener.addClickListener(this);
  423. }
  424. public boolean hasNextPage() {
  425. if (totalMatches > (currentPage + 1) * PAGELENTH) {
  426. return true;
  427. } else {
  428. return false;
  429. }
  430. }
  431. public void filterOptions(int page) {
  432. filterOptions(page, tb.getText());
  433. }
  434. public void filterOptions(int page, String filter) {
  435. if (filter.equals(lastFilter) && currentPage == page) {
  436. if (!suggestionPopup.isAttached()) {
  437. suggestionPopup.showSuggestions(currentSuggestions,
  438. currentPage, totalMatches);
  439. }
  440. return;
  441. }
  442. if (!filter.equals(lastFilter)) {
  443. // we are on subsequent page and text has changed -> reset page
  444. if ("".equals(filter)) {
  445. // let server decide
  446. page = -1;
  447. } else {
  448. page = 0;
  449. }
  450. }
  451. filtering = true;
  452. client.updateVariable(paintableId, "filter", filter, false);
  453. client.updateVariable(paintableId, "page", page, true);
  454. lastFilter = filter;
  455. currentPage = page;
  456. }
  457. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  458. paintableId = uidl.getId();
  459. this.client = client;
  460. boolean readonly = uidl.hasAttribute("readonly");
  461. boolean disabled = uidl.hasAttribute("disabled");
  462. if (disabled || readonly) {
  463. tb.setEnabled(false);
  464. enabled = false;
  465. } else {
  466. tb.setEnabled(true);
  467. enabled = true;
  468. }
  469. if (client.updateComponent(this, uidl, true)) {
  470. return;
  471. }
  472. // not a FocusWidget -> needs own tabindex handling
  473. if (uidl.hasAttribute("tabindex")) {
  474. tb.setTabIndex(uidl.getIntAttribute("tabindex"));
  475. }
  476. immediate = uidl.hasAttribute("immediate");
  477. nullSelectionAllowed = uidl.hasAttribute("nullselect");
  478. currentPage = uidl.getIntVariable("page");
  479. if (uidl.hasAttribute(ATTR_EMPTYTEXT)) {
  480. // "emptytext" changed from server
  481. emptyText = uidl.getStringAttribute(ATTR_EMPTYTEXT);
  482. }
  483. suggestionPopup.setPagingEnabled(true);
  484. allowNewItem = uidl.hasAttribute("allownewitem");
  485. currentSuggestions.clear();
  486. final UIDL options = uidl.getChildUIDL(0);
  487. totalMatches = uidl.getIntAttribute("totalMatches");
  488. String captions = emptyText;
  489. for (final Iterator i = options.getChildIterator(); i.hasNext();) {
  490. final UIDL optionUidl = (UIDL) i.next();
  491. final FilterSelectSuggestion suggestion = new FilterSelectSuggestion(
  492. optionUidl);
  493. currentSuggestions.add(suggestion);
  494. if (optionUidl.hasAttribute("selected")) {
  495. if (!filtering) {
  496. tb.setText(suggestion.getReplacementString());
  497. selectedOptionKey = "" + suggestion.getOptionKey();
  498. }
  499. currentSuggestion = suggestion;
  500. }
  501. // Collect captions so we can calculate minimum width for textarea
  502. if (captions.length() > 0) {
  503. captions += "|";
  504. }
  505. captions += suggestion.getReplacementString();
  506. }
  507. if (!filtering && uidl.hasVariable("selected")
  508. && uidl.getStringArrayVariable("selected").length == 0) {
  509. // select nulled
  510. tb.setText(emptyText);
  511. selectedOptionKey = null;
  512. // TODO add class CLASSNAME_EMPTY
  513. }
  514. if (filtering
  515. && lastFilter.toLowerCase().equals(
  516. uidl.getStringVariable("filter"))) {
  517. suggestionPopup.showSuggestions(currentSuggestions, currentPage,
  518. totalMatches);
  519. filtering = false;
  520. }
  521. // Calculate minumum textarea width
  522. final int minw = minWidth(captions);
  523. final Element spacer = DOM.createDiv();
  524. DOM.setStyleAttribute(spacer, "width", minw + "px");
  525. DOM.setStyleAttribute(spacer, "height", "0");
  526. DOM.setStyleAttribute(spacer, "overflow", "hidden");
  527. DOM.appendChild(panel.getElement(), spacer);
  528. }
  529. public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
  530. currentSuggestion = suggestion;
  531. String newKey;
  532. if (suggestion.key.equals("")) {
  533. // "nullselection"
  534. newKey = "";
  535. } else {
  536. // normal selection
  537. newKey = String.valueOf(suggestion.getOptionKey());
  538. }
  539. String text = suggestion.getReplacementString();
  540. tb.setText(text.equals("") ? emptyText : text);
  541. // TODO add/remove class CLASSNAME_EMPTY
  542. setSelectedItemIcon(suggestion.getIconUri());
  543. if (!newKey.equals(selectedOptionKey)) {
  544. selectedOptionKey = newKey;
  545. client.updateVariable(paintableId, "selected",
  546. new String[] { selectedOptionKey }, immediate);
  547. // currentPage = -1; // forget the page
  548. }
  549. suggestionPopup.hide();
  550. }
  551. private void setSelectedItemIcon(String iconUri) {
  552. if (iconUri == null) {
  553. selectedItemIcon.setVisible(false);
  554. } else {
  555. selectedItemIcon.setUrl(iconUri);
  556. selectedItemIcon.setVisible(true);
  557. }
  558. }
  559. public void onKeyDown(Widget sender, char keyCode, int modifiers) {
  560. if (enabled && suggestionPopup.isAttached()) {
  561. switch (keyCode) {
  562. case KeyboardListener.KEY_DOWN:
  563. suggestionPopup.selectNextItem();
  564. DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
  565. break;
  566. case KeyboardListener.KEY_UP:
  567. suggestionPopup.selectPrevItem();
  568. DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
  569. break;
  570. case KeyboardListener.KEY_PAGEDOWN:
  571. if (hasNextPage()) {
  572. filterOptions(currentPage + 1, lastFilter);
  573. }
  574. break;
  575. case KeyboardListener.KEY_PAGEUP:
  576. if (currentPage > 0) {
  577. filterOptions(currentPage - 1, lastFilter);
  578. }
  579. break;
  580. case KeyboardListener.KEY_ENTER:
  581. case KeyboardListener.KEY_TAB:
  582. suggestionPopup.menu.doSelectedItemAction();
  583. break;
  584. }
  585. }
  586. }
  587. public void onKeyPress(Widget sender, char keyCode, int modifiers) {
  588. }
  589. public void onKeyUp(Widget sender, char keyCode, int modifiers) {
  590. if (enabled) {
  591. switch (keyCode) {
  592. case KeyboardListener.KEY_ENTER:
  593. case KeyboardListener.KEY_TAB:
  594. case KeyboardListener.KEY_SHIFT:
  595. case KeyboardListener.KEY_CTRL:
  596. case KeyboardListener.KEY_ALT:
  597. ; // NOP
  598. break;
  599. case KeyboardListener.KEY_DOWN:
  600. case KeyboardListener.KEY_UP:
  601. case KeyboardListener.KEY_PAGEDOWN:
  602. case KeyboardListener.KEY_PAGEUP:
  603. if (suggestionPopup.isAttached()) {
  604. break;
  605. } else {
  606. // open popup as from gadget
  607. filterOptions(-1, "");
  608. lastFilter = "";
  609. tb.selectAll();
  610. break;
  611. }
  612. case KeyboardListener.KEY_ESCAPE:
  613. if (currentSuggestion != null) {
  614. String text = currentSuggestion.getReplacementString();
  615. tb.setText((text.equals("") ? emptyText : text));
  616. // TODO add/remove class CLASSNAME_EMPTY
  617. selectedOptionKey = currentSuggestion.key;
  618. } else {
  619. tb.setText(emptyText);
  620. // TODO add class CLASSNAME_EMPTY
  621. selectedOptionKey = null;
  622. }
  623. lastFilter = "";
  624. suggestionPopup.hide();
  625. break;
  626. default:
  627. filterOptions(currentPage);
  628. break;
  629. }
  630. }
  631. }
  632. /**
  633. * Listener for popupopener
  634. */
  635. public void onClick(Widget sender) {
  636. if (enabled) {
  637. // ask suggestionPopup if it was just closed, we are using GWT
  638. // Popup's
  639. // auto close feature
  640. if (!suggestionPopup.isJustClosed()) {
  641. filterOptions(-1, "");
  642. lastFilter = "";
  643. }
  644. DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
  645. tb.setFocus(true);
  646. tb.selectAll();
  647. }
  648. }
  649. /*
  650. * Calculate minumum width for FilterSelect textarea
  651. */
  652. private native int minWidth(String captions)
  653. /*-{
  654. if(!captions || captions.length <= 0)
  655. return 0;
  656. captions = captions.split("|");
  657. var d = $wnd.document.createElement("div");
  658. var html = "";
  659. for(var i=0; i < captions.length; i++) {
  660. html += "<div>" + captions[i] + "</div>";
  661. // TODO apply same CSS classname as in suggestionmenu
  662. }
  663. d.style.position = "absolute";
  664. d.style.top = "0";
  665. d.style.left = "0";
  666. d.style.visibility = "hidden";
  667. d.innerHTML = html;
  668. $wnd.document.body.appendChild(d);
  669. var w = d.offsetWidth;
  670. $wnd.document.body.removeChild(d);
  671. return w;
  672. }-*/;
  673. public void onFocus(Widget sender) {
  674. if (emptyText.equals(tb.getText())) {
  675. tb.setText("");
  676. // TODO remove class CLASSNAME_EMPTY
  677. }
  678. }
  679. public void onLostFocus(Widget sender) {
  680. if (suggestionPopup.isJustClosed()) {
  681. suggestionPopup.menu.doSelectedItemAction();
  682. }
  683. if ("".equals(tb.getText())) {
  684. tb.setText(emptyText);
  685. // TODO add CLASSNAME_EMPTY
  686. }
  687. }
  688. public void focus() {
  689. tb.setFocus(true);
  690. }
  691. }