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 }
|