modules/controls/src/main/java/javafx/scene/control/skin/ComboBoxBaseSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
   1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control.skin;
  27 

  28 import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
  29 import javafx.geometry.HPos;
  30 import javafx.geometry.VPos;
  31 import javafx.scene.Node;
  32 import javafx.scene.control.ComboBoxBase;
  33 import javafx.scene.input.MouseEvent;
  34 import javafx.scene.layout.Region;
  35 import javafx.scene.layout.StackPane;
  36 
  37 import java.util.List;
  38 
  39 public abstract class ComboBoxBaseSkin<T> extends BehaviorSkinBase<ComboBoxBase<T>, ComboBoxBaseBehavior<T>> {

















  40     
  41     private Node displayNode; // this is normally either label or textField
  42     
  43     protected StackPane arrowButton;
  44     protected Region arrow;
  45     
  46     /** The mode in which this control will be represented. */
  47     private ComboBoxMode mode = ComboBoxMode.COMBOBOX;
  48     protected final ComboBoxMode getMode() { return mode; }
  49     protected final void setMode(ComboBoxMode value) { mode = value; }


  50     
  51     public ComboBoxBaseSkin(final ComboBoxBase<T> comboBox, final ComboBoxBaseBehavior<T> behavior) {
  52         // Call the super method with the ComboBox we were just given in the 
  53         // constructor, as well as an instance of the behavior class.
  54         super(comboBox, behavior);












  55         
  56         // open button / arrow
  57         arrow = new Region();
  58         arrow.setFocusTraversable(false);
  59         arrow.getStyleClass().setAll("arrow");
  60         arrow.setId("arrow");
  61         arrow.setMaxWidth(Region.USE_PREF_SIZE);
  62         arrow.setMaxHeight(Region.USE_PREF_SIZE);
  63         arrow.setMouseTransparent(true);
  64 
  65         arrowButton = new StackPane();
  66         arrowButton.setFocusTraversable(false);
  67         arrowButton.setId("arrow-button");
  68         arrowButton.getStyleClass().setAll("arrow-button");
  69         arrowButton.getChildren().add(arrow);
  70 
  71         if (comboBox.isEditable()) {
  72             //
  73             // arrowButton behaves like a button.
  74             // This is strongly tied to the implementation in ComboBoxBaseBehavior.
  75             //
  76             arrowButton.addEventHandler(MouseEvent.MOUSE_ENTERED,  (e) -> getBehavior().mouseEntered(e));
  77             arrowButton.addEventHandler(MouseEvent.MOUSE_PRESSED,  (e) -> { getBehavior().mousePressed(e);  e.consume(); });
  78             arrowButton.addEventHandler(MouseEvent.MOUSE_RELEASED, (e) -> { getBehavior().mouseReleased(e); e.consume();});
  79             arrowButton.addEventHandler(MouseEvent.MOUSE_EXITED, (e) -> getBehavior().mouseExited(e));
  80 
  81         }
  82         getChildren().add(arrowButton);
  83 
  84         // When ComboBoxBase focus shifts to another node, it should hide.
  85         getSkinnable().focusedProperty().addListener((observable, oldValue, newValue) -> {
  86             if (!newValue) {
  87                 focusLost();
  88             }
  89         });
  90         
  91         // Register listeners
  92         registerChangeListener(comboBox.editableProperty(), "EDITABLE");
  93         registerChangeListener(comboBox.showingProperty(), "SHOWING");
  94         registerChangeListener(comboBox.focusedProperty(), "FOCUSED");
  95         registerChangeListener(comboBox.valueProperty(), "VALUE");


  96     }
  97     
  98     protected void focusLost() {
  99         getSkinnable().hide();
 100     }









 101     /**
 102      * This method should return a Node that will be positioned within the
 103      * ComboBox 'button' area.
 104      */
 105     public abstract Node getDisplayNode();
 106 
 107     /**
 108      * This method will be called when the ComboBox popup should be displayed.
 109      * It is up to specific skin implementations to determine how this is handled.
 110      */
 111     public abstract void show();
 112  
 113     /**
 114      * This method will be called when the ComboBox popup should be hidden.
 115      * It is up to specific skin implementations to determine how this is handled.
 116      */
 117     public abstract void hide();
 118     
 119      /**
 120      * Handles changes to properties of the MenuButton.
 121      */
 122     @Override protected void handleControlPropertyChanged(String p) {
 123         super.handleControlPropertyChanged(p);
 124 
 125         if ("SHOWING".equals(p)) {
 126             if (getSkinnable().isShowing()) {
 127                 show();
 128             } else {
 129                 hide();
 130             }
 131         } else if ("EDITABLE".equals(p)) {
 132             updateDisplayArea();
 133         } else if ("VALUE".equals(p)) {
 134             updateDisplayArea();
 135         } 
 136     }
 137     
 138     protected void updateDisplayArea() {
 139         final List<Node> children = getChildren();
 140         final Node oldDisplayNode = displayNode;
 141         displayNode = getDisplayNode();
 142 
 143         // don't remove displayNode if it hasn't changed.
 144         if (oldDisplayNode != null && oldDisplayNode != displayNode) {
 145             children.remove(oldDisplayNode);
 146         }
 147 
 148         if (displayNode != null && !children.contains(displayNode)) {
 149             children.add(displayNode);
 150             displayNode.applyCss();
 151         }
 152     }
 153     
 154     private boolean isButton() {
 155         return getMode() == ComboBoxMode.BUTTON;
 156     }
 157     
 158     @Override protected void layoutChildren(final double x, final double y,
 159             final double w, final double h) {
 160         if (displayNode == null) {
 161             updateDisplayArea();
 162         }
 163 
 164         final double arrowWidth = snapSize(arrow.prefWidth(-1));
 165         final double arrowButtonWidth = (isButton()) ? 0 :
 166                 arrowButton.snappedLeftInset() + arrowWidth +
 167                 arrowButton.snappedRightInset();
 168         
 169         if (displayNode != null) {
 170             displayNode.resizeRelocate(x, y, w - arrowButtonWidth, h);
 171         }
 172 
 173         arrowButton.setVisible(! isButton());
 174         if (! isButton()) {
 175             arrowButton.resize(arrowButtonWidth, h);
 176             positionInArea(arrowButton, (x + w) - arrowButtonWidth, y,
 177                     arrowButtonWidth, h, 0, HPos.CENTER, VPos.CENTER);
 178         }
 179     }
 180     

 181     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 182         if (displayNode == null) {
 183             updateDisplayArea();
 184         }
 185 
 186         final double arrowWidth = snapSize(arrow.prefWidth(-1));
 187         final double arrowButtonWidth = isButton() ? 0 :
 188                                         arrowButton.snappedLeftInset() +
 189                                         arrowWidth + 
 190                                         arrowButton.snappedRightInset();
 191         final double displayNodeWidth = displayNode == null ? 0 : displayNode.prefWidth(height);
 192         
 193         final double totalWidth = displayNodeWidth + arrowButtonWidth;
 194         return leftInset + totalWidth + rightInset;
 195     }
 196     

 197     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 198         if (displayNode == null) {
 199             updateDisplayArea();
 200         }
 201 
 202         double ph;
 203         if (displayNode == null) {
 204             final int DEFAULT_HEIGHT = 21;
 205             double arrowHeight = (isButton()) ? 0 : 
 206                     (arrowButton.snappedTopInset() + arrow.prefHeight(-1) + arrowButton.snappedBottomInset());
 207             ph = Math.max(DEFAULT_HEIGHT, arrowHeight);
 208         } else {
 209             ph = displayNode.prefHeight(width);
 210         }
 211 
 212         return topInset+ ph + bottomInset;
 213     }
 214 

 215     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 216         return getSkinnable().prefWidth(height);
 217     }
 218 

 219     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 220         return getSkinnable().prefHeight(width);
 221     }
 222 
 223     // Overridden so that we use the displayNode as the baseline, rather than the arrow.
 224     // See RT-30754 for more information.

 225     @Override protected double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
 226         if (displayNode == null) {
 227             updateDisplayArea();
 228         }
 229 
 230         if (displayNode != null) {
 231             return displayNode.getLayoutBounds().getMinY() + displayNode.getLayoutY() + displayNode.getBaselineOffset();
 232         }
 233 
 234         return super.computeBaselineOffset(topInset, rightInset, bottomInset, leftInset);




































 235     }
 236 }
   1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import javafx.scene.control.SkinBase;
  29 import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
  30 import javafx.geometry.HPos;
  31 import javafx.geometry.VPos;
  32 import javafx.scene.Node;
  33 import javafx.scene.control.ComboBoxBase;
  34 import javafx.scene.input.MouseEvent;
  35 import javafx.scene.layout.Region;
  36 import javafx.scene.layout.StackPane;
  37 
  38 import java.util.List;
  39 
  40 /**
  41  * An abstract class intended to be used as the base skin for ComboBox-like
  42  * controls that are based on {@link ComboBoxBase}. Most users of this skin class
  43  * would be well-advised to also look at {@link ComboBoxPopupControl} for
  44  * additional useful API.
  45  *
  46  * @since 9
  47  * @param <T> The type of the ComboBox-like control.
  48  * @see ComboBoxBase
  49  * @see ComboBoxPopupControl
  50  */
  51 public abstract class ComboBoxBaseSkin<T> extends SkinBase<ComboBoxBase<T>> {
  52 
  53     /***************************************************************************
  54      *                                                                         *
  55      * Private Fields                                                          *
  56      *                                                                         *
  57      **************************************************************************/
  58     
  59     private Node displayNode; // this is normally either label or textField
  60     
  61     StackPane arrowButton;
  62     Region arrow;
  63     
  64     /** The mode in which this control will be represented. */
  65     private ComboBoxMode mode = ComboBoxMode.COMBOBOX;
  66     final ComboBoxMode getMode() { return mode; }
  67     final void setMode(ComboBoxMode value) { mode = value; }
  68 
  69 
  70 
  71     /***************************************************************************
  72      *                                                                         *
  73      * Constructors                                                            *
  74      *                                                                         *
  75      **************************************************************************/
  76 
  77     /**
  78      * Creates a new instance of ComboBoxBaseSkin, although note that this
  79      * instance does not handle any behavior / input mappings - this needs to be
  80      * handled appropriately by subclasses.
  81      *
  82      * @param control The control that this skin should be installed onto.
  83      */
  84     public ComboBoxBaseSkin(final ComboBoxBase<T> control) {
  85         // Call the super method with the ComboBox we were just given in the constructor
  86         super(control);
  87 
  88         // open button / arrow
  89         arrow = new Region();
  90         arrow.setFocusTraversable(false);
  91         arrow.getStyleClass().setAll("arrow");
  92         arrow.setId("arrow");
  93         arrow.setMaxWidth(Region.USE_PREF_SIZE);
  94         arrow.setMaxHeight(Region.USE_PREF_SIZE);
  95         arrow.setMouseTransparent(true);
  96 
  97         arrowButton = new StackPane();
  98         arrowButton.setFocusTraversable(false);
  99         arrowButton.setId("arrow-button");
 100         arrowButton.getStyleClass().setAll("arrow-button");
 101         arrowButton.getChildren().add(arrow);
 102 
 103         if (control.isEditable()) {
 104             //
 105             // arrowButton behaves like a button.
 106             // This is strongly tied to the implementation in ComboBoxBaseBehavior.
 107             //
 108             arrowButton.addEventHandler(MouseEvent.MOUSE_ENTERED,  (e) ->   getBehavior().mouseEntered(e));
 109             arrowButton.addEventHandler(MouseEvent.MOUSE_PRESSED,  (e) -> { getBehavior().mousePressed(e);  e.consume(); });
 110             arrowButton.addEventHandler(MouseEvent.MOUSE_RELEASED, (e) -> { getBehavior().mouseReleased(e); e.consume();});
 111             arrowButton.addEventHandler(MouseEvent.MOUSE_EXITED,   (e) ->   getBehavior().mouseExited(e));
 112 
 113         }
 114         getChildren().add(arrowButton);
 115 
 116         // When ComboBoxBase focus shifts to another node, it should hide.
 117         getSkinnable().focusedProperty().addListener((observable, oldValue, newValue) -> {
 118             if (!newValue) {
 119                 focusLost();
 120             }
 121         });
 122         
 123         // Register listeners
 124         registerChangeListener(control.editableProperty(), e -> updateDisplayArea());
 125         registerChangeListener(control.showingProperty(), e -> {
 126             if (getSkinnable().isShowing()) {
 127                 show();
 128             } else {
 129                 hide();
 130             }
 131         });
 132         registerChangeListener(control.valueProperty(), e -> updateDisplayArea());

 133     }
 134 
 135 
 136 
 137     /***************************************************************************
 138      *                                                                         *
 139      * Public API                                                              *
 140      *                                                                         *
 141      **************************************************************************/
 142 
 143     /**
 144      * This method should return a Node that will be positioned within the
 145      * ComboBox 'button' area.
 146      */
 147     public abstract Node getDisplayNode();
 148 
 149     /**
 150      * This method will be called when the ComboBox popup should be displayed.
 151      * It is up to specific skin implementations to determine how this is handled.
 152      */
 153     public abstract void show();
 154  
 155     /**
 156      * This method will be called when the ComboBox popup should be hidden.
 157      * It is up to specific skin implementations to determine how this is handled.
 158      */
 159     public abstract void hide();
 160     
 161     /** {@inheritDoc} */






































 162     @Override protected void layoutChildren(final double x, final double y,
 163             final double w, final double h) {
 164         if (displayNode == null) {
 165             updateDisplayArea();
 166         }
 167 
 168         final double arrowWidth = snapSize(arrow.prefWidth(-1));
 169         final double arrowButtonWidth = (isButton()) ? 0 :
 170                 arrowButton.snappedLeftInset() + arrowWidth +
 171                 arrowButton.snappedRightInset();
 172         
 173         if (displayNode != null) {
 174             displayNode.resizeRelocate(x, y, w - arrowButtonWidth, h);
 175         }
 176 
 177         arrowButton.setVisible(! isButton());
 178         if (! isButton()) {
 179             arrowButton.resize(arrowButtonWidth, h);
 180             positionInArea(arrowButton, (x + w) - arrowButtonWidth, y,
 181                     arrowButtonWidth, h, 0, HPos.CENTER, VPos.CENTER);
 182         }
 183     }
 184 
 185     /** {@inheritDoc} */
 186     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 187         if (displayNode == null) {
 188             updateDisplayArea();
 189         }
 190 
 191         final double arrowWidth = snapSize(arrow.prefWidth(-1));
 192         final double arrowButtonWidth = isButton() ? 0 :
 193                                         arrowButton.snappedLeftInset() +
 194                                         arrowWidth + 
 195                                         arrowButton.snappedRightInset();
 196         final double displayNodeWidth = displayNode == null ? 0 : displayNode.prefWidth(height);
 197         
 198         final double totalWidth = displayNodeWidth + arrowButtonWidth;
 199         return leftInset + totalWidth + rightInset;
 200     }
 201 
 202     /** {@inheritDoc} */
 203     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 204         if (displayNode == null) {
 205             updateDisplayArea();
 206         }
 207 
 208         double ph;
 209         if (displayNode == null) {
 210             final int DEFAULT_HEIGHT = 21;
 211             double arrowHeight = (isButton()) ? 0 : 
 212                     (arrowButton.snappedTopInset() + arrow.prefHeight(-1) + arrowButton.snappedBottomInset());
 213             ph = Math.max(DEFAULT_HEIGHT, arrowHeight);
 214         } else {
 215             ph = displayNode.prefHeight(width);
 216         }
 217 
 218         return topInset+ ph + bottomInset;
 219     }
 220 
 221     /** {@inheritDoc} */
 222     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 223         return getSkinnable().prefWidth(height);
 224     }
 225 
 226     /** {@inheritDoc} */
 227     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 228         return getSkinnable().prefHeight(width);
 229     }
 230 
 231     // Overridden so that we use the displayNode as the baseline, rather than the arrow.
 232     // See RT-30754 for more information.
 233     /** {@inheritDoc} */
 234     @Override protected double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
 235         if (displayNode == null) {
 236             updateDisplayArea();
 237         }
 238 
 239         if (displayNode != null) {
 240             return displayNode.getLayoutBounds().getMinY() + displayNode.getLayoutY() + displayNode.getBaselineOffset();
 241         }
 242 
 243         return super.computeBaselineOffset(topInset, rightInset, bottomInset, leftInset);
 244     }
 245 
 246 
 247 
 248     /***************************************************************************
 249      *                                                                         *
 250      * Private implementation                                                  *
 251      *                                                                         *
 252      **************************************************************************/
 253 
 254     ComboBoxBaseBehavior getBehavior() {
 255         return null;
 256     }
 257 
 258     void focusLost() {
 259         getSkinnable().hide();
 260     }
 261 
 262     private boolean isButton() {
 263         return getMode() == ComboBoxMode.BUTTON;
 264     }
 265 
 266     void updateDisplayArea() {
 267         final List<Node> children = getChildren();
 268         final Node oldDisplayNode = displayNode;
 269         displayNode = getDisplayNode();
 270 
 271         // don't remove displayNode if it hasn't changed.
 272         if (oldDisplayNode != null && oldDisplayNode != displayNode) {
 273             children.remove(oldDisplayNode);
 274         }
 275 
 276         if (displayNode != null && !children.contains(displayNode)) {
 277             children.add(displayNode);
 278             displayNode.applyCss();
 279         }
 280     }
 281 }