/* * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package javafx.scene.control; import javafx.css.CssMetaData; import javafx.css.PseudoClass; import java.util.Collections; import java.util.List; import javafx.collections.ObservableList; import javafx.css.Styleable; import javafx.event.EventHandler; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.VPos; import javafx.scene.AccessibleAction; import javafx.scene.AccessibleAttribute; import javafx.scene.Node; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; /** * Base implementation class for defining the visual representation of user * interface controls by defining a scene graph of nodes to represent the * {@link Skin skin}. * A user interface control is abstracted behind the {@link Skinnable} interface. * * @since JavaFX 8.0 */ public abstract class SkinBase implements Skin { /*************************************************************************** * * * Private fields * * * **************************************************************************/ /** * The {@code Control} that is referencing this Skin. There is a * one-to-one relationship between a {@code Skin} and a {@code Control}. * When a {@code Skin} is set on a {@code Control}, this variable is * automatically updated. */ private C control; /** * A local field that directly refers to the children list inside the Control. */ private ObservableList children; /*************************************************************************** * * * Event Handlers / Listeners * * * **************************************************************************/ /** * Mouse handler used for consuming all mouse events (preventing them * from bubbling up to parent) */ private static final EventHandler mouseEventConsumer = event -> { /* ** we used to consume mouse wheel rotations here, ** be we've switched to ScrollEvents, and only consume those which we use. ** See RT-13995 & RT-14480 */ event.consume(); }; /*************************************************************************** * * * Constructor * * * **************************************************************************/ /** * Constructor for all SkinBase instances. * * @param control The control for which this Skin should attach to. */ protected SkinBase(final C control) { if (control == null) { throw new IllegalArgumentException("Cannot pass null for control"); } // Update the control and behavior this.control = control; this.children = control.getControlChildren(); // Default behavior for controls is to consume all mouse events consumeMouseEvents(true); } /*************************************************************************** * * * Public API (from Skin) * * * **************************************************************************/ /** {@inheritDoc} */ @Override public final C getSkinnable() { return control; } /** {@inheritDoc} */ @Override public final Node getNode() { return control; } /** {@inheritDoc} */ @Override public void dispose() { // control.removeEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler); this.control = null; } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** * Returns the children of the skin. */ public final ObservableList getChildren() { return children; } /** * Called during the layout pass of the scenegraph. */ protected void layoutChildren(final double contentX, final double contentY, final double contentWidth, final double contentHeight) { // By default simply sizes all managed children to fit within the space provided for (int i=0, max=children.size(); i Control forwarding API * * * **************************************************************************/ /** * Utility method to get the top inset which includes padding and border * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. * * @return Rounded up insets top */ protected double snappedTopInset() { return control.snappedTopInset(); } /** * Utility method to get the bottom inset which includes padding and border * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. * * @return Rounded up insets bottom */ protected double snappedBottomInset() { return control.snappedBottomInset(); } /** * Utility method to get the left inset which includes padding and border * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. * * @return Rounded up insets left */ protected double snappedLeftInset() { return control.snappedLeftInset(); } /** * Utility method to get the right inset which includes padding and border * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. * * @return Rounded up insets right */ protected double snappedRightInset() { return control.snappedRightInset(); } /** * If this region's snapToPixel property is true, returns a value rounded * to the nearest pixel, else returns the same value. * @param value the space value to be snapped * @return value rounded to nearest pixel */ protected double snapSpace(double value) { return control.isSnapToPixel() ? Math.round(value) : value; } /** * If this region's snapToPixel property is true, returns a value ceiled * to the nearest pixel, else returns the same value. * @param value the size value to be snapped * @return value ceiled to nearest pixel */ protected double snapSize(double value) { return control.isSnapToPixel() ? Math.ceil(value) : value; } /** * If this region's snapToPixel property is true, returns a value rounded * to the nearest pixel, else returns the same value. * @param value the position value to be snapped * @return value rounded to nearest pixel */ protected double snapPosition(double value) { return control.isSnapToPixel() ? Math.round(value) : value; } /** * Utility method which positions the child within an area of this * skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, * with a baseline offset relative to that area. *

* This function does not resize the node and uses the node's layout bounds * width and height to determine how it should be positioned within the area. *

* If the vertical alignment is {@code VPos.BASELINE} then it * will position the node so that its own baseline aligns with the passed in * {@code baselineOffset}, otherwise the baseline parameter is ignored. *

* If {@code snapToPixel} is {@code true} for this skin, then the x/y position * values will be rounded to their nearest pixel boundaries. * * @param child the child being positioned within this skin * @param areaX the horizontal offset of the layout area relative to this skin * @param areaY the vertical offset of the layout area relative to this skin * @param areaWidth the width of the layout area * @param areaHeight the height of the layout area * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE * @param halignment the horizontal alignment for the child within the area * @param valignment the vertical alignment for the child within the area * */ protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, double areaBaselineOffset, HPos halignment, VPos valignment) { positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, Insets.EMPTY, halignment, valignment); } /** * Utility method which positions the child within an area of this * skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, * with a baseline offset relative to that area. *

* This function does not resize the node and uses the node's layout bounds * width and height to determine how it should be positioned within the area. *

* If the vertical alignment is {@code VPos.BASELINE} then it * will position the node so that its own baseline aligns with the passed in * {@code baselineOffset}, otherwise the baseline parameter is ignored. *

* If {@code snapToPixel} is {@code true} for this skin, then the x/y position * values will be rounded to their nearest pixel boundaries. *

* If {@code margin} is non-null, then that space will be allocated around the * child within the layout area. margin may be null. * * @param child the child being positioned within this skin * @param areaX the horizontal offset of the layout area relative to this skin * @param areaY the vertical offset of the layout area relative to this skin * @param areaWidth the width of the layout area * @param areaHeight the height of the layout area * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE * @param margin the margin of space to be allocated around the child * @param halignment the horizontal alignment for the child within the area * @param valignment the vertical alignment for the child within the area * * @since JavaFX 8.0 */ protected void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment) { Region.positionInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, halignment, valignment, control.isSnapToPixel()); } /** * Utility method which lays out the child within an area of this * skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, * with a baseline offset relative to that area. *

* If the child is resizable, this method will resize it to fill the specified * area unless the node's maximum size prevents it. If the node's maximum * size preference is less than the area size, the maximum size will be used. * If node's maximum is greater than the area size, then the node will be * resized to fit within the area, unless its minimum size prevents it. *

* If the child has a non-null contentBias, then this method will use it when * resizing the child. If the contentBias is horizontal, it will set its width * first to the area's width (up to the child's max width limit) and then pass * that value to compute the child's height. If child's contentBias is vertical, * then it will set its height to the area height (up to child's max height limit) * and pass that height to compute the child's width. If the child's contentBias * is null, then it's width and height have no dependencies on each other. *

* If the child is not resizable (Shape, Group, etc) then it will only be * positioned and not resized. *

* If the child's resulting size differs from the area's size (either * because it was not resizable or it's sizing preferences prevented it), then * this function will align the node relative to the area using horizontal and * vertical alignment values. * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned * with the area baseline offset parameter, otherwise the baseline parameter * is ignored. *

* If {@code snapToPixel} is {@code true} for this skin, then the resulting x,y * values will be rounded to their nearest pixel boundaries and the * width/height values will be ceiled to the next pixel boundary. * * @param child the child being positioned within this skin * @param areaX the horizontal offset of the layout area relative to this skin * @param areaY the vertical offset of the layout area relative to this skin * @param areaWidth the width of the layout area * @param areaHeight the height of the layout area * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE * @param halignment the horizontal alignment for the child within the area * @param valignment the vertical alignment for the child within the area * */ protected void layoutInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, double areaBaselineOffset, HPos halignment, VPos valignment) { layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, Insets.EMPTY, true, true, halignment, valignment); } /** * Utility method which lays out the child within an area of this * skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, * with a baseline offset relative to that area. *

* If the child is resizable, this method will resize it to fill the specified * area unless the node's maximum size prevents it. If the node's maximum * size preference is less than the area size, the maximum size will be used. * If node's maximum is greater than the area size, then the node will be * resized to fit within the area, unless its minimum size prevents it. *

* If the child has a non-null contentBias, then this method will use it when * resizing the child. If the contentBias is horizontal, it will set its width * first to the area's width (up to the child's max width limit) and then pass * that value to compute the child's height. If child's contentBias is vertical, * then it will set its height to the area height (up to child's max height limit) * and pass that height to compute the child's width. If the child's contentBias * is null, then it's width and height have no dependencies on each other. *

* If the child is not resizable (Shape, Group, etc) then it will only be * positioned and not resized. *

* If the child's resulting size differs from the area's size (either * because it was not resizable or it's sizing preferences prevented it), then * this function will align the node relative to the area using horizontal and * vertical alignment values. * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned * with the area baseline offset parameter, otherwise the baseline parameter * is ignored. *

* If {@code margin} is non-null, then that space will be allocated around the * child within the layout area. margin may be null. *

* If {@code snapToPixel} is {@code true} for this skin, then the resulting x,y * values will be rounded to their nearest pixel boundaries and the * width/height values will be ceiled to the next pixel boundary. * * @param child the child being positioned within this skin * @param areaX the horizontal offset of the layout area relative to this skin * @param areaY the vertical offset of the layout area relative to this skin * @param areaWidth the width of the layout area * @param areaHeight the height of the layout area * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE * @param margin the margin of space to be allocated around the child * @param halignment the horizontal alignment for the child within the area * @param valignment the vertical alignment for the child within the area */ protected void layoutInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment) { layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, true, true, halignment, valignment); } /** * Utility method which lays out the child within an area of this * skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight}, * with a baseline offset relative to that area. *

* If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight} * to determine whether to resize it to fill the area or keep the child at its * preferred dimension. If fillWidth/fillHeight are true, then this method * will only resize the child up to its max size limits. If the node's maximum * size preference is less than the area size, the maximum size will be used. * If node's maximum is greater than the area size, then the node will be * resized to fit within the area, unless its minimum size prevents it. *

* If the child has a non-null contentBias, then this method will use it when * resizing the child. If the contentBias is horizontal, it will set its width * first and then pass that value to compute the child's height. If child's * contentBias is vertical, then it will set its height first * and pass that value to compute the child's width. If the child's contentBias * is null, then it's width and height have no dependencies on each other. *

* If the child is not resizable (Shape, Group, etc) then it will only be * positioned and not resized. *

* If the child's resulting size differs from the area's size (either * because it was not resizable or it's sizing preferences prevented it), then * this function will align the node relative to the area using horizontal and * vertical alignment values. * If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned * with the area baseline offset parameter, otherwise the baseline parameter * is ignored. *

* If {@code margin} is non-null, then that space will be allocated around the * child within the layout area. margin may be null. *

* If {@code snapToPixel} is {@code true} for this skin, then the resulting x,y * values will be rounded to their nearest pixel boundaries and the * width/height values will be ceiled to the next pixel boundary. * * @param child the child being positioned within this skin * @param areaX the horizontal offset of the layout area relative to this skin * @param areaY the vertical offset of the layout area relative to this skin * @param areaWidth the width of the layout area * @param areaHeight the height of the layout area * @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE * @param margin the margin of space to be allocated around the child * @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width * @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height * @param halignment the horizontal alignment for the child within the area * @param valignment the vertical alignment for the child within the area */ protected void layoutInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight, double areaBaselineOffset, Insets margin, boolean fillWidth, boolean fillHeight, HPos halignment, VPos valignment) { Region.layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, margin, fillWidth, fillHeight, halignment, valignment, control.isSnapToPixel()); } /*************************************************************************** * * * Private Implementation * * * **************************************************************************/ /************************************************************************** * * * Specialization of CSS handling code * * * **************************************************************************/ private static class StyleableProperties { private static final List> STYLEABLES; static { STYLEABLES = Collections.unmodifiableList(Control.getClassCssMetaData()); } } /** * @return The CssMetaData associated with this class, which may include the * CssMetaData of its super classes. */ public static List> getClassCssMetaData() { return SkinBase.StyleableProperties.STYLEABLES; } /** * This method should delegate to {@link Node#getClassCssMetaData()} so that * a Node's CssMetaData can be accessed without the need for reflection. * @return The CssMetaData associated with this node, which may include the * CssMetaData of its super classes. */ public List> getCssMetaData() { return getClassCssMetaData(); } /** @see Node#pseudoClassStateChanged */ public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) { Control ctl = getSkinnable(); if (ctl != null) { ctl.pseudoClassStateChanged(pseudoClass, active); } } /*************************************************************************** * * * Accessibility handling * * * **************************************************************************/ /** @see Node#queryAccessibleAttribute */ protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { return null; } /** @see Node#executeAccessibleAction */ protected void executeAccessibleAction(AccessibleAction action, Object... parameters) { } /*************************************************************************** * * * Testing-only API * * * **************************************************************************/ }