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 26KB

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