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.

VTwinColSelect.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  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.vaadin.client.ui;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.HashSet;
  20. import java.util.List;
  21. import java.util.Objects;
  22. import java.util.Set;
  23. import java.util.function.BiConsumer;
  24. import java.util.stream.Collectors;
  25. import com.google.gwt.dom.client.Style.Overflow;
  26. import com.google.gwt.dom.client.Style.Position;
  27. import com.google.gwt.event.dom.client.ClickEvent;
  28. import com.google.gwt.event.dom.client.ClickHandler;
  29. import com.google.gwt.event.dom.client.DoubleClickEvent;
  30. import com.google.gwt.event.dom.client.DoubleClickHandler;
  31. import com.google.gwt.event.dom.client.KeyCodes;
  32. import com.google.gwt.event.dom.client.KeyDownEvent;
  33. import com.google.gwt.event.dom.client.KeyDownHandler;
  34. import com.google.gwt.event.dom.client.MouseDownEvent;
  35. import com.google.gwt.event.dom.client.MouseDownHandler;
  36. import com.google.gwt.event.shared.HandlerRegistration;
  37. import com.google.gwt.user.client.ui.Composite;
  38. import com.google.gwt.user.client.ui.FlowPanel;
  39. import com.google.gwt.user.client.ui.HTML;
  40. import com.google.gwt.user.client.ui.HasEnabled;
  41. import com.google.gwt.user.client.ui.ListBox;
  42. import com.google.gwt.user.client.ui.Panel;
  43. import com.google.gwt.user.client.ui.Widget;
  44. import com.vaadin.client.Focusable;
  45. import com.vaadin.client.StyleConstants;
  46. import com.vaadin.client.WidgetUtil;
  47. import com.vaadin.client.connectors.AbstractMultiSelectConnector.MultiSelectWidget;
  48. import com.vaadin.shared.Registration;
  49. import elemental.json.JsonObject;
  50. /**
  51. * A list builder widget that has two selects; one for selectable options,
  52. * another for selected options, and buttons for selecting and deselecting the
  53. * items.
  54. *
  55. * @author Vaadin Ltd
  56. */
  57. public class VTwinColSelect extends Composite implements MultiSelectWidget,
  58. Field, ClickHandler, Focusable, HasEnabled, KeyDownHandler,
  59. MouseDownHandler, DoubleClickHandler, SubPartAware {
  60. private static final String SUBPART_OPTION_SELECT = "leftSelect";
  61. private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT
  62. + "-item";
  63. private static final String SUBPART_SELECTION_SELECT = "rightSelect";
  64. private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT
  65. + "-item";
  66. private static final String SUBPART_LEFT_CAPTION = "leftCaption";
  67. private static final String SUBPART_RIGHT_CAPTION = "rightCaption";
  68. private static final String SUBPART_ADD_BUTTON = "add";
  69. private static final String SUBPART_REMOVE_BUTTON = "remove";
  70. /** Primary style name for twin col select. */
  71. public static final String CLASSNAME = "v-select-twincol";
  72. private static final int VISIBLE_COUNT = 10;
  73. private static final int DEFAULT_COLUMN_COUNT = 10;
  74. private final DoubleClickListBox optionsListBox;
  75. private final DoubleClickListBox selectionsListBox;
  76. private final FlowPanel optionsContainer;
  77. private final FlowPanel captionWrapper;
  78. private final VButton addItemsLeftToRightButton;
  79. private final VButton removeItemsRightToLeftButton;
  80. private final FlowPanel buttons;
  81. private final Panel panel;
  82. private HTML optionsCaption = null;
  83. private HTML selectionsCaption = null;
  84. private List<BiConsumer<Set<String>, Set<String>>> selectionChangeListeners;
  85. private boolean enabled;
  86. private boolean readOnly;
  87. private int rows = 0;
  88. /**
  89. * A multiselect ListBox which catches double clicks.
  90. */
  91. public class DoubleClickListBox extends ListBox {
  92. /**
  93. * Constructs a new DoubleClickListBox.
  94. */
  95. public DoubleClickListBox() {
  96. setMultipleSelect(true);
  97. }
  98. @Override
  99. public HandlerRegistration addDoubleClickHandler(
  100. DoubleClickHandler handler) {
  101. return addDomHandler(handler, DoubleClickEvent.getType());
  102. }
  103. }
  104. /**
  105. * Constructs a new VTwinColSelect.
  106. */
  107. public VTwinColSelect() {
  108. selectionChangeListeners = new ArrayList<>();
  109. optionsContainer = new FlowPanel();
  110. initWidget(optionsContainer);
  111. optionsContainer.setStyleName(CLASSNAME);
  112. captionWrapper = new FlowPanel();
  113. optionsListBox = new DoubleClickListBox();
  114. optionsListBox.addClickHandler(this);
  115. optionsListBox.addDoubleClickHandler(this);
  116. optionsListBox.setVisibleItemCount(VISIBLE_COUNT);
  117. optionsListBox.setStyleName(CLASSNAME + "-options");
  118. selectionsListBox = new DoubleClickListBox();
  119. selectionsListBox.addClickHandler(this);
  120. selectionsListBox.addDoubleClickHandler(this);
  121. selectionsListBox.setVisibleItemCount(VISIBLE_COUNT);
  122. selectionsListBox.setStyleName(CLASSNAME + "-selections");
  123. buttons = new FlowPanel();
  124. buttons.setStyleName(CLASSNAME + "-buttons");
  125. addItemsLeftToRightButton = new VButton();
  126. addItemsLeftToRightButton.setText(">>");
  127. addItemsLeftToRightButton.addClickHandler(this);
  128. removeItemsRightToLeftButton = new VButton();
  129. removeItemsRightToLeftButton.setText("<<");
  130. removeItemsRightToLeftButton.addClickHandler(this);
  131. panel = optionsContainer;
  132. panel.add(captionWrapper);
  133. captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
  134. // Hide until there actually is a caption to prevent IE from rendering
  135. // extra empty space
  136. captionWrapper.setVisible(false);
  137. panel.add(optionsListBox);
  138. buttons.add(addItemsLeftToRightButton);
  139. final HTML br = new HTML("<span/>");
  140. br.setStyleName(CLASSNAME + "-deco");
  141. buttons.add(br);
  142. buttons.add(removeItemsRightToLeftButton);
  143. panel.add(buttons);
  144. panel.add(selectionsListBox);
  145. optionsListBox.addKeyDownHandler(this);
  146. optionsListBox.addMouseDownHandler(this);
  147. selectionsListBox.addMouseDownHandler(this);
  148. selectionsListBox.addKeyDownHandler(this);
  149. updateEnabledState();
  150. }
  151. /**
  152. * Gets the options caption HTML Widget.
  153. *
  154. * @return the options caption widget
  155. */
  156. protected HTML getOptionsCaption() {
  157. if (optionsCaption == null) {
  158. optionsCaption = new HTML();
  159. optionsCaption.setStyleName(CLASSNAME + "-caption-left");
  160. optionsCaption.getElement().getStyle()
  161. .setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
  162. captionWrapper.add(optionsCaption);
  163. }
  164. return optionsCaption;
  165. }
  166. /**
  167. * Gets the selections caption HTML widget.
  168. *
  169. * @return the selections caption widget
  170. */
  171. protected HTML getSelectionsCaption() {
  172. if (selectionsCaption == null) {
  173. selectionsCaption = new HTML();
  174. selectionsCaption.setStyleName(CLASSNAME + "-caption-right");
  175. selectionsCaption.getElement().getStyle()
  176. .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT);
  177. captionWrapper.add(selectionsCaption);
  178. }
  179. return selectionsCaption;
  180. }
  181. /**
  182. * For internal use only. May be removed or replaced in the future.
  183. *
  184. * @return the caption wrapper widget
  185. */
  186. public Widget getCaptionWrapper() {
  187. return captionWrapper;
  188. }
  189. /**
  190. * Sets the number of visible items for the list boxes.
  191. *
  192. * @param rows
  193. * the number of items to show
  194. * @see ListBox#setVisibleItemCount(int)
  195. */
  196. public void setRows(int rows) {
  197. if (this.rows != rows) {
  198. this.rows = rows;
  199. optionsListBox.setVisibleItemCount(rows);
  200. selectionsListBox.setVisibleItemCount(rows);
  201. }
  202. }
  203. /**
  204. * Returns the number of visible items for the list boxes.
  205. *
  206. * @return the number of items to show
  207. * @see ListBox#setVisibleItemCount(int)
  208. */
  209. public int getRows() {
  210. return rows;
  211. }
  212. /**
  213. * Updates the captions above the left (options) and right (selections)
  214. * columns. {code null} value clear the caption.
  215. *
  216. * @param leftCaption
  217. * the left caption to set, or {@code null} to clear
  218. * @param rightCaption
  219. * the right caption to set, or {@code null} to clear
  220. */
  221. public void updateCaptions(String leftCaption, String rightCaption) {
  222. boolean hasCaptions = leftCaption != null || rightCaption != null;
  223. if (leftCaption == null) {
  224. removeOptionsCaption();
  225. } else {
  226. getOptionsCaption().setText(leftCaption);
  227. }
  228. if (rightCaption == null) {
  229. removeSelectionsCaption();
  230. } else {
  231. getSelectionsCaption().setText(rightCaption);
  232. }
  233. captionWrapper.setVisible(hasCaptions);
  234. }
  235. private void removeOptionsCaption() {
  236. if (optionsCaption == null) {
  237. return;
  238. }
  239. if (optionsCaption.getParent() != null) {
  240. captionWrapper.remove(optionsCaption);
  241. }
  242. optionsCaption = null;
  243. }
  244. private void removeSelectionsCaption() {
  245. if (selectionsCaption == null) {
  246. return;
  247. }
  248. if (selectionsCaption.getParent() != null) {
  249. captionWrapper.remove(selectionsCaption);
  250. }
  251. selectionsCaption = null;
  252. }
  253. @Override
  254. public Registration addSelectionChangeListener(
  255. BiConsumer<Set<String>, Set<String>> listener) {
  256. Objects.nonNull(listener);
  257. selectionChangeListeners.add(listener);
  258. return (Registration) () -> selectionChangeListeners.remove(listener);
  259. }
  260. @Override
  261. public void setItems(List<JsonObject> items) {
  262. // filter selected items
  263. List<JsonObject> selection = items.stream()
  264. .filter(item -> MultiSelectWidget.isSelected(item))
  265. .collect(Collectors.toList());
  266. items.removeAll(selection);
  267. updateListBox(optionsListBox, items);
  268. updateListBox(selectionsListBox, selection);
  269. }
  270. private static void updateListBox(ListBox listBox,
  271. List<JsonObject> options) {
  272. for (int i = 0; i < options.size(); i++) {
  273. final JsonObject item = options.get(i);
  274. // reuse existing option if possible
  275. if (i < listBox.getItemCount()) {
  276. listBox.setItemText(i, MultiSelectWidget.getCaption(item));
  277. listBox.setValue(i, MultiSelectWidget.getKey(item));
  278. } else {
  279. listBox.addItem(MultiSelectWidget.getCaption(item),
  280. MultiSelectWidget.getKey(item));
  281. }
  282. }
  283. // remove extra
  284. for (int i = listBox.getItemCount() - 1; i >= options.size(); i--) {
  285. listBox.removeItem(i);
  286. }
  287. }
  288. private static boolean[] getSelectionBitmap(ListBox listBox) {
  289. final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
  290. for (int i = 0; i < listBox.getItemCount(); i++) {
  291. if (listBox.isItemSelected(i)) {
  292. selectedIndexes[i] = true;
  293. } else {
  294. selectedIndexes[i] = false;
  295. }
  296. }
  297. return selectedIndexes;
  298. }
  299. private void moveSelectedItemsLeftToRight() {
  300. Set<String> movedItems = moveSelectedItems(optionsListBox,
  301. selectionsListBox);
  302. selectionChangeListeners.forEach(listener -> listener.accept(movedItems,
  303. Collections.emptySet()));
  304. }
  305. private void moveSelectedItemsRightToLeft() {
  306. Set<String> movedItems = moveSelectedItems(selectionsListBox,
  307. optionsListBox);
  308. selectionChangeListeners.forEach(listener -> listener
  309. .accept(Collections.emptySet(), movedItems));
  310. }
  311. private static Set<String> moveSelectedItems(ListBox source,
  312. ListBox target) {
  313. final boolean[] sel = getSelectionBitmap(source);
  314. final Set<String> movedItems = new HashSet<>();
  315. for (int i = 0; i < sel.length; i++) {
  316. if (sel[i]) {
  317. final int optionIndex = i
  318. - (sel.length - source.getItemCount());
  319. movedItems.add(source.getValue(optionIndex));
  320. // Move selection to another column
  321. final String text = source.getItemText(optionIndex);
  322. final String value = source.getValue(optionIndex);
  323. target.addItem(text, value);
  324. target.setItemSelected(target.getItemCount() - 1, true);
  325. source.removeItem(optionIndex);
  326. }
  327. }
  328. // If no items are left move the focus to the selections
  329. if (source.getItemCount() == 0) {
  330. target.setFocus(true);
  331. } else {
  332. source.setFocus(true);
  333. }
  334. return movedItems;
  335. }
  336. @Override
  337. public void onClick(ClickEvent event) {
  338. if (event.getSource() == addItemsLeftToRightButton) {
  339. moveSelectedItemsLeftToRight();
  340. } else if (event.getSource() == removeItemsRightToLeftButton) {
  341. moveSelectedItemsRightToLeft();
  342. } else if (event.getSource() == optionsListBox) {
  343. // unselect all in other list, to avoid mistakes (i.e wrong button)
  344. final int count = selectionsListBox.getItemCount();
  345. for (int i = 0; i < count; i++) {
  346. selectionsListBox.setItemSelected(i, false);
  347. }
  348. } else if (event.getSource() == selectionsListBox) {
  349. // unselect all in other list, to avoid mistakes (i.e wrong button)
  350. final int count = optionsListBox.getItemCount();
  351. for (int i = 0; i < count; i++) {
  352. optionsListBox.setItemSelected(i, false);
  353. }
  354. }
  355. }
  356. /** For internal use only. May be removed or replaced in the future. */
  357. public void clearInternalHeights() {
  358. selectionsListBox.setHeight("");
  359. optionsListBox.setHeight("");
  360. }
  361. /** For internal use only. May be removed or replaced in the future. */
  362. public void setInternalHeights() {
  363. int captionHeight = WidgetUtil.getRequiredHeight(captionWrapper);
  364. int totalHeight = getOffsetHeight();
  365. String selectHeight = totalHeight - captionHeight + "px";
  366. selectionsListBox.setHeight(selectHeight);
  367. optionsListBox.setHeight(selectHeight);
  368. }
  369. /** For internal use only. May be removed or replaced in the future. */
  370. public void clearInternalWidths() {
  371. String colWidth = DEFAULT_COLUMN_COUNT + "em";
  372. String containerWidth = 2 * DEFAULT_COLUMN_COUNT + 4 + "em";
  373. // Caption wrapper width == optionsSelect + buttons +
  374. // selectionsSelect
  375. String captionWrapperWidth = 2 * DEFAULT_COLUMN_COUNT + 4 - 0.5 + "em";
  376. optionsListBox.setWidth(colWidth);
  377. if (optionsCaption != null) {
  378. optionsCaption.setWidth(colWidth);
  379. }
  380. selectionsListBox.setWidth(colWidth);
  381. if (selectionsCaption != null) {
  382. selectionsCaption.setWidth(colWidth);
  383. }
  384. buttons.setWidth("3.5em");
  385. optionsContainer.setWidth(containerWidth);
  386. captionWrapper.setWidth(captionWrapperWidth);
  387. }
  388. /** For internal use only. May be removed or replaced in the future. */
  389. public void setInternalWidths() {
  390. getElement().getStyle().setPosition(Position.RELATIVE);
  391. int bordersAndPaddings = WidgetUtil
  392. .measureHorizontalPaddingAndBorder(buttons.getElement(), 0);
  393. int buttonWidth = WidgetUtil.getRequiredWidth(buttons);
  394. int totalWidth = getOffsetWidth();
  395. int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings)
  396. / 2;
  397. optionsListBox.setWidth(spaceForSelect + "px");
  398. if (optionsCaption != null) {
  399. optionsCaption.setWidth(spaceForSelect + "px");
  400. }
  401. selectionsListBox.setWidth(spaceForSelect + "px");
  402. if (selectionsCaption != null) {
  403. selectionsCaption.setWidth(spaceForSelect + "px");
  404. }
  405. captionWrapper.setWidth("100%");
  406. }
  407. /**
  408. * Sets the tab index.
  409. *
  410. * @param tabIndex
  411. * the tab index to set
  412. */
  413. public void setTabIndex(int tabIndex) {
  414. optionsListBox.setTabIndex(tabIndex);
  415. selectionsListBox.setTabIndex(tabIndex);
  416. addItemsLeftToRightButton.setTabIndex(tabIndex);
  417. removeItemsRightToLeftButton.setTabIndex(tabIndex);
  418. }
  419. /**
  420. * Sets this twin column select as read only, meaning selection cannot be
  421. * changed.
  422. *
  423. * @param readOnly
  424. * {@code true} for read only, {@code false} for not read only
  425. */
  426. public void setReadOnly(boolean readOnly) {
  427. if (this.readOnly != readOnly) {
  428. this.readOnly = readOnly;
  429. updateEnabledState();
  430. }
  431. }
  432. /**
  433. * Returns {@code true} if this twin column select is in read only mode,
  434. * {@code false} if not.
  435. *
  436. * @return {@code true} for read only, {@code false} for not read only
  437. */
  438. public boolean isReadOnly() {
  439. return readOnly;
  440. }
  441. @Override
  442. public void setEnabled(boolean enabled) {
  443. if (this.enabled != enabled) {
  444. this.enabled = enabled;
  445. updateEnabledState();
  446. }
  447. }
  448. @Override
  449. public boolean isEnabled() {
  450. return enabled;
  451. }
  452. private void updateEnabledState() {
  453. boolean enabled = isEnabled() && !isReadOnly();
  454. optionsListBox.setEnabled(enabled);
  455. selectionsListBox.setEnabled(enabled);
  456. addItemsLeftToRightButton.setEnabled(enabled);
  457. removeItemsRightToLeftButton.setEnabled(enabled);
  458. addItemsLeftToRightButton.setStyleName(StyleConstants.DISABLED,
  459. !enabled);
  460. removeItemsRightToLeftButton.setStyleName(StyleConstants.DISABLED,
  461. !enabled);
  462. }
  463. @Override
  464. public void focus() {
  465. optionsListBox.setFocus(true);
  466. }
  467. /**
  468. * Get the key that selects an item in the table. By default it is the Enter
  469. * key but by overriding this you can change the key to whatever you want.
  470. *
  471. * @return the key that selects an item
  472. */
  473. protected int getNavigationSelectKey() {
  474. return KeyCodes.KEY_ENTER;
  475. }
  476. @Override
  477. public void onKeyDown(KeyDownEvent event) {
  478. int keycode = event.getNativeKeyCode();
  479. // Catch Ctrl-A and select all items since other browsers
  480. // than Chrome do not handle this natively
  481. if (event.isControlKeyDown() && (keycode == KeyCodes.KEY_A)) {
  482. for (int i = 0; i < optionsListBox.getItemCount(); i++) {
  483. optionsListBox.setItemSelected(i, true);
  484. }
  485. }
  486. // Catch tab and move between select:s
  487. if (keycode == KeyCodes.KEY_TAB
  488. && event.getSource() == optionsListBox) {
  489. // Prevent default behavior
  490. event.preventDefault();
  491. // Remove current selections
  492. for (int i = 0; i < optionsListBox.getItemCount(); i++) {
  493. optionsListBox.setItemSelected(i, false);
  494. }
  495. // Focus selections
  496. selectionsListBox.setFocus(true);
  497. }
  498. if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
  499. && event.getSource() == selectionsListBox) {
  500. // Prevent default behavior
  501. event.preventDefault();
  502. // Remove current selections
  503. for (int i = 0; i < selectionsListBox.getItemCount(); i++) {
  504. selectionsListBox.setItemSelected(i, false);
  505. }
  506. // Focus options
  507. optionsListBox.setFocus(true);
  508. }
  509. if (keycode == getNavigationSelectKey()) {
  510. // Prevent default behavior
  511. event.preventDefault();
  512. // Decide which select the selection was made in
  513. if (event.getSource() == optionsListBox) {
  514. // Prevents the selection to become a single selection when
  515. // using Enter key
  516. // as the selection key (default)
  517. optionsListBox.setFocus(false);
  518. moveSelectedItemsLeftToRight();
  519. } else if (event.getSource() == selectionsListBox) {
  520. // Prevents the selection to become a single selection when
  521. // using Enter key
  522. // as the selection key (default)
  523. selectionsListBox.setFocus(false);
  524. moveSelectedItemsRightToLeft();
  525. }
  526. }
  527. }
  528. @Override
  529. public void onMouseDown(MouseDownEvent event) {
  530. // Ensure that items are deselected when selecting
  531. // from a different source. See #3699 for details.
  532. if (event.getSource() == optionsListBox) {
  533. for (int i = 0; i < selectionsListBox.getItemCount(); i++) {
  534. selectionsListBox.setItemSelected(i, false);
  535. }
  536. } else if (event.getSource() == selectionsListBox) {
  537. for (int i = 0; i < optionsListBox.getItemCount(); i++) {
  538. optionsListBox.setItemSelected(i, false);
  539. }
  540. }
  541. }
  542. @Override
  543. public void onDoubleClick(DoubleClickEvent event) {
  544. if (event.getSource() == optionsListBox) {
  545. moveSelectedItemsLeftToRight();
  546. optionsListBox.setSelectedIndex(-1);
  547. optionsListBox.setFocus(false);
  548. } else if (event.getSource() == selectionsListBox) {
  549. moveSelectedItemsRightToLeft();
  550. selectionsListBox.setSelectedIndex(-1);
  551. selectionsListBox.setFocus(false);
  552. }
  553. }
  554. @Override
  555. public com.google.gwt.user.client.Element getSubPartElement(
  556. String subPart) {
  557. if (SUBPART_OPTION_SELECT.equals(subPart)) {
  558. return optionsListBox.getElement();
  559. } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) {
  560. String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length());
  561. return (com.google.gwt.user.client.Element) optionsListBox
  562. .getElement().getChild(Integer.parseInt(idx));
  563. } else if (SUBPART_SELECTION_SELECT.equals(subPart)) {
  564. return selectionsListBox.getElement();
  565. } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) {
  566. String idx = subPart
  567. .substring(SUBPART_SELECTION_SELECT_ITEM.length());
  568. return (com.google.gwt.user.client.Element) selectionsListBox
  569. .getElement().getChild(Integer.parseInt(idx));
  570. } else if (optionsCaption != null
  571. && SUBPART_LEFT_CAPTION.equals(subPart)) {
  572. return optionsCaption.getElement();
  573. } else if (selectionsCaption != null
  574. && SUBPART_RIGHT_CAPTION.equals(subPart)) {
  575. return selectionsCaption.getElement();
  576. } else if (SUBPART_ADD_BUTTON.equals(subPart)) {
  577. return addItemsLeftToRightButton.getElement();
  578. } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) {
  579. return removeItemsRightToLeftButton.getElement();
  580. }
  581. return null;
  582. }
  583. @Override
  584. public String getSubPartName(
  585. com.google.gwt.user.client.Element subElement) {
  586. if (optionsCaption != null
  587. && optionsCaption.getElement().isOrHasChild(subElement)) {
  588. return SUBPART_LEFT_CAPTION;
  589. } else if (selectionsCaption != null
  590. && selectionsCaption.getElement().isOrHasChild(subElement)) {
  591. return SUBPART_RIGHT_CAPTION;
  592. } else if (optionsListBox.getElement().isOrHasChild(subElement)) {
  593. if (optionsListBox.getElement() == subElement) {
  594. return SUBPART_OPTION_SELECT;
  595. } else {
  596. int idx = WidgetUtil.getChildElementIndex(subElement);
  597. return SUBPART_OPTION_SELECT_ITEM + idx;
  598. }
  599. } else if (selectionsListBox.getElement().isOrHasChild(subElement)) {
  600. if (selectionsListBox.getElement() == subElement) {
  601. return SUBPART_SELECTION_SELECT;
  602. } else {
  603. int idx = WidgetUtil.getChildElementIndex(subElement);
  604. return SUBPART_SELECTION_SELECT_ITEM + idx;
  605. }
  606. } else if (addItemsLeftToRightButton.getElement()
  607. .isOrHasChild(subElement)) {
  608. return SUBPART_ADD_BUTTON;
  609. } else if (removeItemsRightToLeftButton.getElement()
  610. .isOrHasChild(subElement)) {
  611. return SUBPART_REMOVE_BUTTON;
  612. }
  613. return null;
  614. }
  615. }