modules/controls/src/main/java/javafx/scene/control/skin/ChoiceBoxSkin.java
Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
@@ -21,13 +21,20 @@
* 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 com.sun.javafx.scene.control.skin;
+package javafx.scene.control.skin;
+import com.sun.javafx.scene.control.ContextMenuContent;
+import com.sun.javafx.scene.control.behavior.BehaviorBase;
import javafx.beans.WeakInvalidationListener;
+import javafx.scene.Node;
+import javafx.scene.control.Accordion;
+import javafx.scene.control.Button;
+import javafx.scene.control.Control;
+import javafx.scene.control.SkinBase;
import javafx.util.StringConverter;
import javafx.beans.InvalidationListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.HPos;
@@ -48,28 +55,22 @@
import com.sun.javafx.scene.control.behavior.ChoiceBoxBehavior;
import javafx.collections.WeakListChangeListener;
/**
- * ChoiceBoxSkin - default implementation
+ * Default skin implementation for the {@link ChoiceBox} control.
+ *
+ * @see ChoiceBox
+ * @since 9
*/
-public class ChoiceBoxSkin<T> extends BehaviorSkinBase<ChoiceBox<T>, ChoiceBoxBehavior<T>> {
+public class ChoiceBoxSkin<T> extends SkinBase<ChoiceBox<T>> {
- public ChoiceBoxSkin(ChoiceBox<T> control) {
- super(control, new ChoiceBoxBehavior<T>(control));
- initialize();
-
- itemsObserver = observable -> updateChoiceBoxItems();
- control.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
-
- control.requestLayout();
- registerChangeListener(control.selectionModelProperty(), "SELECTION_MODEL");
- registerChangeListener(control.showingProperty(), "SHOWING");
- registerChangeListener(control.itemsProperty(), "ITEMS");
- registerChangeListener(control.getSelectionModel().selectedItemProperty(), "SELECTION_CHANGED");
- registerChangeListener(control.converterProperty(), "CONVERTER");
- }
+ /***************************************************************************
+ * *
+ * Private fields *
+ * *
+ **************************************************************************/
private ObservableList<T> choiceBoxItems;
private ContextMenu popup;
@@ -84,10 +85,20 @@
*/
private SelectionModel<T> selectionModel;
private Label label;
+ private final BehaviorBase<ChoiceBox<T>> behavior;
+
+
+
+ /***************************************************************************
+ * *
+ * Listeners *
+ * *
+ **************************************************************************/
+
private final ListChangeListener<T> choiceBoxItemsListener = new ListChangeListener<T>() {
@Override public void onChanged(Change<? extends T> c) {
while (c.next()) {
if (c.getRemovedSize() > 0 || c.wasPermutated()) {
toggleGroup.getToggles().clear();
@@ -112,10 +123,186 @@
private final WeakListChangeListener<T> weakChoiceBoxItemsListener =
new WeakListChangeListener<T>(choiceBoxItemsListener);
private final InvalidationListener itemsObserver;
+
+
+ /***************************************************************************
+ * *
+ * Constructors *
+ * *
+ **************************************************************************/
+
+ /**
+ * Creates a new ChoiceBoxSkin instance, installing the necessary child
+ * nodes into the Control {@link Control#getChildren() children} list, as
+ * well as the necessary input mappings for handling key, mouse, etc events.
+ *
+ * @param control The control that this skin should be installed onto.
+ */
+ public ChoiceBoxSkin(ChoiceBox<T> control) {
+ super(control);
+
+ // install default input map for the ChoiceBox control
+ behavior = new ChoiceBoxBehavior<>(control);
+// control.setInputMap(behavior.getInputMap());
+
+ initialize();
+
+ itemsObserver = observable -> updateChoiceBoxItems();
+ control.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
+
+ control.requestLayout();
+ registerChangeListener(control.selectionModelProperty(), e -> updateSelectionModel());
+ registerChangeListener(control.showingProperty(), e -> {
+ if (getSkinnable().isShowing()) {
+ MenuItem item = null;
+
+ SelectionModel sm = getSkinnable().getSelectionModel();
+ if (sm == null) return;
+
+ long currentSelectedIndex = sm.getSelectedIndex();
+ int itemInControlCount = choiceBoxItems.size();
+ boolean hasSelection = currentSelectedIndex >= 0 && currentSelectedIndex < itemInControlCount;
+ if (hasSelection) {
+ item = popup.getItems().get((int) currentSelectedIndex);
+ if (item != null && item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
+ } else {
+ if (itemInControlCount > 0) item = popup.getItems().get(0);
+ }
+
+ // This is a fix for RT-9071. Ideally this won't be necessary in
+ // the long-run, but for now at least this resolves the
+ // positioning
+ // problem of ChoiceBox inside a Cell.
+ getSkinnable().autosize();
+ // -- End of RT-9071 fix
+
+ double y = 0;
+
+ if (popup.getSkin() != null) {
+ ContextMenuContent cmContent = (ContextMenuContent)popup.getSkin().getNode();
+ if (cmContent != null && currentSelectedIndex != -1) {
+ y = -(cmContent.getMenuYOffset((int)currentSelectedIndex));
+ }
+ }
+
+ popup.show(getSkinnable(), Side.BOTTOM, 2, y);
+ } else {
+ popup.hide();
+ }
+ });
+ registerChangeListener(control.itemsProperty(), e -> {
+ updateChoiceBoxItems();
+ updatePopupItems();
+ updateSelectionModel();
+ updateSelection();
+ if(selectionModel != null && selectionModel.getSelectedIndex() == -1) {
+ label.setText(""); // clear label text when selectedIndex is -1
+ }
+ });
+ registerChangeListener(control.getSelectionModel().selectedItemProperty(), e -> {
+ if (getSkinnable().getSelectionModel() != null) {
+ int index = getSkinnable().getSelectionModel().getSelectedIndex();
+ if (index != -1) {
+ MenuItem item = popup.getItems().get(index);
+ if (item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
+ }
+ }
+ });
+ registerChangeListener(control.converterProperty(), e -> {
+ updateChoiceBoxItems();
+ updatePopupItems();
+ });
+ }
+
+
+
+ /***************************************************************************
+ * *
+ * Public API *
+ * *
+ **************************************************************************/
+
+ /** {@inheritDoc} */
+ @Override public void dispose() {
+ super.dispose();
+
+ if (behavior != null) {
+ behavior.dispose();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void layoutChildren(final double x, final double y,
+ final double w, final double h) {
+ // open button width/height
+ double obw = openButton.prefWidth(-1);
+
+ ChoiceBox<T> control = getSkinnable();
+ label.resizeRelocate(x, y, w, h);
+ openButton.resize(obw, openButton.prefHeight(-1));
+ positionInArea(openButton, (x+w) - obw,
+ y, obw, h, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+ final double boxWidth = label.minWidth(-1) + openButton.minWidth(-1);
+ final double popupWidth = popup.minWidth(-1);
+ return leftInset + Math.max(boxWidth, popupWidth) + rightInset;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+ final double displayHeight = label.minHeight(-1);
+ final double openButtonHeight = openButton.minHeight(-1);
+ return topInset + Math.max(displayHeight, openButtonHeight) + bottomInset;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+ final double boxWidth = label.prefWidth(-1)
+ + openButton.prefWidth(-1);
+ double popupWidth = popup.prefWidth(-1);
+ if (popupWidth <= 0) { // first time: when the popup has not shown yet
+ if (popup.getItems().size() > 0){
+ popupWidth = (new Text(((MenuItem)popup.getItems().get(0)).getText())).prefWidth(-1);
+ }
+ }
+ return (popup.getItems().size() == 0) ? 50 : leftInset + Math.max(boxWidth, popupWidth)
+ + rightInset;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+ final double displayHeight = label.prefHeight(-1);
+ final double openButtonHeight = openButton.prefHeight(-1);
+ return topInset
+ + Math.max(displayHeight, openButtonHeight)
+ + bottomInset;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+ return getSkinnable().prefHeight(width);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+ return getSkinnable().prefWidth(height);
+ }
+
+
+
+ /***************************************************************************
+ * *
+ * Private implementation *
+ * *
+ **************************************************************************/
+
private void initialize() {
updateChoiceBoxItems();
label = new Label();
label.setMnemonicParsing(false); // ChoiceBox doesn't do Mnemonics
@@ -176,74 +363,10 @@
// Test only purpose
String getChoiceBoxSelectedText() {
return label.getText();
}
- @SuppressWarnings("rawtypes")
- @Override protected void handleControlPropertyChanged(String p) {
- super.handleControlPropertyChanged(p);
- if ("ITEMS".equals(p)) {
- updateChoiceBoxItems();
- updatePopupItems();
- updateSelectionModel();
- updateSelection();
- if(selectionModel != null && selectionModel.getSelectedIndex() == -1) {
- label.setText(""); // clear label text when selectedIndex is -1
- }
- } else if (("SELECTION_MODEL").equals(p)) {
- updateSelectionModel();
- } else if ("SELECTION_CHANGED".equals(p)) {
- if (getSkinnable().getSelectionModel() != null) {
- int index = getSkinnable().getSelectionModel().getSelectedIndex();
- if (index != -1) {
- MenuItem item = popup.getItems().get(index);
- if (item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
- }
- }
- } else if ("SHOWING".equals(p)) {
- if (getSkinnable().isShowing()) {
- MenuItem item = null;
-
- SelectionModel sm = getSkinnable().getSelectionModel();
- if (sm == null) return;
-
- long currentSelectedIndex = sm.getSelectedIndex();
- int itemInControlCount = choiceBoxItems.size();
- boolean hasSelection = currentSelectedIndex >= 0 && currentSelectedIndex < itemInControlCount;
- if (hasSelection) {
- item = popup.getItems().get((int) currentSelectedIndex);
- if (item != null && item instanceof RadioMenuItem) ((RadioMenuItem)item).setSelected(true);
- } else {
- if (itemInControlCount > 0) item = popup.getItems().get(0);
- }
-
- // This is a fix for RT-9071. Ideally this won't be necessary in
- // the long-run, but for now at least this resolves the
- // positioning
- // problem of ChoiceBox inside a Cell.
- getSkinnable().autosize();
- // -- End of RT-9071 fix
-
- double y = 0;
-
- if (popup.getSkin() != null) {
- ContextMenuContent cmContent = (ContextMenuContent)popup.getSkin().getNode();
- if (cmContent != null && currentSelectedIndex != -1) {
- y = -(cmContent.getMenuYOffset((int)currentSelectedIndex));
- }
- }
-
- popup.show(getSkinnable(), Side.BOTTOM, 2, y);
- } else {
- popup.hide();
- }
- } else if ("CONVERTER".equals(p)) {
- updateChoiceBoxItems();
- updatePopupItems();
- }
- }
-
private void addPopupItem(final T o, int i) {
MenuItem popupItem = null;
if (o instanceof Separator) {
// We translate the Separator into a SeparatorMenuItem...
popupItem = new SeparatorMenuItem();
@@ -311,59 +434,6 @@
// update the label
label.setText(popup.getItems().get(selectedIndex).getText());
}
}
}
-
- @Override protected void layoutChildren(final double x, final double y,
- final double w, final double h) {
- // open button width/height
- double obw = openButton.prefWidth(-1);
-
- ChoiceBox<T> control = getSkinnable();
- label.resizeRelocate(x, y, w, h);
- openButton.resize(obw, openButton.prefHeight(-1));
- positionInArea(openButton, (x+w) - obw,
- y, obw, h, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
- }
-
- @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
- final double boxWidth = label.minWidth(-1) + openButton.minWidth(-1);
- final double popupWidth = popup.minWidth(-1);
- return leftInset + Math.max(boxWidth, popupWidth) + rightInset;
- }
-
- @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
- final double displayHeight = label.minHeight(-1);
- final double openButtonHeight = openButton.minHeight(-1);
- return topInset + Math.max(displayHeight, openButtonHeight) + bottomInset;
- }
-
- @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
- final double boxWidth = label.prefWidth(-1)
- + openButton.prefWidth(-1);
- double popupWidth = popup.prefWidth(-1);
- if (popupWidth <= 0) { // first time: when the popup has not shown yet
- if (popup.getItems().size() > 0){
- popupWidth = (new Text(((MenuItem)popup.getItems().get(0)).getText())).prefWidth(-1);
- }
- }
- return (popup.getItems().size() == 0) ? 50 : leftInset + Math.max(boxWidth, popupWidth)
- + rightInset;
- }
-
- @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
- final double displayHeight = label.prefHeight(-1);
- final double openButtonHeight = openButton.prefHeight(-1);
- return topInset
- + Math.max(displayHeight, openButtonHeight)
- + bottomInset;
- }
-
- @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
- return getSkinnable().prefHeight(width);
- }
-
- @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
- return getSkinnable().prefWidth(height);
- }
}