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 java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31
32 import com.sun.javafx.scene.traversal.Algorithm;
33 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
34 import com.sun.javafx.scene.traversal.TraversalContext;
35
36 import javafx.beans.property.ObjectProperty;
37 import javafx.beans.property.DoubleProperty;
38 import javafx.beans.value.WritableValue;
39 import javafx.collections.FXCollections;
40 import javafx.collections.ListChangeListener;
41 import javafx.collections.ObservableList;
42 import javafx.geometry.HPos;
43 import javafx.geometry.Orientation;
44 import javafx.geometry.Pos;
45 import javafx.geometry.Side;
46 import javafx.geometry.VPos;
47 import javafx.scene.AccessibleAction;
48 import javafx.scene.AccessibleAttribute;
49 import javafx.scene.AccessibleRole;
50 import javafx.scene.Node;
51 import javafx.scene.Parent;
52 import javafx.scene.control.ContextMenu;
53 import javafx.scene.control.MenuItem;
54 import javafx.scene.control.CustomMenuItem;
55 import javafx.scene.control.Separator;
56 import javafx.scene.control.SeparatorMenuItem;
57 import javafx.scene.control.SkinBase;
58 import javafx.scene.control.ToolBar;
59 import javafx.scene.input.KeyCode;
60 import javafx.scene.layout.HBox;
61 import javafx.scene.layout.Pane;
62 import javafx.scene.layout.StackPane;
63 import javafx.scene.layout.VBox;
64 import javafx.css.StyleableDoubleProperty;
65 import javafx.css.StyleableObjectProperty;
66 import javafx.css.StyleableProperty;
67 import javafx.css.CssMetaData;
68
69 import com.sun.javafx.css.converters.EnumConverter;
70 import com.sun.javafx.css.converters.SizeConverter;
71 import com.sun.javafx.scene.control.behavior.ToolBarBehavior;
72 import com.sun.javafx.scene.traversal.Direction;
73
74 import javafx.css.Styleable;
75
76 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
77
78 public class ToolBarSkin extends BehaviorSkinBase<ToolBar, ToolBarBehavior> {
79
80 private Pane box;
81 private ToolBarOverflowMenu overflowMenu;
82 private boolean overflow = false;
83 private double previousWidth = 0;
84 private double previousHeight = 0;
85 private double savedPrefWidth = 0;
86 private double savedPrefHeight = 0;
87 private ObservableList<MenuItem> overflowMenuItems;
88 private boolean needsUpdate = false;
89 private final ParentTraversalEngine engine;
90
91 public ToolBarSkin(ToolBar toolbar) {
92 super(toolbar, new ToolBarBehavior(toolbar));
93 overflowMenuItems = FXCollections.observableArrayList();
94 initialize();
95 registerChangeListener(toolbar.orientationProperty(), "ORIENTATION");
96
97 engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() {
98
99 private Node selectPrev(int from, TraversalContext context) {
100 for (int i = from; i >= 0; --i) {
101 Node n = box.getChildren().get(i);
102 if (n.isDisabled() || !n.impl_isTreeVisible()) continue;
103 if (n instanceof Parent) {
104 Node selected = context.selectLastInParent((Parent)n);
105 if (selected != null) return selected;
106 }
107 if (n.isFocusTraversable() ) {
108 return n;
109 }
110 }
111 return null;
112 }
113
114 private Node selectNext(int from, TraversalContext context) {
115 for (int i = from, max = box.getChildren().size(); i < max; ++i) {
171 @Override
172 public Node selectFirst(TraversalContext context) {
173 Node selected = selectNext(0, context);
174 if (selected != null) return selected;
175 if (overflow) {
176 return overflowMenu;
177 }
178 return null;
179 }
180
181 @Override
182 public Node selectLast(TraversalContext context) {
183 if (overflow) {
184 return overflowMenu;
185 }
186 return selectPrev(box.getChildren().size() - 1, context);
187 }
188 });
189 getSkinnable().setImpl_traversalEngine(engine);
190
191 toolbar.focusedProperty().addListener((observable, oldValue, newValue) -> {
192 if (newValue) {
193 // TODO need to detect the focus direction
194 // to selected the first control in the toolbar when TAB is pressed
195 // or select the last control in the toolbar when SHIFT TAB is pressed.
196 if (!box.getChildren().isEmpty()) {
197 box.getChildren().get(0).requestFocus();
198 } else {
199 overflowMenu.requestFocus();
200 }
201 }
202 });
203
204 toolbar.getItems().addListener((ListChangeListener<Node>) c -> {
205 while (c.next()) {
206 for (Node n: c.getRemoved()) {
207 box.getChildren().remove(n);
208 }
209 box.getChildren().addAll(c.getAddedSubList());
210 }
211 needsUpdate = true;
212 getSkinnable().requestLayout();
213 });
214 }
215
216 private DoubleProperty spacing;
217 public final void setSpacing(double value) {
218 spacingProperty().set(snapSpace(value));
219 }
220
221 public final double getSpacing() {
222 return spacing == null ? 0.0 : snapSpace(spacing.get());
223 }
224
225 public final DoubleProperty spacingProperty() {
226 if (spacing == null) {
227 spacing = new StyleableDoubleProperty() {
228
229 @Override
230 protected void invalidated() {
231 final double value = get();
232 if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
233 ((VBox)box).setSpacing(value);
234 } else {
235 ((HBox)box).setSpacing(value);
236 }
237 }
238
239 @Override
240 public Object getBean() {
241 return ToolBarSkin.this;
242 }
243
244 @Override
245 public String getName() {
246 return "spacing";
247 }
248
249 @Override
250 public CssMetaData<ToolBar,Number> getCssMetaData() {
251 return StyleableProperties.SPACING;
252 }
253 };
254 }
255 return spacing;
256 }
257
258 private ObjectProperty<Pos> boxAlignment;
259 public final void setBoxAlignment(Pos value) {
260 boxAlignmentProperty().set(value);
261 }
262
263 public final Pos getBoxAlignment() {
264 return boxAlignment == null ? Pos.TOP_LEFT : boxAlignment.get();
265 }
266
267 public final ObjectProperty<Pos> boxAlignmentProperty() {
268 if (boxAlignment == null) {
269 boxAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
270
271 @Override
272 public void invalidated() {
273 final Pos value = get();
274 if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
275 ((VBox)box).setAlignment(value);
276 } else {
277 ((HBox)box).setAlignment(value);
278 }
279 }
280
281 @Override
282 public Object getBean() {
283 return ToolBarSkin.this;
284 }
285
286 @Override
287 public String getName() {
288 return "boxAlignment";
289 }
290
291 @Override
292 public CssMetaData<ToolBar,Pos> getCssMetaData() {
293 return StyleableProperties.ALIGNMENT;
294 }
295 };
296 }
297 return boxAlignment;
298 }
299
300 @Override protected void handleControlPropertyChanged(String property) {
301 super.handleControlPropertyChanged(property);
302 if ("ORIENTATION".equals(property)) {
303 initialize();
304 }
305 }
306
307 @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
308 final ToolBar toolbar = getSkinnable();
309 return toolbar.getOrientation() == Orientation.VERTICAL ?
310 computePrefWidth(-1, topInset, rightInset, bottomInset, leftInset) :
311 snapSize(overflowMenu.prefWidth(-1)) + leftInset + rightInset;
312 }
313
314 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
315 final ToolBar toolbar = getSkinnable();
316 return toolbar.getOrientation() == Orientation.VERTICAL?
317 snapSize(overflowMenu.prefHeight(-1)) + topInset + bottomInset :
318 computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset);
319 }
320
321 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
322 double prefWidth = 0;
323 final ToolBar toolbar = getSkinnable();
324
325 if (toolbar.getOrientation() == Orientation.HORIZONTAL) {
326 for (Node node : toolbar.getItems()) {
327 prefWidth += snapSize(node.prefWidth(-1)) + getSpacing();
328 }
329 prefWidth -= getSpacing();
330 } else {
331 for (Node node : toolbar.getItems()) {
332 prefWidth = Math.max(prefWidth, snapSize(node.prefWidth(-1)));
333 }
334 if (toolbar.getItems().size() > 0) {
335 savedPrefWidth = prefWidth;
336 } else {
337 prefWidth = savedPrefWidth;
338 }
339 }
340 return leftInset + prefWidth + rightInset;
341 }
342
343 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
344 double prefHeight = 0;
345 final ToolBar toolbar = getSkinnable();
346
347 if(toolbar.getOrientation() == Orientation.VERTICAL) {
348 for (Node node: toolbar.getItems()) {
349 prefHeight += snapSize(node.prefHeight(-1)) + getSpacing();
350 }
351 prefHeight -= getSpacing();
352 } else {
353 for (Node node : toolbar.getItems()) {
354 prefHeight = Math.max(prefHeight, snapSize(node.prefHeight(-1)));
355 }
356 if (toolbar.getItems().size() > 0) {
357 savedPrefHeight = prefHeight;
358 } else {
359 prefHeight = savedPrefHeight;
360 }
361 }
362 return topInset + prefHeight + bottomInset;
363 }
364
365 @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
366 return getSkinnable().getOrientation() == Orientation.VERTICAL ?
367 snapSize(getSkinnable().prefWidth(-1)) : Double.MAX_VALUE;
368 }
369
370 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
371 return getSkinnable().getOrientation() == Orientation.VERTICAL ?
372 Double.MAX_VALUE : snapSize(getSkinnable().prefHeight(-1));
373 }
374
375 @Override protected void layoutChildren(final double x,final double y,
376 final double w, final double h) {
377 // super.layoutChildren();
378 final ToolBar toolbar = getSkinnable();
379
380 if (toolbar.getOrientation() == Orientation.VERTICAL) {
381 if (snapSize(toolbar.getHeight()) != previousHeight || needsUpdate) {
382 ((VBox)box).setSpacing(getSpacing());
383 ((VBox)box).setAlignment(getBoxAlignment());
384 previousHeight = snapSize(toolbar.getHeight());
385 addNodesToToolBar();
386 }
387 } else {
388 if (snapSize(toolbar.getWidth()) != previousWidth || needsUpdate) {
389 ((HBox)box).setSpacing(getSpacing());
390 ((HBox)box).setAlignment(getBoxAlignment());
391 previousWidth = snapSize(toolbar.getWidth());
392 addNodesToToolBar();
393 }
394 }
438 toolbarHeight = savedPrefHeight;
439 }
440 VPos pos = ((HBox)box).getAlignment().getVpos();
441 if (VPos.TOP.equals(pos)) {
442 overflowY = y +
443 Math.abs((toolbarHeight - overflowMenuHeight)/2);
444 } else if (VPos.BOTTOM.equals(pos)) {
445 overflowY = (snapSize(toolbar.getHeight()) - snappedBottomInset() - toolbarHeight) +
446 Math.abs((toolbarHeight - overflowMenuHeight)/2);
447 } else {
448 overflowY = y + Math.abs((toolbarHeight - overflowMenuHeight)/2);
449 }
450 overflowX = snapSize(toolbar.getWidth()) - overflowMenuWidth - snappedRightInset();
451 }
452 overflowMenu.resize(overflowMenuWidth, overflowMenuHeight);
453 positionInArea(overflowMenu, overflowX, overflowY, overflowMenuWidth, overflowMenuHeight, /*baseline ignored*/0,
454 HPos.CENTER, VPos.CENTER);
455 }
456 }
457
458 private void initialize() {
459 if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
460 box = new VBox();
461 } else {
462 box = new HBox();
463 }
464 box.getStyleClass().add("container");
465 box.getChildren().addAll(getSkinnable().getItems());
466 overflowMenu = new ToolBarOverflowMenu(overflowMenuItems);
467 overflowMenu.setVisible(false);
468 overflowMenu.setManaged(false);
469
470 getChildren().clear();
471 getChildren().add(box);
472 getChildren().add(overflowMenu);
473
474 previousWidth = 0;
475 previousHeight = 0;
476 savedPrefWidth = 0;
477 savedPrefHeight = 0;
579 break;
580 }
581
582 overflowMenuItems.add(customMenuItem);
583 }
584 }
585 }
586
587 // Check if we overflowed.
588 overflow = overflowMenuItems.size() > 0;
589 if (!overflow && overflowMenu.isFocused()) {
590 Node last = engine.selectLast();
591 if (last != null) {
592 last.requestFocus();
593 }
594 }
595 overflowMenu.setVisible(overflow);
596 overflowMenu.setManaged(overflow);
597 }
598
599 class ToolBarOverflowMenu extends StackPane {
600 private StackPane downArrow;
601 private ContextMenu popup;
602 private ObservableList<MenuItem> menuItems;
603
604 public ToolBarOverflowMenu(ObservableList<MenuItem> items) {
605 getStyleClass().setAll("tool-bar-overflow-button");
606 setAccessibleRole(AccessibleRole.BUTTON);
607 setAccessibleText(getString("Accessibility.title.ToolBar.OverflowButton"));
608 setFocusTraversable(true);
609 this.menuItems = items;
610 downArrow = new StackPane();
611 downArrow.getStyleClass().setAll("arrow");
612 downArrow.setOnMousePressed(me -> {
613 fire();
614 });
615
616 setOnKeyPressed(ke -> {
617 if (KeyCode.SPACE.equals(ke.getCode())) {
618 if (!popup.isShowing()) {
741 final List<CssMetaData<? extends Styleable, ?>> styleables =
742 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
743
744 // StackPane also has -fx-alignment. Replace it with
745 // ToolBarSkin's.
746 // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT
747 final String alignmentProperty = ALIGNMENT.getProperty();
748 for (int n=0, nMax=styleables.size(); n<nMax; n++) {
749 final CssMetaData<?,?> prop = styleables.get(n);
750 if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop);
751 }
752
753 styleables.add(SPACING);
754 styleables.add(ALIGNMENT);
755 STYLEABLES = Collections.unmodifiableList(styleables);
756
757 }
758 }
759
760 /**
761 * @return The CssMetaData associated with this class, which may include the
762 * CssMetaData of its super classes.
763 */
764 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
765 return StyleableProperties.STYLEABLES;
766 }
767
768 /**
769 * {@inheritDoc}
770 */
771 @Override
772 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
773 return getClassCssMetaData();
774 }
775
776 @Override
777 protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
778 switch (attribute) {
779 case OVERFLOW_BUTTON: return overflowMenu;
780 default: return super.queryAccessibleAttribute(attribute, parameters);
781 }
|
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 java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31
32 import com.sun.javafx.scene.control.behavior.BehaviorBase;
33 import com.sun.javafx.scene.traversal.Algorithm;
34 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
35 import com.sun.javafx.scene.traversal.TraversalContext;
36
37 import javafx.beans.property.ObjectProperty;
38 import javafx.beans.property.DoubleProperty;
39 import javafx.beans.value.WritableValue;
40 import javafx.collections.FXCollections;
41 import javafx.collections.ListChangeListener;
42 import javafx.collections.ObservableList;
43 import javafx.geometry.HPos;
44 import javafx.geometry.Orientation;
45 import javafx.geometry.Pos;
46 import javafx.geometry.Side;
47 import javafx.geometry.VPos;
48 import javafx.scene.AccessibleAction;
49 import javafx.scene.AccessibleAttribute;
50 import javafx.scene.AccessibleRole;
51 import javafx.scene.Node;
52 import javafx.scene.Parent;
53 import javafx.scene.control.ContextMenu;
54 import javafx.scene.control.Control;
55 import javafx.scene.control.MenuItem;
56 import javafx.scene.control.CustomMenuItem;
57 import javafx.scene.control.Separator;
58 import javafx.scene.control.SeparatorMenuItem;
59 import javafx.scene.control.SkinBase;
60 import javafx.scene.control.ToolBar;
61 import javafx.scene.input.KeyCode;
62 import javafx.scene.layout.HBox;
63 import javafx.scene.layout.Pane;
64 import javafx.scene.layout.StackPane;
65 import javafx.scene.layout.VBox;
66 import javafx.css.StyleableDoubleProperty;
67 import javafx.css.StyleableObjectProperty;
68 import javafx.css.StyleableProperty;
69 import javafx.css.CssMetaData;
70
71 import javafx.css.converter.EnumConverter;
72 import javafx.css.converter.SizeConverter;
73 import com.sun.javafx.scene.control.behavior.ToolBarBehavior;
74 import com.sun.javafx.scene.traversal.Direction;
75
76 import javafx.css.Styleable;
77
78 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
79
80 /**
81 * Default skin implementation for the {@link ToolBar} control.
82 *
83 * @see ToolBar
84 * @since 9
85 */
86 public class ToolBarSkin extends SkinBase<ToolBar> {
87
88 /***************************************************************************
89 * *
90 * Private fields *
91 * *
92 **************************************************************************/
93
94 private Pane box;
95 private ToolBarOverflowMenu overflowMenu;
96 private boolean overflow = false;
97 private double previousWidth = 0;
98 private double previousHeight = 0;
99 private double savedPrefWidth = 0;
100 private double savedPrefHeight = 0;
101 private ObservableList<MenuItem> overflowMenuItems;
102 private boolean needsUpdate = false;
103 private final ParentTraversalEngine engine;
104 private final BehaviorBase<ToolBar> behavior;
105
106
107
108 /***************************************************************************
109 * *
110 * Constructors *
111 * *
112 **************************************************************************/
113
114 /**
115 * Creates a new ToolBarSkin instance, installing the necessary child
116 * nodes into the Control {@link Control#getChildren() children} list, as
117 * well as the necessary input mappings for handling key, mouse, etc events.
118 *
119 * @param control The control that this skin should be installed onto.
120 */
121 public ToolBarSkin(ToolBar control) {
122 super(control);
123
124 // install default input map for the ToolBar control
125 behavior = new ToolBarBehavior(control);
126 // control.setInputMap(behavior.getInputMap());
127
128 overflowMenuItems = FXCollections.observableArrayList();
129 initialize();
130 registerChangeListener(control.orientationProperty(), e -> initialize());
131
132 engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() {
133
134 private Node selectPrev(int from, TraversalContext context) {
135 for (int i = from; i >= 0; --i) {
136 Node n = box.getChildren().get(i);
137 if (n.isDisabled() || !n.impl_isTreeVisible()) continue;
138 if (n instanceof Parent) {
139 Node selected = context.selectLastInParent((Parent)n);
140 if (selected != null) return selected;
141 }
142 if (n.isFocusTraversable() ) {
143 return n;
144 }
145 }
146 return null;
147 }
148
149 private Node selectNext(int from, TraversalContext context) {
150 for (int i = from, max = box.getChildren().size(); i < max; ++i) {
206 @Override
207 public Node selectFirst(TraversalContext context) {
208 Node selected = selectNext(0, context);
209 if (selected != null) return selected;
210 if (overflow) {
211 return overflowMenu;
212 }
213 return null;
214 }
215
216 @Override
217 public Node selectLast(TraversalContext context) {
218 if (overflow) {
219 return overflowMenu;
220 }
221 return selectPrev(box.getChildren().size() - 1, context);
222 }
223 });
224 getSkinnable().setImpl_traversalEngine(engine);
225
226 control.focusedProperty().addListener((observable, oldValue, newValue) -> {
227 if (newValue) {
228 // TODO need to detect the focus direction
229 // to selected the first control in the toolbar when TAB is pressed
230 // or select the last control in the toolbar when SHIFT TAB is pressed.
231 if (!box.getChildren().isEmpty()) {
232 box.getChildren().get(0).requestFocus();
233 } else {
234 overflowMenu.requestFocus();
235 }
236 }
237 });
238
239 control.getItems().addListener((ListChangeListener<Node>) c -> {
240 while (c.next()) {
241 for (Node n: c.getRemoved()) {
242 box.getChildren().remove(n);
243 }
244 box.getChildren().addAll(c.getAddedSubList());
245 }
246 needsUpdate = true;
247 getSkinnable().requestLayout();
248 });
249 }
250
251
252
253 /***************************************************************************
254 * *
255 * Properties *
256 * *
257 **************************************************************************/
258
259 // --- spacing
260 private DoubleProperty spacing;
261 private final void setSpacing(double value) {
262 spacingProperty().set(snapSpace(value));
263 }
264
265 private final double getSpacing() {
266 return spacing == null ? 0.0 : snapSpace(spacing.get());
267 }
268
269 private final DoubleProperty spacingProperty() {
270 if (spacing == null) {
271 spacing = new StyleableDoubleProperty() {
272
273 @Override
274 protected void invalidated() {
275 final double value = get();
276 if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
277 ((VBox)box).setSpacing(value);
278 } else {
279 ((HBox)box).setSpacing(value);
280 }
281 }
282
283 @Override
284 public Object getBean() {
285 return ToolBarSkin.this;
286 }
287
288 @Override
289 public String getName() {
290 return "spacing";
291 }
292
293 @Override
294 public CssMetaData<ToolBar,Number> getCssMetaData() {
295 return StyleableProperties.SPACING;
296 }
297 };
298 }
299 return spacing;
300 }
301
302 // --- box alignment
303 private ObjectProperty<Pos> boxAlignment;
304 private final void setBoxAlignment(Pos value) {
305 boxAlignmentProperty().set(value);
306 }
307
308 private final Pos getBoxAlignment() {
309 return boxAlignment == null ? Pos.TOP_LEFT : boxAlignment.get();
310 }
311
312 private final ObjectProperty<Pos> boxAlignmentProperty() {
313 if (boxAlignment == null) {
314 boxAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
315
316 @Override
317 public void invalidated() {
318 final Pos value = get();
319 if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
320 ((VBox)box).setAlignment(value);
321 } else {
322 ((HBox)box).setAlignment(value);
323 }
324 }
325
326 @Override
327 public Object getBean() {
328 return ToolBarSkin.this;
329 }
330
331 @Override
332 public String getName() {
333 return "boxAlignment";
334 }
335
336 @Override
337 public CssMetaData<ToolBar,Pos> getCssMetaData() {
338 return StyleableProperties.ALIGNMENT;
339 }
340 };
341 }
342 return boxAlignment;
343 }
344
345
346
347 /***************************************************************************
348 * *
349 * Public API *
350 * *
351 **************************************************************************/
352
353 /** {@inheritDoc} */
354 @Override public void dispose() {
355 super.dispose();
356
357 if (behavior != null) {
358 behavior.dispose();
359 }
360 }
361
362 /** {@inheritDoc} */
363 @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
364 final ToolBar toolbar = getSkinnable();
365 return toolbar.getOrientation() == Orientation.VERTICAL ?
366 computePrefWidth(-1, topInset, rightInset, bottomInset, leftInset) :
367 snapSize(overflowMenu.prefWidth(-1)) + leftInset + rightInset;
368 }
369
370 /** {@inheritDoc} */
371 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
372 final ToolBar toolbar = getSkinnable();
373 return toolbar.getOrientation() == Orientation.VERTICAL?
374 snapSize(overflowMenu.prefHeight(-1)) + topInset + bottomInset :
375 computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset);
376 }
377
378 /** {@inheritDoc} */
379 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
380 double prefWidth = 0;
381 final ToolBar toolbar = getSkinnable();
382
383 if (toolbar.getOrientation() == Orientation.HORIZONTAL) {
384 for (Node node : toolbar.getItems()) {
385 prefWidth += snapSize(node.prefWidth(-1)) + getSpacing();
386 }
387 prefWidth -= getSpacing();
388 } else {
389 for (Node node : toolbar.getItems()) {
390 prefWidth = Math.max(prefWidth, snapSize(node.prefWidth(-1)));
391 }
392 if (toolbar.getItems().size() > 0) {
393 savedPrefWidth = prefWidth;
394 } else {
395 prefWidth = savedPrefWidth;
396 }
397 }
398 return leftInset + prefWidth + rightInset;
399 }
400
401 /** {@inheritDoc} */
402 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
403 double prefHeight = 0;
404 final ToolBar toolbar = getSkinnable();
405
406 if(toolbar.getOrientation() == Orientation.VERTICAL) {
407 for (Node node: toolbar.getItems()) {
408 prefHeight += snapSize(node.prefHeight(-1)) + getSpacing();
409 }
410 prefHeight -= getSpacing();
411 } else {
412 for (Node node : toolbar.getItems()) {
413 prefHeight = Math.max(prefHeight, snapSize(node.prefHeight(-1)));
414 }
415 if (toolbar.getItems().size() > 0) {
416 savedPrefHeight = prefHeight;
417 } else {
418 prefHeight = savedPrefHeight;
419 }
420 }
421 return topInset + prefHeight + bottomInset;
422 }
423
424 /** {@inheritDoc} */
425 @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
426 return getSkinnable().getOrientation() == Orientation.VERTICAL ?
427 snapSize(getSkinnable().prefWidth(-1)) : Double.MAX_VALUE;
428 }
429
430 /** {@inheritDoc} */
431 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
432 return getSkinnable().getOrientation() == Orientation.VERTICAL ?
433 Double.MAX_VALUE : snapSize(getSkinnable().prefHeight(-1));
434 }
435
436 /** {@inheritDoc} */
437 @Override protected void layoutChildren(final double x,final double y,
438 final double w, final double h) {
439 // super.layoutChildren();
440 final ToolBar toolbar = getSkinnable();
441
442 if (toolbar.getOrientation() == Orientation.VERTICAL) {
443 if (snapSize(toolbar.getHeight()) != previousHeight || needsUpdate) {
444 ((VBox)box).setSpacing(getSpacing());
445 ((VBox)box).setAlignment(getBoxAlignment());
446 previousHeight = snapSize(toolbar.getHeight());
447 addNodesToToolBar();
448 }
449 } else {
450 if (snapSize(toolbar.getWidth()) != previousWidth || needsUpdate) {
451 ((HBox)box).setSpacing(getSpacing());
452 ((HBox)box).setAlignment(getBoxAlignment());
453 previousWidth = snapSize(toolbar.getWidth());
454 addNodesToToolBar();
455 }
456 }
500 toolbarHeight = savedPrefHeight;
501 }
502 VPos pos = ((HBox)box).getAlignment().getVpos();
503 if (VPos.TOP.equals(pos)) {
504 overflowY = y +
505 Math.abs((toolbarHeight - overflowMenuHeight)/2);
506 } else if (VPos.BOTTOM.equals(pos)) {
507 overflowY = (snapSize(toolbar.getHeight()) - snappedBottomInset() - toolbarHeight) +
508 Math.abs((toolbarHeight - overflowMenuHeight)/2);
509 } else {
510 overflowY = y + Math.abs((toolbarHeight - overflowMenuHeight)/2);
511 }
512 overflowX = snapSize(toolbar.getWidth()) - overflowMenuWidth - snappedRightInset();
513 }
514 overflowMenu.resize(overflowMenuWidth, overflowMenuHeight);
515 positionInArea(overflowMenu, overflowX, overflowY, overflowMenuWidth, overflowMenuHeight, /*baseline ignored*/0,
516 HPos.CENTER, VPos.CENTER);
517 }
518 }
519
520
521
522 /***************************************************************************
523 * *
524 * Private implementation *
525 * *
526 **************************************************************************/
527
528 private void initialize() {
529 if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
530 box = new VBox();
531 } else {
532 box = new HBox();
533 }
534 box.getStyleClass().add("container");
535 box.getChildren().addAll(getSkinnable().getItems());
536 overflowMenu = new ToolBarOverflowMenu(overflowMenuItems);
537 overflowMenu.setVisible(false);
538 overflowMenu.setManaged(false);
539
540 getChildren().clear();
541 getChildren().add(box);
542 getChildren().add(overflowMenu);
543
544 previousWidth = 0;
545 previousHeight = 0;
546 savedPrefWidth = 0;
547 savedPrefHeight = 0;
649 break;
650 }
651
652 overflowMenuItems.add(customMenuItem);
653 }
654 }
655 }
656
657 // Check if we overflowed.
658 overflow = overflowMenuItems.size() > 0;
659 if (!overflow && overflowMenu.isFocused()) {
660 Node last = engine.selectLast();
661 if (last != null) {
662 last.requestFocus();
663 }
664 }
665 overflowMenu.setVisible(overflow);
666 overflowMenu.setManaged(overflow);
667 }
668
669
670
671 /***************************************************************************
672 * *
673 * Support classes *
674 * *
675 **************************************************************************/
676
677 class ToolBarOverflowMenu extends StackPane {
678 private StackPane downArrow;
679 private ContextMenu popup;
680 private ObservableList<MenuItem> menuItems;
681
682 public ToolBarOverflowMenu(ObservableList<MenuItem> items) {
683 getStyleClass().setAll("tool-bar-overflow-button");
684 setAccessibleRole(AccessibleRole.BUTTON);
685 setAccessibleText(getString("Accessibility.title.ToolBar.OverflowButton"));
686 setFocusTraversable(true);
687 this.menuItems = items;
688 downArrow = new StackPane();
689 downArrow.getStyleClass().setAll("arrow");
690 downArrow.setOnMousePressed(me -> {
691 fire();
692 });
693
694 setOnKeyPressed(ke -> {
695 if (KeyCode.SPACE.equals(ke.getCode())) {
696 if (!popup.isShowing()) {
819 final List<CssMetaData<? extends Styleable, ?>> styleables =
820 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
821
822 // StackPane also has -fx-alignment. Replace it with
823 // ToolBarSkin's.
824 // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT
825 final String alignmentProperty = ALIGNMENT.getProperty();
826 for (int n=0, nMax=styleables.size(); n<nMax; n++) {
827 final CssMetaData<?,?> prop = styleables.get(n);
828 if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop);
829 }
830
831 styleables.add(SPACING);
832 styleables.add(ALIGNMENT);
833 STYLEABLES = Collections.unmodifiableList(styleables);
834
835 }
836 }
837
838 /**
839 * Returns the CssMetaData associated with this class, which may include the
840 * CssMetaData of its super classes.
841 */
842 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
843 return StyleableProperties.STYLEABLES;
844 }
845
846 /**
847 * {@inheritDoc}
848 */
849 @Override
850 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
851 return getClassCssMetaData();
852 }
853
854 @Override
855 protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
856 switch (attribute) {
857 case OVERFLOW_BUTTON: return overflowMenu;
858 default: return super.queryAccessibleAttribute(attribute, parameters);
859 }
|