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,33 **** * 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; import javafx.beans.WeakInvalidationListener; import javafx.util.StringConverter; import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.HPos; --- 21,40 ---- * 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.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,75 **** import com.sun.javafx.scene.control.behavior.ChoiceBoxBehavior; import javafx.collections.WeakListChangeListener; /** ! * ChoiceBoxSkin - default implementation */ ! public class ChoiceBoxSkin<T> extends BehaviorSkinBase<ChoiceBox<T>, ChoiceBoxBehavior<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 ObservableList<T> choiceBoxItems; private ContextMenu popup; --- 55,76 ---- import com.sun.javafx.scene.control.behavior.ChoiceBoxBehavior; import javafx.collections.WeakListChangeListener; /** ! * Default skin implementation for the {@link ChoiceBox} control. ! * ! * @see ChoiceBox ! * @since 9 */ ! public class ChoiceBoxSkin<T> extends SkinBase<ChoiceBox<T>> { ! /*************************************************************************** ! * * ! * Private fields * ! * * ! **************************************************************************/ private ObservableList<T> choiceBoxItems; private ContextMenu popup;
*** 84,93 **** --- 85,104 ---- */ 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,121 **** --- 123,308 ---- 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,249 **** // 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(); --- 363,372 ----
*** 311,369 **** // 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); - } } --- 434,439 ----