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