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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. /*
  2. * Copyright 2000-2022 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.v7.client.ui;
  17. import java.util.ArrayList;
  18. import java.util.HashSet;
  19. import java.util.List;
  20. import java.util.Set;
  21. import com.google.gwt.dom.client.Style.Overflow;
  22. import com.google.gwt.dom.client.Style.Position;
  23. import com.google.gwt.event.dom.client.ClickEvent;
  24. import com.google.gwt.event.dom.client.DoubleClickEvent;
  25. import com.google.gwt.event.dom.client.DoubleClickHandler;
  26. import com.google.gwt.event.dom.client.KeyCodes;
  27. import com.google.gwt.event.dom.client.KeyDownEvent;
  28. import com.google.gwt.event.dom.client.KeyDownHandler;
  29. import com.google.gwt.event.dom.client.MouseDownEvent;
  30. import com.google.gwt.event.dom.client.MouseDownHandler;
  31. import com.google.gwt.event.shared.HandlerRegistration;
  32. import com.google.gwt.user.client.ui.FlowPanel;
  33. import com.google.gwt.user.client.ui.HTML;
  34. import com.google.gwt.user.client.ui.ListBox;
  35. import com.google.gwt.user.client.ui.Panel;
  36. import com.vaadin.client.StyleConstants;
  37. import com.vaadin.client.UIDL;
  38. import com.vaadin.client.WidgetUtil;
  39. import com.vaadin.client.ui.SubPartAware;
  40. import com.vaadin.client.ui.VButton;
  41. import com.vaadin.v7.shared.ui.twincolselect.TwinColSelectConstants;
  42. public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler,
  43. MouseDownHandler, DoubleClickHandler, SubPartAware {
  44. public static final String CLASSNAME = "v-select-twincol";
  45. private static final int VISIBLE_COUNT = 10;
  46. private static final int DEFAULT_COLUMN_COUNT = 10;
  47. private final DoubleClickListBox options;
  48. private final DoubleClickListBox selections;
  49. /** For internal use only. May be removed or replaced in the future. */
  50. public FlowPanel captionWrapper;
  51. private HTML optionsCaption = null;
  52. private HTML selectionsCaption = null;
  53. private final VButton add;
  54. private final VButton remove;
  55. private final FlowPanel buttons;
  56. private final Panel panel;
  57. /**
  58. * A ListBox which catches double clicks.
  59. *
  60. */
  61. public class DoubleClickListBox extends ListBox {
  62. public DoubleClickListBox(boolean isMultipleSelect) {
  63. super(isMultipleSelect);
  64. }
  65. public DoubleClickListBox() {
  66. super();
  67. }
  68. @Override
  69. public HandlerRegistration addDoubleClickHandler(
  70. DoubleClickHandler handler) {
  71. return addDomHandler(handler, DoubleClickEvent.getType());
  72. }
  73. }
  74. public VTwinColSelect() {
  75. super(CLASSNAME);
  76. captionWrapper = new FlowPanel();
  77. options = new DoubleClickListBox();
  78. options.addClickHandler(this);
  79. options.addDoubleClickHandler(this);
  80. options.setVisibleItemCount(VISIBLE_COUNT);
  81. options.setStyleName(CLASSNAME + "-options");
  82. selections = new DoubleClickListBox();
  83. selections.addClickHandler(this);
  84. selections.addDoubleClickHandler(this);
  85. selections.setVisibleItemCount(VISIBLE_COUNT);
  86. selections.setStyleName(CLASSNAME + "-selections");
  87. buttons = new FlowPanel();
  88. buttons.setStyleName(CLASSNAME + "-buttons");
  89. add = new VButton();
  90. add.setText(">>");
  91. add.addClickHandler(this);
  92. remove = new VButton();
  93. remove.setText("<<");
  94. remove.addClickHandler(this);
  95. panel = ((Panel) optionsContainer);
  96. panel.add(captionWrapper);
  97. captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
  98. // Hide until there actually is a caption to prevent IE from rendering
  99. // extra empty space
  100. captionWrapper.setVisible(false);
  101. panel.add(options);
  102. buttons.add(add);
  103. final HTML br = new HTML("<span/>");
  104. br.setStyleName(CLASSNAME + "-deco");
  105. buttons.add(br);
  106. buttons.add(remove);
  107. panel.add(buttons);
  108. panel.add(selections);
  109. options.addKeyDownHandler(this);
  110. options.addMouseDownHandler(this);
  111. selections.addMouseDownHandler(this);
  112. selections.addKeyDownHandler(this);
  113. updateEnabledState();
  114. }
  115. public HTML getOptionsCaption() {
  116. if (optionsCaption == null) {
  117. optionsCaption = new HTML();
  118. optionsCaption.setStyleName(CLASSNAME + "-caption-left");
  119. optionsCaption.getElement().getStyle()
  120. .setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
  121. captionWrapper.add(optionsCaption);
  122. }
  123. return optionsCaption;
  124. }
  125. public HTML getSelectionsCaption() {
  126. if (selectionsCaption == null) {
  127. selectionsCaption = new HTML();
  128. selectionsCaption.setStyleName(CLASSNAME + "-caption-right");
  129. selectionsCaption.getElement().getStyle()
  130. .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT);
  131. captionWrapper.add(selectionsCaption);
  132. }
  133. return selectionsCaption;
  134. }
  135. /** For internal use only. May be removed or replaced in the future. */
  136. public void updateCaptions(UIDL uidl) {
  137. String leftCaption = (uidl
  138. .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION)
  139. ? uidl.getStringAttribute(
  140. TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION)
  141. : null);
  142. String rightCaption = (uidl
  143. .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION)
  144. ? uidl.getStringAttribute(
  145. TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION)
  146. : null);
  147. boolean hasCaptions = (leftCaption != null || rightCaption != null);
  148. if (leftCaption == null) {
  149. removeOptionsCaption();
  150. } else {
  151. getOptionsCaption().setText(leftCaption);
  152. }
  153. if (rightCaption == null) {
  154. removeSelectionsCaption();
  155. } else {
  156. getSelectionsCaption().setText(rightCaption);
  157. }
  158. captionWrapper.setVisible(hasCaptions);
  159. }
  160. private void removeOptionsCaption() {
  161. if (optionsCaption == null) {
  162. return;
  163. }
  164. if (optionsCaption.getParent() != null) {
  165. captionWrapper.remove(optionsCaption);
  166. }
  167. optionsCaption = null;
  168. }
  169. private void removeSelectionsCaption() {
  170. if (selectionsCaption == null) {
  171. return;
  172. }
  173. if (selectionsCaption.getParent() != null) {
  174. captionWrapper.remove(selectionsCaption);
  175. }
  176. selectionsCaption = null;
  177. }
  178. @Override
  179. public void buildOptions(UIDL uidl) {
  180. options.setMultipleSelect(isMultiselect());
  181. selections.setMultipleSelect(isMultiselect());
  182. options.clear();
  183. selections.clear();
  184. for (final Object child : uidl) {
  185. final UIDL optionUidl = (UIDL) child;
  186. if (optionUidl.hasAttribute("selected")) {
  187. selections.addItem(optionUidl.getStringAttribute("caption"),
  188. optionUidl.getStringAttribute("key"));
  189. } else {
  190. options.addItem(optionUidl.getStringAttribute("caption"),
  191. optionUidl.getStringAttribute("key"));
  192. }
  193. }
  194. if (getRows() > 0) {
  195. options.setVisibleItemCount(getRows());
  196. selections.setVisibleItemCount(getRows());
  197. }
  198. }
  199. @Override
  200. protected String[] getSelectedItems() {
  201. final List<String> selectedItemKeys = new ArrayList<String>();
  202. for (int i = 0; i < selections.getItemCount(); i++) {
  203. selectedItemKeys.add(selections.getValue(i));
  204. }
  205. return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
  206. }
  207. private boolean[] getSelectionBitmap(ListBox listBox) {
  208. final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
  209. for (int i = 0; i < listBox.getItemCount(); i++) {
  210. if (listBox.isItemSelected(i)) {
  211. selectedIndexes[i] = true;
  212. } else {
  213. selectedIndexes[i] = false;
  214. }
  215. }
  216. return selectedIndexes;
  217. }
  218. private void addItem() {
  219. Set<String> movedItems = moveSelectedItems(options, selections);
  220. selectedKeys.addAll(movedItems);
  221. client.updateVariable(paintableId, "selected",
  222. selectedKeys.toArray(new String[selectedKeys.size()]),
  223. isImmediate());
  224. }
  225. private void removeItem() {
  226. Set<String> movedItems = moveSelectedItems(selections, options);
  227. selectedKeys.removeAll(movedItems);
  228. client.updateVariable(paintableId, "selected",
  229. selectedKeys.toArray(new String[selectedKeys.size()]),
  230. isImmediate());
  231. }
  232. private Set<String> moveSelectedItems(ListBox source, ListBox target) {
  233. final boolean[] sel = getSelectionBitmap(source);
  234. final Set<String> movedItems = new HashSet<String>();
  235. int lastSelected = 0;
  236. for (int i = 0; i < sel.length; i++) {
  237. if (sel[i]) {
  238. final int optionIndex = i
  239. - (sel.length - source.getItemCount());
  240. movedItems.add(source.getValue(optionIndex));
  241. // Move selection to another column
  242. final String text = source.getItemText(optionIndex);
  243. final String value = source.getValue(optionIndex);
  244. target.addItem(text, value);
  245. target.setItemSelected(target.getItemCount() - 1, true);
  246. source.removeItem(optionIndex);
  247. if (source.getItemCount() > 0) {
  248. lastSelected = optionIndex > 0 ? optionIndex - 1 : 0;
  249. }
  250. }
  251. }
  252. if (source.getItemCount() > 0) {
  253. source.setSelectedIndex(lastSelected);
  254. }
  255. // If no items are left move the focus to the selections
  256. if (source.getItemCount() == 0) {
  257. target.setFocus(true);
  258. } else {
  259. source.setFocus(true);
  260. }
  261. return movedItems;
  262. }
  263. @Override
  264. public void onClick(ClickEvent event) {
  265. super.onClick(event);
  266. if (event.getSource() == add) {
  267. addItem();
  268. } else if (event.getSource() == remove) {
  269. removeItem();
  270. } else if (event.getSource() == options) {
  271. // unselect all in other list, to avoid mistakes (i.e wrong button)
  272. final int c = selections.getItemCount();
  273. for (int i = 0; i < c; i++) {
  274. selections.setItemSelected(i, false);
  275. }
  276. } else if (event.getSource() == selections) {
  277. // unselect all in other list, to avoid mistakes (i.e wrong button)
  278. final int c = options.getItemCount();
  279. for (int i = 0; i < c; i++) {
  280. options.setItemSelected(i, false);
  281. }
  282. }
  283. }
  284. /** For internal use only. May be removed or replaced in the future. */
  285. public void clearInternalHeights() {
  286. selections.setHeight("");
  287. options.setHeight("");
  288. }
  289. /** For internal use only. May be removed or replaced in the future. */
  290. public void setInternalHeights() {
  291. int captionHeight = WidgetUtil.getRequiredHeight(captionWrapper);
  292. int totalHeight = getOffsetHeight();
  293. String selectHeight = (totalHeight - captionHeight) + "px";
  294. selections.setHeight(selectHeight);
  295. options.setHeight(selectHeight);
  296. }
  297. /** For internal use only. May be removed or replaced in the future. */
  298. public void clearInternalWidths() {
  299. int cols = -1;
  300. if (getColumns() > 0) {
  301. cols = getColumns();
  302. } else {
  303. cols = DEFAULT_COLUMN_COUNT;
  304. }
  305. if (cols >= 0) {
  306. String colWidth = cols + "em";
  307. String containerWidth = (2 * cols + 4) + "em";
  308. // Caption wrapper width == optionsSelect + buttons +
  309. // selectionsSelect
  310. String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em";
  311. options.setWidth(colWidth);
  312. if (optionsCaption != null) {
  313. optionsCaption.setWidth(colWidth);
  314. }
  315. selections.setWidth(colWidth);
  316. if (selectionsCaption != null) {
  317. selectionsCaption.setWidth(colWidth);
  318. }
  319. buttons.setWidth("3.5em");
  320. optionsContainer.setWidth(containerWidth);
  321. captionWrapper.setWidth(captionWrapperWidth);
  322. }
  323. }
  324. /** For internal use only. May be removed or replaced in the future. */
  325. public void setInternalWidths() {
  326. getElement().getStyle().setPosition(Position.RELATIVE);
  327. int bordersAndPaddings = WidgetUtil
  328. .measureHorizontalPaddingAndBorder(buttons.getElement(), 0);
  329. int buttonWidth = WidgetUtil.getRequiredWidth(buttons);
  330. int totalWidth = getOffsetWidth();
  331. int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings)
  332. / 2;
  333. options.setWidth(spaceForSelect + "px");
  334. if (optionsCaption != null) {
  335. optionsCaption.setWidth(spaceForSelect + "px");
  336. }
  337. selections.setWidth(spaceForSelect + "px");
  338. if (selectionsCaption != null) {
  339. selectionsCaption.setWidth(spaceForSelect + "px");
  340. }
  341. captionWrapper.setWidth("100%");
  342. }
  343. @Override
  344. public void setTabIndex(int tabIndex) {
  345. options.setTabIndex(tabIndex);
  346. selections.setTabIndex(tabIndex);
  347. add.setTabIndex(tabIndex);
  348. remove.setTabIndex(tabIndex);
  349. }
  350. @Override
  351. public void updateEnabledState() {
  352. boolean enabled = isEnabled() && !isReadonly();
  353. options.setEnabled(enabled);
  354. selections.setEnabled(enabled);
  355. add.setEnabled(enabled);
  356. remove.setEnabled(enabled);
  357. add.setStyleName(StyleConstants.DISABLED, !enabled);
  358. remove.setStyleName(StyleConstants.DISABLED, !enabled);
  359. }
  360. @Override
  361. public void focus() {
  362. options.setFocus(true);
  363. }
  364. /**
  365. * Get the key that selects an item in the table. By default it is the Enter
  366. * key but by overriding this you can change the key to whatever you want.
  367. *
  368. * @return
  369. */
  370. protected int getNavigationSelectKey() {
  371. return KeyCodes.KEY_ENTER;
  372. }
  373. /*
  374. * (non-Javadoc)
  375. *
  376. * @see
  377. * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
  378. * .event.dom.client.KeyDownEvent)
  379. */
  380. @Override
  381. public void onKeyDown(KeyDownEvent event) {
  382. int keycode = event.getNativeKeyCode();
  383. // Catch tab and move between select:s
  384. if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) {
  385. // Prevent default behavior
  386. event.preventDefault();
  387. // Remove current selections
  388. for (int i = 0; i < options.getItemCount(); i++) {
  389. options.setItemSelected(i, false);
  390. }
  391. // Focus selections
  392. selections.setFocus(true);
  393. }
  394. if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
  395. && event.getSource() == selections) {
  396. // Prevent default behavior
  397. event.preventDefault();
  398. // Remove current selections
  399. for (int i = 0; i < selections.getItemCount(); i++) {
  400. selections.setItemSelected(i, false);
  401. }
  402. // Focus options
  403. options.setFocus(true);
  404. }
  405. if (keycode == getNavigationSelectKey()) {
  406. // Prevent default behavior
  407. event.preventDefault();
  408. // Decide which select the selection was made in
  409. if (event.getSource() == options) {
  410. // Prevents the selection to become a single selection when
  411. // using Enter key
  412. // as the selection key (default)
  413. options.setFocus(false);
  414. addItem();
  415. } else if (event.getSource() == selections) {
  416. // Prevents the selection to become a single selection when
  417. // using Enter key
  418. // as the selection key (default)
  419. selections.setFocus(false);
  420. removeItem();
  421. }
  422. }
  423. }
  424. /*
  425. * (non-Javadoc)
  426. *
  427. * @see
  428. * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
  429. * .gwt.event.dom.client.MouseDownEvent)
  430. */
  431. @Override
  432. public void onMouseDown(MouseDownEvent event) {
  433. // Ensure that items are deselected when selecting
  434. // from a different source. See #3699 for details.
  435. if (event.getSource() == options) {
  436. for (int i = 0; i < selections.getItemCount(); i++) {
  437. selections.setItemSelected(i, false);
  438. }
  439. } else if (event.getSource() == selections) {
  440. for (int i = 0; i < options.getItemCount(); i++) {
  441. options.setItemSelected(i, false);
  442. }
  443. }
  444. }
  445. /*
  446. * (non-Javadoc)
  447. *
  448. * @see
  449. * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.
  450. * google.gwt.event.dom.client.DoubleClickEvent)
  451. */
  452. @Override
  453. public void onDoubleClick(DoubleClickEvent event) {
  454. if (event.getSource() == options) {
  455. addItem();
  456. options.setSelectedIndex(-1);
  457. options.setFocus(false);
  458. } else if (event.getSource() == selections) {
  459. removeItem();
  460. selections.setSelectedIndex(-1);
  461. selections.setFocus(false);
  462. }
  463. }
  464. private static final String SUBPART_OPTION_SELECT = "leftSelect";
  465. private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT
  466. + "-item";
  467. private static final String SUBPART_SELECTION_SELECT = "rightSelect";
  468. private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT
  469. + "-item";
  470. private static final String SUBPART_LEFT_CAPTION = "leftCaption";
  471. private static final String SUBPART_RIGHT_CAPTION = "rightCaption";
  472. private static final String SUBPART_ADD_BUTTON = "add";
  473. private static final String SUBPART_REMOVE_BUTTON = "remove";
  474. @Override
  475. public com.google.gwt.user.client.Element getSubPartElement(
  476. String subPart) {
  477. if (SUBPART_OPTION_SELECT.equals(subPart)) {
  478. return options.getElement();
  479. } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) {
  480. String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length());
  481. return (com.google.gwt.user.client.Element) options.getElement()
  482. .getChild(Integer.parseInt(idx));
  483. } else if (SUBPART_SELECTION_SELECT.equals(subPart)) {
  484. return selections.getElement();
  485. } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) {
  486. String idx = subPart
  487. .substring(SUBPART_SELECTION_SELECT_ITEM.length());
  488. return (com.google.gwt.user.client.Element) selections.getElement()
  489. .getChild(Integer.parseInt(idx));
  490. } else if (optionsCaption != null
  491. && SUBPART_LEFT_CAPTION.equals(subPart)) {
  492. return optionsCaption.getElement();
  493. } else if (selectionsCaption != null
  494. && SUBPART_RIGHT_CAPTION.equals(subPart)) {
  495. return selectionsCaption.getElement();
  496. } else if (SUBPART_ADD_BUTTON.equals(subPart)) {
  497. return add.getElement();
  498. } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) {
  499. return remove.getElement();
  500. }
  501. return null;
  502. }
  503. @Override
  504. public String getSubPartName(
  505. com.google.gwt.user.client.Element subElement) {
  506. if (optionsCaption != null
  507. && optionsCaption.getElement().isOrHasChild(subElement)) {
  508. return SUBPART_LEFT_CAPTION;
  509. } else if (selectionsCaption != null
  510. && selectionsCaption.getElement().isOrHasChild(subElement)) {
  511. return SUBPART_RIGHT_CAPTION;
  512. } else if (options.getElement().isOrHasChild(subElement)) {
  513. if (options.getElement() == subElement) {
  514. return SUBPART_OPTION_SELECT;
  515. } else {
  516. int idx = WidgetUtil.getChildElementIndex(subElement);
  517. return SUBPART_OPTION_SELECT_ITEM + idx;
  518. }
  519. } else if (selections.getElement().isOrHasChild(subElement)) {
  520. if (selections.getElement() == subElement) {
  521. return SUBPART_SELECTION_SELECT;
  522. } else {
  523. int idx = WidgetUtil.getChildElementIndex(subElement);
  524. return SUBPART_SELECTION_SELECT_ITEM + idx;
  525. }
  526. } else if (add.getElement().isOrHasChild(subElement)) {
  527. return SUBPART_ADD_BUTTON;
  528. } else if (remove.getElement().isOrHasChild(subElement)) {
  529. return SUBPART_REMOVE_BUTTON;
  530. }
  531. return null;
  532. }
  533. }