/*
 * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.control.cell;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;

// Package protected - not intended for external use
class CellUtils {

    static int TREE_VIEW_HBOX_GRAPHIC_PADDING = 3;

    
* Private fields * *
/*************************************************************************** * * * Private fields * * * **************************************************************************/
private final static StringConverter<?> defaultStringConverter = new StringConverter<Object>() { @Override public String toString(Object t) { return t == null ? null : t.toString(); } @Override public Object fromString(String string) { return (Object) string; } }; private final static StringConverter<?> defaultTreeItemStringConverter = new StringConverter<TreeItem<?>>() { @Override public String toString(TreeItem<?> treeItem) { return (treeItem == null || treeItem.getValue() == null) ? "" : treeItem.getValue().toString(); } @Override public TreeItem<?> fromString(String string) { return new TreeItem<>(string); } };
* General convenience * *
/*************************************************************************** * * * General convenience * * * **************************************************************************/
/* * Simple method to provide a StringConverter implementation in various cell * implementations. */ @SuppressWarnings("unchecked") static <T> StringConverter<T> defaultStringConverter() { return (StringConverter<T>) defaultStringConverter; } /* * Simple method to provide a TreeItem-specific StringConverter * implementation in various cell implementations. */ @SuppressWarnings("unchecked") static <T> StringConverter<TreeItem<T>> defaultTreeItemStringConverter() { return (StringConverter<TreeItem<T>>) defaultTreeItemStringConverter; } private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) { return converter == null ? cell.getItem() == null ? "" : cell.getItem().toString() : converter.toString(cell.getItem()); } static Node getGraphic(TreeItem<?> treeItem) { return treeItem == null ? null : treeItem.getGraphic(); }
* ChoiceBox convenience * *
/*************************************************************************** * * * ChoiceBox convenience * * * **************************************************************************/
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final ChoiceBox<T> choiceBox) { updateItem(cell, converter, null, null, choiceBox); } static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox, final Node graphic, final ChoiceBox<T> choiceBox) { if (cell.isEmpty()) { cell.setText(null); cell.setGraphic(null); } else { if (cell.isEditing()) { if (choiceBox != null) { choiceBox.getSelectionModel().select(cell.getItem()); } cell.setText(null); if (graphic != null) { hbox.getChildren().setAll(graphic, choiceBox); cell.setGraphic(hbox); } else { cell.setGraphic(choiceBox); } } else { cell.setText(getItemText(cell, converter)); cell.setGraphic(graphic); } } }; static <T> ChoiceBox<T> createChoiceBox( final Cell<T> cell, final ObservableList<T> items, final ObjectProperty<StringConverter<T>> converter) { ChoiceBox<T> choiceBox = new ChoiceBox<T>(items); choiceBox.setMaxWidth(Double.MAX_VALUE); choiceBox.converterProperty().bind(converter); choiceBox.showingProperty().addListener(o -> { if (!choiceBox.isShowing()) { cell.commitEdit(choiceBox.getSelectionModel().getSelectedItem()); } }); return choiceBox; }
* TextField convenience * *
/*************************************************************************** * * * TextField convenience * * * **************************************************************************/
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final TextField textField) { updateItem(cell, converter, null, null, textField); } static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox, final Node graphic, final TextField textField) { if (cell.isEmpty()) { cell.setText(null); cell.setGraphic(null); } else { if (cell.isEditing()) { if (textField != null) { textField.setText(getItemText(cell, converter)); } cell.setText(null); if (graphic != null) { hbox.getChildren().setAll(graphic, textField); cell.setGraphic(hbox); } else { cell.setGraphic(textField); } } else { cell.setText(getItemText(cell, converter)); cell.setGraphic(graphic); } } } static <T> void startEdit(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox, final Node graphic, final TextField textField) { if (textField != null) { textField.setText(getItemText(cell, converter)); } cell.setText(null); if (graphic != null) { hbox.getChildren().setAll(graphic, textField); cell.setGraphic(hbox); } else { cell.setGraphic(textField); } textField.selectAll(); // requesting focus so that key input can immediately go into the // TextField (see RT-28132) textField.requestFocus(); } static <T> void cancelEdit(Cell<T> cell, final StringConverter<T> converter, Node graphic) { cell.setText(getItemText(cell, converter)); cell.setGraphic(graphic); } static <T> TextField createTextField(final Cell<T> cell, final StringConverter<T> converter) { final TextField textField = new TextField(getItemText(cell, converter)); // Use onAction here rather than onKeyReleased (with check for Enter), // as otherwise we encounter RT-34685 textField.setOnAction(event -> { if (converter == null) { throw new IllegalStateException( "Attempting to convert text input into Object, but provided " + "StringConverter is null. Be sure to set a StringConverter " + "in your cell factory."); } cell.commitEdit(converter.fromString(textField.getText())); event.consume(); }); textField.setOnKeyReleased(t -> { if (t.getCode() == KeyCode.ESCAPE) { cell.cancelEdit(); t.consume(); } }); return textField; }
* ComboBox convenience * *
/*************************************************************************** * * * ComboBox convenience * * * **************************************************************************/
static <T> void updateItem(Cell<T> cell, StringConverter<T> converter, ComboBox<T> comboBox) { updateItem(cell, converter, null, null, comboBox); } static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox, final Node graphic, final ComboBox<T> comboBox) { if (cell.isEmpty()) { cell.setText(null); cell.setGraphic(null); } else { if (cell.isEditing()) { if (comboBox != null) { comboBox.getSelectionModel().select(cell.getItem()); } cell.setText(null); if (graphic != null) { hbox.getChildren().setAll(graphic, comboBox); cell.setGraphic(hbox); } else { cell.setGraphic(comboBox); } } else { cell.setText(getItemText(cell, converter)); cell.setGraphic(graphic); } } }; static <T> ComboBox<T> createComboBox(final Cell<T> cell, final ObservableList<T> items, final ObjectProperty<StringConverter<T>> converter) { ComboBox<T> comboBox = new ComboBox<T>(items); comboBox.converterProperty().bind(converter); comboBox.setMaxWidth(Double.MAX_VALUE); // setup listeners to properly commit any changes back into the data model. // First listener attempts to commit or cancel when the ENTER or ESC keys are released. // This is applicable in cases where the ComboBox is editable, and the user has // typed some input, and also when the ComboBox popup is showing. comboBox.addEventFilter(KeyEvent.KEY_RELEASED, e -> { if (e.getCode() == KeyCode.ENTER) { tryComboBoxCommit(comboBox, cell); } else if (e.getCode() == KeyCode.ESCAPE) { cell.cancelEdit(); } }); // Second listener attempts to commit when the user is in the editor of // the ComboBox, and moves focus away. comboBox.getEditor().focusedProperty().addListener(o -> { if (!comboBox.isFocused()) { tryComboBoxCommit(comboBox, cell); } }); // Third listener makes an assumption about the skin being used, and attempts to add // a listener to the ListView within it, such that when the user mouse clicks on a // on an item, that is immediately committed and the cell exits the editing mode. boolean success = listenToComboBoxSkin(comboBox, cell); if (!success) { comboBox.skinProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable observable) { boolean successInListener = listenToComboBoxSkin(comboBox, cell); if (successInListener) { comboBox.skinProperty().removeListener(this); } } }); } return comboBox; } private static <T> void tryComboBoxCommit(ComboBox<T> comboBox, Cell<T> cell) { StringConverter<T> sc = comboBox.getConverter(); if (comboBox.isEditable() && sc != null) { T value = sc.fromString(comboBox.getEditor().getText()); cell.commitEdit(value); } else { cell.commitEdit(comboBox.getValue()); } } private static <T> boolean listenToComboBoxSkin(final ComboBox<T> comboBox, final Cell<T> cell) { Skin<?> skin = comboBox.getSkin(); if (skin != null && skin instanceof ComboBoxListViewSkin) { ComboBoxListViewSkin cbSkin = (ComboBoxListViewSkin) skin; Node popupContent = cbSkin.getPopupContent(); if (popupContent != null && popupContent instanceof ListView) { popupContent.addEventHandler(MouseEvent.MOUSE_RELEASED, e -> cell.commitEdit(comboBox.getValue())); return true; } } return false; } }