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

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   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 package com.sun.javafx.scene.control.skin;
  26 
  27 import com.sun.javafx.scene.control.behavior.SpinnerBehavior;
  28 import com.sun.javafx.scene.traversal.Algorithm;

  29 import com.sun.javafx.scene.traversal.Direction;
  30 import com.sun.javafx.scene.traversal.ParentTraversalEngine;





  31 import com.sun.javafx.scene.traversal.TraversalContext;
  32 import javafx.collections.ListChangeListener;
  33 import javafx.css.PseudoClass;
  34 import javafx.geometry.HPos;
  35 import javafx.geometry.VPos;
  36 import javafx.scene.AccessibleAction;
  37 import javafx.scene.AccessibleRole;
  38 import javafx.scene.Node;
  39 import javafx.scene.control.Spinner;
  40 import javafx.scene.control.TextField;
  41 import javafx.scene.input.KeyCode;
  42 import javafx.scene.input.KeyEvent;
  43 import javafx.scene.layout.Region;
  44 import javafx.scene.layout.StackPane;
  45 
  46 import java.util.List;
  47 
  48 public class SpinnerSkin<T> extends BehaviorSkinBase<Spinner<T>, SpinnerBehavior<T>> {












  49 
  50     private TextField textField;
  51 
  52     private Region incrementArrow;
  53     private StackPane incrementArrowButton;
  54 
  55     private Region decrementArrow;
  56     private StackPane decrementArrowButton;
  57 
  58     // rather than create an private enum, lets just use an int, here's the important details:
  59     private static final int ARROWS_ON_RIGHT_VERTICAL   = 0;
  60     private static final int ARROWS_ON_LEFT_VERTICAL    = 1;
  61     private static final int ARROWS_ON_RIGHT_HORIZONTAL = 2;
  62     private static final int ARROWS_ON_LEFT_HORIZONTAL  = 3;
  63     private static final int SPLIT_ARROWS_VERTICAL      = 4;
  64     private static final int SPLIT_ARROWS_HORIZONTAL    = 5;
  65 
  66     private int layoutMode = 0;
  67 
  68     public SpinnerSkin(Spinner<T> spinner) {
  69         super(spinner, new SpinnerBehavior<T>(spinner));
  70 
  71         textField = spinner.getEditor();





















  72         getChildren().add(textField);
  73 
  74         updateStyleClass();
  75         spinner.getStyleClass().addListener((ListChangeListener<String>) c -> updateStyleClass());
  76 
  77         // increment / decrement arrows
  78         incrementArrow = new Region();
  79         incrementArrow.setFocusTraversable(false);
  80         incrementArrow.getStyleClass().setAll("increment-arrow");
  81         incrementArrow.setMaxWidth(Region.USE_PREF_SIZE);
  82         incrementArrow.setMaxHeight(Region.USE_PREF_SIZE);
  83         incrementArrow.setMouseTransparent(true);
  84 
  85         incrementArrowButton = new StackPane() {
  86             public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
  87                 switch (action) {
  88                     case FIRE: getSkinnable().increment();
  89                     default: super.executeAccessibleAction(action, parameters);
  90                 }
  91             }
  92         };
  93         incrementArrowButton.setAccessibleRole(AccessibleRole.INCREMENT_BUTTON);
  94         incrementArrowButton.setFocusTraversable(false);
  95         incrementArrowButton.getStyleClass().setAll("increment-arrow-button");
  96         incrementArrowButton.getChildren().add(incrementArrow);
  97         incrementArrowButton.setOnMousePressed(e -> {
  98             getSkinnable().requestFocus();
  99             getBehavior().startSpinning(true);
 100         });
 101         incrementArrowButton.setOnMouseReleased(e -> getBehavior().stopSpinning());
 102 
 103         decrementArrow = new Region();
 104         decrementArrow.setFocusTraversable(false);
 105         decrementArrow.getStyleClass().setAll("decrement-arrow");
 106         decrementArrow.setMaxWidth(Region.USE_PREF_SIZE);
 107         decrementArrow.setMaxHeight(Region.USE_PREF_SIZE);
 108         decrementArrow.setMouseTransparent(true);
 109 
 110         decrementArrowButton = new StackPane() {
 111             public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 112                 switch (action) {
 113                     case FIRE: getSkinnable().decrement();
 114                     default: super.executeAccessibleAction(action, parameters);
 115                 }
 116             }
 117         };
 118         decrementArrowButton.setAccessibleRole(AccessibleRole.DECREMENT_BUTTON);
 119         decrementArrowButton.setFocusTraversable(false);
 120         decrementArrowButton.getStyleClass().setAll("decrement-arrow-button");
 121         decrementArrowButton.getChildren().add(decrementArrow);
 122         decrementArrowButton.setOnMousePressed(e -> {
 123             getSkinnable().requestFocus();
 124             getBehavior().startSpinning(false);
 125         });
 126         decrementArrowButton.setOnMouseReleased(e -> getBehavior().stopSpinning());
 127 
 128         getChildren().addAll(incrementArrowButton, decrementArrowButton);
 129 
 130         // Fixes in the same vein as ComboBoxListViewSkin
 131 
 132         // move fake focus in to the textfield if the spinner is editable
 133         spinner.focusedProperty().addListener((ov, t, hasFocus) -> {
 134             // Fix for the regression noted in a comment in RT-29885.
 135             ((ComboBoxListViewSkin.FakeFocusTextField)textField).setFakeFocus(hasFocus);
 136         });
 137 
 138         spinner.addEventFilter(KeyEvent.ANY, ke -> {
 139             if (spinner.isEditable()) {
 140                 // This prevents a stack overflow from our rebroadcasting of the
 141                 // event to the textfield that occurs in the final else statement
 142                 // of the conditions below.
 143                 if (ke.getTarget().equals(textField)) return;
 144 
 145                 // Fix for RT-38527 which led to a stack overflow
 146                 if (ke.getCode() == KeyCode.ESCAPE) return;
 147 
 148                 // Fix for the regression noted in a comment in RT-29885.
 149                 // This forwards the event down into the TextField when
 150                 // the key event is actually received by the Spinner.
 151                 textField.fireEvent(ke.copyFor(textField, textField));
 152                 ke.consume();
 153             }
 154         });
 155 
 156         // This event filter is to enable keyboard events being delivered to the
 157         // spinner when the user has mouse clicked into the TextField area of the
 158         // Spinner control. Without this the up/down/left/right arrow keys don't
 159         // work when you click inside the TextField area (but they do in the case
 160         // of tabbing in).
 161         textField.addEventFilter(KeyEvent.ANY, ke -> {
 162             if (! spinner.isEditable()) {
 163                 spinner.fireEvent(ke.copyFor(spinner, spinner));
 164                 ke.consume();
 165             }
 166         });
 167 
 168         textField.focusedProperty().addListener((ov, t, hasFocus) -> {
 169             // Fix for RT-29885
 170             spinner.getProperties().put("FOCUSED", hasFocus);
 171             // --- end of RT-29885
 172 
 173             // RT-21454 starts here
 174             if (! hasFocus) {
 175                 pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, false);
 176             } else {
 177                 pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, true);
 178             }
 179             // --- end of RT-21454
 180         });
 181 
 182         // end of comboBox-esque fixes
 183 
 184         textField.focusTraversableProperty().bind(spinner.editableProperty());
 185 
 186 
 187         // Following code borrowed from ComboBoxPopupControl, to resolve the
 188         // issue initially identified in RT-36902, but specifically (for Spinner)
 189         // identified in RT-40625
 190         spinner.setImpl_traversalEngine(new ParentTraversalEngine(spinner, new Algorithm() {
 191             @Override public Node select(Node owner, Direction dir, TraversalContext context) {
 192                 return null;
 193             }
 194 
 195             @Override public Node selectFirst(TraversalContext context) {
 196                 return null;
 197             }
 198 
 199             @Override public Node selectLast(TraversalContext context) {
 200                 return null;
 201             }
 202         }));
 203     }
 204 
 205     private void updateStyleClass() {
 206         final List<String> styleClass = getSkinnable().getStyleClass();
 207 
 208         if (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL)) {
 209             layoutMode = ARROWS_ON_LEFT_VERTICAL;
 210         } else if (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL)) {
 211             layoutMode = ARROWS_ON_LEFT_HORIZONTAL;
 212         } else if (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL)) {
 213             layoutMode = ARROWS_ON_RIGHT_HORIZONTAL;
 214         } else if (styleClass.contains(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL)) {
 215             layoutMode = SPLIT_ARROWS_VERTICAL;
 216         } else if (styleClass.contains(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL)) {
 217             layoutMode = SPLIT_ARROWS_HORIZONTAL;
 218         } else {
 219             layoutMode = ARROWS_ON_RIGHT_VERTICAL;

 220         }
 221     }
 222 

 223     @Override protected void layoutChildren(final double x, final double y,
 224                                             final double w, final double h) {
 225 
 226         final double incrementArrowButtonWidth = incrementArrowButton.snappedLeftInset() +
 227                 snapSize(incrementArrow.prefWidth(-1)) + incrementArrowButton.snappedRightInset();
 228 
 229         final double decrementArrowButtonWidth = decrementArrowButton.snappedLeftInset() +
 230                 snapSize(decrementArrow.prefWidth(-1)) + decrementArrowButton.snappedRightInset();
 231 
 232         final double widestArrowButton = Math.max(incrementArrowButtonWidth, decrementArrowButtonWidth);
 233 
 234         // we need to decide on our layout approach, and this depends on
 235         // the presence of style classes in the Spinner styleClass list.
 236         // To be a bit more efficient, we observe the list for changes, so
 237         // here in layoutChildren we can just react to a few booleans.
 238         if (layoutMode == ARROWS_ON_RIGHT_VERTICAL || layoutMode == ARROWS_ON_LEFT_VERTICAL) {
 239             final double textFieldStartX = layoutMode == ARROWS_ON_RIGHT_VERTICAL ? x : x + widestArrowButton;
 240             final double buttonStartX = layoutMode == ARROWS_ON_RIGHT_VERTICAL ? x + w - widestArrowButton : x;
 241             final double halfHeight = Math.floor(h / 2.0);
 242 


 285             // decrement is at the bottom
 286             decrementArrowButton.resize(w, tallestArrowButton);
 287             positionInArea(decrementArrowButton, x, h - tallestArrowButton,
 288                     w, tallestArrowButton, 0, HPos.CENTER, VPos.CENTER);
 289         } else if (layoutMode == SPLIT_ARROWS_HORIZONTAL) {
 290             // decrement is on the left-hand side
 291             decrementArrowButton.resize(widestArrowButton, h);
 292             positionInArea(decrementArrowButton, x, y,
 293                     widestArrowButton, h, 0, HPos.CENTER, VPos.CENTER);
 294 
 295             // textfield in the middle
 296             textField.resizeRelocate(x + widestArrowButton, y, w - (2*widestArrowButton), h);
 297 
 298             // increment is on the right-hand side
 299             incrementArrowButton.resize(widestArrowButton, h);
 300             positionInArea(incrementArrowButton, w - widestArrowButton, y,
 301                     widestArrowButton, h, 0, HPos.CENTER, VPos.CENTER);
 302         }
 303     }
 304 

 305     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 306         return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
 307     }
 308 

 309     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 310         final double textfieldWidth = textField.prefWidth(height);
 311         return leftInset + textfieldWidth + rightInset;
 312     }
 313 

 314     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 315         double ph;
 316         double textFieldHeight = textField.prefHeight(width);
 317 
 318         if (layoutMode == SPLIT_ARROWS_VERTICAL) {
 319             ph = topInset + incrementArrowButton.prefHeight(width) +
 320                     textFieldHeight + decrementArrowButton.prefHeight(width) + bottomInset;
 321         } else {
 322             ph = topInset + textFieldHeight + bottomInset;
 323         }
 324 
 325         return ph;
 326     }
 327 

 328     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 329         return getSkinnable().prefWidth(height);
 330     }
 331 

 332     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 333         return getSkinnable().prefHeight(width);
 334     }
 335 
 336     // Overridden so that we use the textfield as the baseline, rather than the arrow.
 337     // See RT-30754 for more information.

 338     @Override protected double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
 339         return textField.getLayoutBounds().getMinY() + textField.getLayoutY() + textField.getBaselineOffset();
 340     }



























 341 
 342 
 343     /***************************************************************************
 344      *                                                                         *
 345      * Stylesheet Handling                                                     *
 346      *                                                                         *
 347      **************************************************************************/
 348 
 349     private static PseudoClass CONTAINS_FOCUS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("contains-focus");
 350 }


   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 package javafx.scene.control.skin;
  26 
  27 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  28 import com.sun.javafx.scene.traversal.Algorithm;
  29 import com.sun.javafx.scene.control.FakeFocusTextField;
  30 import com.sun.javafx.scene.traversal.Direction;
  31 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  32 import javafx.scene.control.Accordion;
  33 import javafx.scene.control.Button;
  34 import javafx.scene.control.Control;
  35 import javafx.scene.control.SkinBase;
  36 import com.sun.javafx.scene.control.behavior.SpinnerBehavior;
  37 import com.sun.javafx.scene.traversal.TraversalContext;
  38 import javafx.collections.ListChangeListener;
  39 import javafx.css.PseudoClass;
  40 import javafx.geometry.HPos;
  41 import javafx.geometry.VPos;
  42 import javafx.scene.AccessibleAction;
  43 import javafx.scene.AccessibleRole;
  44 import javafx.scene.Node;
  45 import javafx.scene.control.Spinner;
  46 import javafx.scene.control.TextField;
  47 import javafx.scene.input.KeyCode;
  48 import javafx.scene.input.KeyEvent;
  49 import javafx.scene.layout.Region;
  50 import javafx.scene.layout.StackPane;
  51 
  52 import java.util.List;
  53 
  54 /**
  55  * Default skin implementation for the {@link Spinner} control.
  56  *
  57  * @see Spinner
  58  * @since 9
  59  */
  60 public class SpinnerSkin<T> extends SkinBase<Spinner<T>> {
  61 
  62     /***************************************************************************
  63      *                                                                         *
  64      * Private fields                                                          *
  65      *                                                                         *
  66      **************************************************************************/
  67 
  68     private TextField textField;
  69 
  70     private Region incrementArrow;
  71     private StackPane incrementArrowButton;
  72 
  73     private Region decrementArrow;
  74     private StackPane decrementArrowButton;
  75 
  76     // rather than create an private enum, lets just use an int, here's the important details:
  77     private static final int ARROWS_ON_RIGHT_VERTICAL   = 0;
  78     private static final int ARROWS_ON_LEFT_VERTICAL    = 1;
  79     private static final int ARROWS_ON_RIGHT_HORIZONTAL = 2;
  80     private static final int ARROWS_ON_LEFT_HORIZONTAL  = 3;
  81     private static final int SPLIT_ARROWS_VERTICAL      = 4;
  82     private static final int SPLIT_ARROWS_HORIZONTAL    = 5;
  83 
  84     private int layoutMode = 0;
  85 
  86     private final SpinnerBehavior behavior;
  87 
  88 
  89 
  90     /***************************************************************************
  91      *                                                                         *
  92      * Constructors                                                            *
  93      *                                                                         *
  94      **************************************************************************/
  95 
  96     /**
  97      * Creates a new SpinnerSkin instance, installing the necessary child
  98      * nodes into the Control {@link Control#getChildren() children} list, as
  99      * well as the necessary input mappings for handling key, mouse, etc events.
 100      *
 101      * @param control The control that this skin should be installed onto.
 102      */
 103     public SpinnerSkin(Spinner<T> control) {
 104         super(control);
 105 
 106         // install default input map for the Button control
 107         behavior = new SpinnerBehavior<>(control);
 108 //        control.setInputMap(behavior.getInputMap());
 109 
 110         textField = control.getEditor();
 111         getChildren().add(textField);
 112 
 113         updateStyleClass();
 114         control.getStyleClass().addListener((ListChangeListener<String>) c -> updateStyleClass());
 115 
 116         // increment / decrement arrows
 117         incrementArrow = new Region();
 118         incrementArrow.setFocusTraversable(false);
 119         incrementArrow.getStyleClass().setAll("increment-arrow");
 120         incrementArrow.setMaxWidth(Region.USE_PREF_SIZE);
 121         incrementArrow.setMaxHeight(Region.USE_PREF_SIZE);
 122         incrementArrow.setMouseTransparent(true);
 123 
 124         incrementArrowButton = new StackPane() {
 125             public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 126                 switch (action) {
 127                     case FIRE: getSkinnable().increment();
 128                     default: super.executeAccessibleAction(action, parameters);
 129                 }
 130             }
 131         };
 132         incrementArrowButton.setAccessibleRole(AccessibleRole.INCREMENT_BUTTON);
 133         incrementArrowButton.setFocusTraversable(false);
 134         incrementArrowButton.getStyleClass().setAll("increment-arrow-button");
 135         incrementArrowButton.getChildren().add(incrementArrow);
 136         incrementArrowButton.setOnMousePressed(e -> {
 137             getSkinnable().requestFocus();
 138             behavior.startSpinning(true);
 139         });
 140         incrementArrowButton.setOnMouseReleased(e -> behavior.stopSpinning());
 141 
 142         decrementArrow = new Region();
 143         decrementArrow.setFocusTraversable(false);
 144         decrementArrow.getStyleClass().setAll("decrement-arrow");
 145         decrementArrow.setMaxWidth(Region.USE_PREF_SIZE);
 146         decrementArrow.setMaxHeight(Region.USE_PREF_SIZE);
 147         decrementArrow.setMouseTransparent(true);
 148 
 149         decrementArrowButton = new StackPane() {
 150             public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 151                 switch (action) {
 152                     case FIRE: getSkinnable().decrement();
 153                     default: super.executeAccessibleAction(action, parameters);
 154                 }
 155             }
 156         };
 157         decrementArrowButton.setAccessibleRole(AccessibleRole.DECREMENT_BUTTON);
 158         decrementArrowButton.setFocusTraversable(false);
 159         decrementArrowButton.getStyleClass().setAll("decrement-arrow-button");
 160         decrementArrowButton.getChildren().add(decrementArrow);
 161         decrementArrowButton.setOnMousePressed(e -> {
 162             getSkinnable().requestFocus();
 163             behavior.startSpinning(false);
 164         });
 165         decrementArrowButton.setOnMouseReleased(e -> behavior.stopSpinning());
 166 
 167         getChildren().addAll(incrementArrowButton, decrementArrowButton);
 168 
 169         // Fixes in the same vein as ComboBoxListViewSkin
 170 
 171         // move fake focus in to the textfield if the spinner is editable
 172         control.focusedProperty().addListener((ov, t, hasFocus) -> {
 173             // Fix for the regression noted in a comment in RT-29885.
 174             ((FakeFocusTextField)textField).setFakeFocus(hasFocus);
 175         });
 176 
 177         control.addEventFilter(KeyEvent.ANY, ke -> {
 178             if (control.isEditable()) {
 179                 // This prevents a stack overflow from our rebroadcasting of the
 180                 // event to the textfield that occurs in the final else statement
 181                 // of the conditions below.
 182                 if (ke.getTarget().equals(textField)) return;
 183 
 184                 // Fix for RT-38527 which led to a stack overflow
 185                 if (ke.getCode() == KeyCode.ESCAPE) return;
 186 
 187                 // Fix for the regression noted in a comment in RT-29885.
 188                 // This forwards the event down into the TextField when
 189                 // the key event is actually received by the Spinner.
 190                 textField.fireEvent(ke.copyFor(textField, textField));
 191                 ke.consume();
 192             }
 193         });
 194 
 195         // This event filter is to enable keyboard events being delivered to the
 196         // spinner when the user has mouse clicked into the TextField area of the
 197         // Spinner control. Without this the up/down/left/right arrow keys don't
 198         // work when you click inside the TextField area (but they do in the case
 199         // of tabbing in).
 200         textField.addEventFilter(KeyEvent.ANY, ke -> {
 201             if (! control.isEditable()) {
 202                 control.fireEvent(ke.copyFor(control, control));
 203                 ke.consume();
 204             }
 205         });
 206 
 207         textField.focusedProperty().addListener((ov, t, hasFocus) -> {
 208             // Fix for RT-29885
 209             control.getProperties().put("FOCUSED", hasFocus);
 210             // --- end of RT-29885
 211 
 212             // RT-21454 starts here
 213             if (! hasFocus) {
 214                 pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, false);
 215             } else {
 216                 pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, true);
 217             }
 218             // --- end of RT-21454
 219         });
 220 
 221         // end of comboBox-esque fixes
 222 
 223         textField.focusTraversableProperty().bind(control.editableProperty());
 224 
 225 
 226         // Following code borrowed from ComboBoxPopupControl, to resolve the
 227         // issue initially identified in RT-36902, but specifically (for Spinner)
 228         // identified in RT-40625
 229         control.setImpl_traversalEngine(new ParentTraversalEngine(control, new Algorithm() {
 230             @Override public Node select(Node owner, Direction dir, TraversalContext context) {
 231                 return null;
 232             }
 233 
 234             @Override public Node selectFirst(TraversalContext context) {
 235                 return null;
 236             }
 237 
 238             @Override public Node selectLast(TraversalContext context) {
 239                 return null;
 240             }
 241         }));
 242     }
 243 


 244 
 245 
 246     /***************************************************************************
 247      *                                                                         *
 248      * Public API                                                              *
 249      *                                                                         *
 250      **************************************************************************/
 251 
 252     /** {@inheritDoc} */
 253     @Override public void dispose() {
 254         super.dispose();
 255 
 256         if (behavior != null) {
 257             behavior.dispose();
 258         }
 259     }
 260 
 261     /** {@inheritDoc} */
 262     @Override protected void layoutChildren(final double x, final double y,
 263                                             final double w, final double h) {
 264 
 265         final double incrementArrowButtonWidth = incrementArrowButton.snappedLeftInset() +
 266                 snapSize(incrementArrow.prefWidth(-1)) + incrementArrowButton.snappedRightInset();
 267 
 268         final double decrementArrowButtonWidth = decrementArrowButton.snappedLeftInset() +
 269                 snapSize(decrementArrow.prefWidth(-1)) + decrementArrowButton.snappedRightInset();
 270 
 271         final double widestArrowButton = Math.max(incrementArrowButtonWidth, decrementArrowButtonWidth);
 272 
 273         // we need to decide on our layout approach, and this depends on
 274         // the presence of style classes in the Spinner styleClass list.
 275         // To be a bit more efficient, we observe the list for changes, so
 276         // here in layoutChildren we can just react to a few booleans.
 277         if (layoutMode == ARROWS_ON_RIGHT_VERTICAL || layoutMode == ARROWS_ON_LEFT_VERTICAL) {
 278             final double textFieldStartX = layoutMode == ARROWS_ON_RIGHT_VERTICAL ? x : x + widestArrowButton;
 279             final double buttonStartX = layoutMode == ARROWS_ON_RIGHT_VERTICAL ? x + w - widestArrowButton : x;
 280             final double halfHeight = Math.floor(h / 2.0);
 281 


 324             // decrement is at the bottom
 325             decrementArrowButton.resize(w, tallestArrowButton);
 326             positionInArea(decrementArrowButton, x, h - tallestArrowButton,
 327                     w, tallestArrowButton, 0, HPos.CENTER, VPos.CENTER);
 328         } else if (layoutMode == SPLIT_ARROWS_HORIZONTAL) {
 329             // decrement is on the left-hand side
 330             decrementArrowButton.resize(widestArrowButton, h);
 331             positionInArea(decrementArrowButton, x, y,
 332                     widestArrowButton, h, 0, HPos.CENTER, VPos.CENTER);
 333 
 334             // textfield in the middle
 335             textField.resizeRelocate(x + widestArrowButton, y, w - (2*widestArrowButton), h);
 336 
 337             // increment is on the right-hand side
 338             incrementArrowButton.resize(widestArrowButton, h);
 339             positionInArea(incrementArrowButton, w - widestArrowButton, y,
 340                     widestArrowButton, h, 0, HPos.CENTER, VPos.CENTER);
 341         }
 342     }
 343 
 344     /** {@inheritDoc} */
 345     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 346         return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
 347     }
 348 
 349     /** {@inheritDoc} */
 350     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 351         final double textfieldWidth = textField.prefWidth(height);
 352         return leftInset + textfieldWidth + rightInset;
 353     }
 354 
 355     /** {@inheritDoc} */
 356     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 357         double ph;
 358         double textFieldHeight = textField.prefHeight(width);
 359 
 360         if (layoutMode == SPLIT_ARROWS_VERTICAL) {
 361             ph = topInset + incrementArrowButton.prefHeight(width) +
 362                     textFieldHeight + decrementArrowButton.prefHeight(width) + bottomInset;
 363         } else {
 364             ph = topInset + textFieldHeight + bottomInset;
 365         }
 366 
 367         return ph;
 368     }
 369 
 370     /** {@inheritDoc} */
 371     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 372         return getSkinnable().prefWidth(height);
 373     }
 374 
 375     /** {@inheritDoc} */
 376     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 377         return getSkinnable().prefHeight(width);
 378     }
 379 
 380     // Overridden so that we use the textfield as the baseline, rather than the arrow.
 381     // See RT-30754 for more information.
 382     /** {@inheritDoc} */
 383     @Override protected double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
 384         return textField.getLayoutBounds().getMinY() + textField.getLayoutY() + textField.getBaselineOffset();
 385     }
 386 
 387 
 388 
 389     /***************************************************************************
 390      *                                                                         *
 391      * Private implementation                                                  *
 392      *                                                                         *
 393      **************************************************************************/
 394 
 395     private void updateStyleClass() {
 396         final List<String> styleClass = getSkinnable().getStyleClass();
 397 
 398         if (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL)) {
 399             layoutMode = ARROWS_ON_LEFT_VERTICAL;
 400         } else if (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL)) {
 401             layoutMode = ARROWS_ON_LEFT_HORIZONTAL;
 402         } else if (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL)) {
 403             layoutMode = ARROWS_ON_RIGHT_HORIZONTAL;
 404         } else if (styleClass.contains(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL)) {
 405             layoutMode = SPLIT_ARROWS_VERTICAL;
 406         } else if (styleClass.contains(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL)) {
 407             layoutMode = SPLIT_ARROWS_HORIZONTAL;
 408         } else {
 409             layoutMode = ARROWS_ON_RIGHT_VERTICAL;
 410         }
 411     }
 412 
 413 
 414 
 415     /***************************************************************************
 416      *                                                                         *
 417      * Stylesheet Handling                                                     *
 418      *                                                                         *
 419      **************************************************************************/
 420 
 421     private static PseudoClass CONTAINS_FOCUS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("contains-focus");
 422 }