/*
* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.control;
import java.lang.ref.
WeakReference;
import java.lang.reflect.
Constructor;
import java.lang.reflect.
InvocationTargetException;
import java.util.
ArrayList;
import java.util.
Collections;
import java.util.
List;
import com.sun.javafx.scene.control.
ControlAcceleratorSupport;
import javafx.application.
Application;
import javafx.beans.property.
ObjectProperty;
import javafx.beans.property.
ObjectPropertyBase;
import javafx.beans.property.
SimpleObjectProperty;
import javafx.beans.property.
StringProperty;
import javafx.beans.value.
WritableValue;
import javafx.collections.
ObservableList;
import javafx.event.
EventHandler;
import javafx.geometry.
Side;
import javafx.scene.
AccessibleAction;
import javafx.scene.
AccessibleAttribute;
import javafx.scene.
Node;
import javafx.scene.input.
ContextMenuEvent;
import javafx.scene.layout.
Region;
import com.sun.javafx.application.
PlatformImpl;
import com.sun.javafx.css.
CssError;
import javafx.css.
CssMetaData;
import com.sun.javafx.css.
StyleManager;
import javafx.css.
StyleableObjectProperty;
import javafx.css.
StyleableStringProperty;
import com.sun.javafx.css.converters.
StringConverter;
import com.sun.javafx.scene.control.
Logging;
import javafx.css.
Styleable;
import javafx.css.
StyleableProperty;
import sun.util.logging.
PlatformLogger;
import sun.util.logging.
PlatformLogger.
Level;
/**
* Base class for all user interface controls. A "Control" is a node in the
* scene graph which can be manipulated by the user. Controls provide
* additional variables and behaviors beyond those of Node to support
* common user interactions in a manner which is consistent and predictable
* for the user.
* <p>
* Additionally, controls support explicit skinning to make it easy to
* leverage the functionality of a control while customizing its appearance.
* <p>
* See specific Control subclasses for information on how to use individual
* types of controls.
* <p> Most controls have their focusTraversable property set to true by default, however
* read-only controls such as {@link Label} and {@link ProgressIndicator}, and some
* controls that are containers {@link ScrollPane} and {@link ToolBar} do not.
* Consult individual control documentation for details.
* @since JavaFX 2.0
*/
public abstract class
Control extends
Region implements
Skinnable {
static {
// Ensures that the default application user agent stylesheet is loaded
if (
Application.
getUserAgentStylesheet() == null) {
PlatformImpl.
setDefaultPlatformUserAgentStylesheet();
}
}
/**
* Utility for loading a class in a manner that will work with multiple
* class loaders, as is typically found in OSGI modular applications.
* In particular, this method will attempt to just load the class
* identified by className. If that fails, it attempts to load the
* class using the current thread's context class loader. If that fails,
* it attempts to use the class loader of the supplied "instance", and
* if it still fails it walks up the class hierarchy of the instance
* and attempts to use the class loader of each class in the super-type
* hierarchy.
*
* @param className The name of the class we want to load
* @param instance An optional instance used to help find the class to load
* @return The class. Cannot return null
* @throws ClassNotFoundException If the class cannot be found using any technique.
*/
private static
Class<?>
loadClass(final
String className, final
Object instance)
throws
ClassNotFoundException
{
try {
// Try just loading the class
return
Class.
forName(
className, false,
Control.class.
getClassLoader());
} catch (
ClassNotFoundException ex) {
// RT-17525 : Use context class loader only if Class.forName fails.
if (
Thread.
currentThread().
getContextClassLoader() != null) {
try {
final
ClassLoader ccl =
Thread.
currentThread().
getContextClassLoader();
return
Class.
forName(
className, false,
ccl);
} catch (
ClassNotFoundException ex2) {
// Do nothing, just fall through
}
}
// RT-14177: Try looking up the class using the class loader of the
// current class, walking up the list of superclasses
// and checking each of them, before bailing and using
// the context class loader.
if (
instance != null) {
Class<?>
currentType =
instance.
getClass();
while (
currentType != null) {
try {
final
ClassLoader loader =
currentType.
getClassLoader();
return
Class.
forName(
className, false,
loader);
} catch (
ClassNotFoundException ex2) {
currentType =
currentType.
getSuperclass();
}
}
}
// We failed to find the class using any of the above means, so we're going
// to just throw the ClassNotFoundException that we caught earlier
throw
ex;
}
}
/***************************************************************************
* *
* Private fields *
* *
**************************************************************************/
private
List<
CssMetaData<? extends
Styleable, ?>>
styleableProperties;
/**
* A private reference directly to the SkinBase instance that is used as the
* Skin for this Control. A Control's Skin doesn't have to be of type
* SkinBase, although 98% of the time or greater it probably will be.
* Because instanceof checks and reading a value from a property are
* not cheap (on interpreters on slower hardware or mobile devices)
* it pays to have a direct reference here to the skinBase. We simply
* need to check this variable -- if it is not null then we know the
* Skin is a SkinBase and this is a direct reference to it. If it is null
* then we know the skin is not a SkinBase and we need to call getSkin().
*/
private
SkinBase<?>
skinBase;
/***************************************************************************
* *
* Event Handlers / Listeners *
* *
**************************************************************************/
/**
* Handles context menu requests by popping up the menu.
* Note that we use this pattern to remove some of the anonymous inner
* classes which we'd otherwise have to create. When lambda expressions
* are supported, we could do it that way instead (or use MethodHandles).
*/
private final static
EventHandler<
ContextMenuEvent>
contextMenuHandler =
event -> {
if (
event.
isConsumed()) return;
// If a context menu was shown, consume the event to prevent multiple context menus
Object source =
event.
getSource();
if (
source instanceof
Control) {
Control c = (
Control)
source;
if (
c.
getContextMenu() != null) {
c.
getContextMenu().
show(
c,
event.
getScreenX(),
event.
getScreenY());
event.
consume();
}
}
};
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- skin
/**
* Skin is responsible for rendering this {@code Control}. From the
* perspective of the {@code Control}, the {@code Skin} is a black box.
* It listens and responds to changes in state in a {@code Control}.
* <p>
* There is a one-to-one relationship between a {@code Control} and its
* {@code Skin}. Every {@code Skin} maintains a back reference to the
* {@code Control} via the {@link Skin#getSkinnable()} method.
* <p>
* A skin may be null.
*/
@
Override public final
ObjectProperty<
Skin<?>>
skinProperty() { return
skin; }
@
Override public final void
setSkin(
Skin<?>
value) {
skinProperty().
set(
value);
}
@
Override public final
Skin<?>
getSkin() { return
skinProperty().
getValue(); }
private
ObjectProperty<
Skin<?>>
skin = new
StyleableObjectProperty<
Skin<?>>() {
// We store a reference to the oldValue so that we can handle
// changes in the skin properly in the case of binding. This is
// only needed because invalidated() does not currently take
// a reference to the old value.
private
Skin<?>
oldValue;
@
Override
//This code is basically a kind of optimization that prevents a Skin that is equal but not instance equal.
//Although it's not kosher from the property perspective (bindings won't pass through set), it should not do any harm.
//But it should be evaluated in the future.
public void
set(
Skin<?>
v) {
if (
v == null
?
oldValue == null
:
oldValue != null &&
v.
getClass().
equals(
oldValue.
getClass()))
return;
super.set(
v);
}
@
Override protected void
invalidated() {
Skin<?>
skin =
get();
// Collect the name of the currently installed skin class. We do this
// so that subsequent updates from CSS to the same skin class will not
// result in reinstalling the skin
currentSkinClassName =
skin == null ? null :
skin.
getClass().
getName();
// if someone calls setSkin, we need to make it look like they
// called set on skinClassName in order to keep CSS from overwriting
// the skin.
skinClassNameProperty().
set(
currentSkinClassName);
// Dispose of the old skin
if (
oldValue != null)
oldValue.
dispose();
// Get the new value, and save it off as the new oldValue
oldValue =
skin;
// Reset skinBase to null - it will be set to the new Skin if it
// is a SkinBase, otherwise it will remain null, as expected
skinBase = null;
// We have two paths, one for "legacy" Skins, and one for
// any Skin which extends from SkinBase. Legacy Skins will
// produce a single node which will be the only child of
// the Control via the getNode() method on the Skin. A
// SkinBase will manipulate the children of the Control
// directly. Further, we maintain a direct reference to
// the skinBase for more optimal updates later.
if (
skin instanceof
SkinBase) {
// record a reference of the skin, if it is a SkinBase, for
// performance reasons
skinBase = (
SkinBase<?>)
skin;
// Note I do not remove any children here, because the
// skin will have already configured all the children
// by the time setSkin has been called. This is because
// our Skin interface was lacking an initialize method (doh!)
// and so the Skin constructor is where it adds listeners
// and so forth. For SkinBase implementations, the
// constructor is also where it will take ownership of
// the children.
} else {
final
Node n =
getSkinNode();
if (
n != null) {
getChildren().
setAll(
n);
} else {
getChildren().
clear();
}
}
// clear out the styleable properties so that the list is rebuilt
// next time they are requested.
styleableProperties = null;
// calling impl_reapplyCSS() as the styleable properties may now
// be different, as we will now be able to return styleable properties
// belonging to the skin. If impl_reapplyCSS() is not called, the
// getCssMetaData() method is never called, so the
// skin properties are never exposed.
impl_reapplyCSS();
// DEBUG: Log that we've changed the skin
final
PlatformLogger logger =
Logging.
getControlsLogger();
if (
logger.
isLoggable(
Level.
FINEST)) {
logger.
finest("Stored skin[" +
getValue() + "] on " + this);
}
}
// This method should be CssMetaData<Control,Skin> getCssMetaData(),
// but SKIN is CssMetaData<Control,String>. This does not matter to
// the CSS code which doesn't care about the actual type. Hence,
// we'll suppress the warnings
@
Override @
SuppressWarnings({"unchecked", "rawtype"})
public
CssMetaData getCssMetaData() {
return
StyleableProperties.
SKIN;
}
@
Override
public
Object getBean() {
return
Control.this;
}
@
Override
public
String getName() {
return "skin";
}
};
// --- tooltip
/**
* The ToolTip for this control.
*/
public final
ObjectProperty<
Tooltip>
tooltipProperty() {
if (
tooltip == null) {
tooltip = new
ObjectPropertyBase<
Tooltip>() {
private
Tooltip old = null;
@
Override protected void
invalidated() {
Tooltip t =
get();
// install / uninstall
if (
t !=
old) {
if (
old != null) {
Tooltip.
uninstall(
Control.this,
old);
}
if (
t != null) {
Tooltip.
install(
Control.this,
t);
}
old =
t;
}
}
@
Override
public
Object getBean() {
return
Control.this;
}
@
Override
public
String getName() {
return "tooltip";
}
};
}
return
tooltip;
}
private
ObjectProperty<
Tooltip>
tooltip;
public final void
setTooltip(
Tooltip value) {
tooltipProperty().
setValue(
value); }
public final
Tooltip getTooltip() { return
tooltip == null ? null :
tooltip.
getValue(); }
// --- context menu
/**
* The ContextMenu to show for this control.
*/
private
ObjectProperty<
ContextMenu>
contextMenu = new
SimpleObjectProperty<
ContextMenu>(this, "contextMenu") {
private
WeakReference<
ContextMenu>
contextMenuRef;
@
Override protected void
invalidated() {
ContextMenu oldMenu =
contextMenuRef == null ? null :
contextMenuRef.
get();
if (
oldMenu != null) {
ControlAcceleratorSupport.
removeAcceleratorsFromScene(
oldMenu.
getItems(),
Control.this);
}
ContextMenu ctx =
get();
contextMenuRef = new
WeakReference<>(
ctx);
if (
ctx != null) {
// set this flag so contextmenu show will be relative to parent window not anchor
ctx.
setImpl_showRelativeToWindow(true); //RT-15160
// if a context menu is set, we need to install any accelerators
// belonging to its menu items ASAP into the scene that this
// Control is in (if the control is not in a Scene, we will need
// to wait until it is and then do it).
ControlAcceleratorSupport.
addAcceleratorsIntoScene(
ctx.
getItems(),
Control.this);
}
}
};
public final
ObjectProperty<
ContextMenu>
contextMenuProperty() { return
contextMenu; }
public final void
setContextMenu(
ContextMenu value) {
contextMenu.
setValue(
value); }
public final
ContextMenu getContextMenu() { return
contextMenu == null ? null :
contextMenu.
getValue(); }
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Create a new Control.
*/
protected
Control() {
// focusTraversable is styleable through css. Calling setFocusTraversable
// makes it look to css like the user set the value and css will not
// override. Initializing focusTraversable by calling applyStyle
// with null for StyleOrigin ensures that css will be able to override
// the value.
final
StyleableProperty<
Boolean>
prop = (
StyleableProperty<
Boolean>)(
WritableValue<
Boolean>)
focusTraversableProperty();
prop.
applyStyle(null,
Boolean.
TRUE);
// we add a listener for menu request events to show the context menu
// that may be set on the Control
this.
addEventHandler(
ContextMenuEvent.
CONTEXT_MENU_REQUESTED,
contextMenuHandler);
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
// Proposed dispose() API.
// Note that there is impl code for a dispose method in TableRowSkinBase
// and TableCell (just search for dispose())
// public void dispose() {
// Skin skin = getSkin();
// if (skin != null) {
// skin.dispose();
// }
// }
/**
* Returns <code>true</code> since all Controls are resizable.
* @return whether this node can be resized by its parent during layout
*/
@
Override public boolean
isResizable() {
return true;
}
// Implementation of the Resizable interface.
// Because only the skin can know the min, pref, and max sizes, these
// functions are implemented to delegate to skin. If there is no skin then
// we simply return 0 for all the values since a Control without a Skin
// doesn't render
/**
* Computes the minimum allowable width of the Control, based on the provided
* height. The minimum width is not calculated within the Control, instead
* the calculation is delegated to the {@link Node#minWidth(double)} method
* of the {@link Skin}. If the Skin is null, the returned value is 0.
*
* @param height The height of the Control, in case this value might dictate
* the minimum width.
* @return A double representing the minimum width of this control.
*/
@
Override protected double
computeMinWidth(final double
height) {
if (
skinBase != null) {
return
skinBase.
computeMinWidth(
height,
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
minWidth(
height);
}
}
/**
* Computes the minimum allowable height of the Control, based on the provided
* width. The minimum height is not calculated within the Control, instead
* the calculation is delegated to the {@link Node#minHeight(double)} method
* of the {@link Skin}. If the Skin is null, the returned value is 0.
*
* @param width The width of the Control, in case this value might dictate
* the minimum height.
* @return A double representing the minimum height of this control.
*/
@
Override protected double
computeMinHeight(final double
width) {
if (
skinBase != null) {
return
skinBase.
computeMinHeight(
width,
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
minHeight(
width);
}
}
/**
* Computes the maximum allowable width of the Control, based on the provided
* height. The maximum width is not calculated within the Control, instead
* the calculation is delegated to the {@link Node#maxWidth(double)} method
* of the {@link Skin}. If the Skin is null, the returned value is 0.
*
* @param height The height of the Control, in case this value might dictate
* the maximum width.
* @return A double representing the maximum width of this control.
*/
@
Override protected double
computeMaxWidth(double
height) {
if (
skinBase != null) {
return
skinBase.
computeMaxWidth(
height,
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
maxWidth(
height);
}
}
/**
* Computes the maximum allowable height of the Control, based on the provided
* width. The maximum height is not calculated within the Control, instead
* the calculation is delegated to the {@link Node#maxHeight(double)} method
* of the {@link Skin}. If the Skin is null, the returned value is 0.
*
* @param width The width of the Control, in case this value might dictate
* the maximum height.
* @return A double representing the maximum height of this control.
*/
@
Override protected double
computeMaxHeight(double
width) {
if (
skinBase != null) {
return
skinBase.
computeMaxHeight(
width,
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
maxHeight(
width);
}
}
/** {@inheritDoc} */
@
Override protected double
computePrefWidth(double
height) {
if (
skinBase != null) {
return
skinBase.
computePrefWidth(
height,
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
prefWidth(
height);
}
}
/** {@inheritDoc} */
@
Override protected double
computePrefHeight(double
width) {
if (
skinBase != null) {
return
skinBase.
computePrefHeight(
width,
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
prefHeight(
width);
}
}
/** {@inheritDoc} */
@
Override public double
getBaselineOffset() {
if (
skinBase != null) {
return
skinBase.
computeBaselineOffset(
snappedTopInset(),
snappedRightInset(),
snappedBottomInset(),
snappedLeftInset());
} else {
final
Node skinNode =
getSkinNode();
return
skinNode == null ? 0 :
skinNode.
getBaselineOffset();
}
}
/***************************************************************************
* Implementation of layout bounds for the Control. We want to preserve *
* the lazy semantics of layout bounds. So whenever the width/height *
* changes on the node, we end up invalidating layout bounds. We then *
* recompute it on demand. *
**************************************************************************/
/** {@inheritDoc} */
@
Override protected void
layoutChildren() {
if (
skinBase != null) {
final double
x =
snappedLeftInset();
final double
y =
snappedTopInset();
final double
w =
snapSize(
getWidth()) -
x -
snappedRightInset();
final double
h =
snapSize(
getHeight()) -
y -
snappedBottomInset();
skinBase.
layoutChildren(
x,
y,
w,
h);
} else {
Node n =
getSkinNode();
if (
n != null) {
n.
resizeRelocate(0, 0,
getWidth(),
getHeight());
}
}
}
/***************************************************************************
* Forward the following to the skin *
**************************************************************************/
/**
* Create a new instance of the default skin for this control. This is called to create a skin for the control if
* no skin is provided via CSS {@code -fx-skin} or set explicitly in a sub-class with {@code setSkin(...)}.
*
* @return new instance of default skin for this control. If null then the control will have no skin unless one
* is provided by css.
* @since JavaFX 8.0
*/
protected
Skin<?>
createDefaultSkin() {
return null;
}
/***************************************************************************
* *
* Package API for SkinBase *
* *
**************************************************************************/
// package private for SkinBase
ObservableList<
Node>
getControlChildren() {
return
getChildren();
}
/***************************************************************************
* *
* Private implementation *
* *
**************************************************************************/
/**
* Gets the Skin's node, or returns null if there is no Skin.
* Convenience method for getting the node of the skin. This is null-safe,
* meaning if skin is null then it will return null instead of throwing
* a NullPointerException.
*
* @return The Skin's node, or null.
*/
private
Node getSkinNode() {
assert
skinBase == null;
Skin<?>
skin =
getSkin();
return
skin == null ? null :
skin.
getNode();
}
/**
* Keeps a reference to the name of the class currently acting as the skin.
*/
private
String currentSkinClassName = null;
private
StringProperty skinClassName;
/**
* @treatAsPrivate
* @since JavaFX 2.1
*/
@
Deprecated protected
StringProperty skinClassNameProperty() {
if (
skinClassName == null) {
skinClassName = new
StyleableStringProperty() {
@
Override
public void
set(
String v) {
// do not allow the skin to be set to null through CSS
if (
v == null ||
v.
isEmpty() ||
v.
equals(
get())) return;
super.set(
v);
}
@
Override
public void
invalidated() {
if (
get() != null) {
if (!
get().
equals(
currentSkinClassName)) {
loadSkinClass(
Control.this,
skinClassName.
get());
}
// Note: CSS should not set skin to null
}
}
@
Override
public
Object getBean() {
return
Control.this;
}
@
Override
public
String getName() {
return "skinClassName";
}
@
Override
public
CssMetaData<
Control,
String>
getCssMetaData() {
return
StyleableProperties.
SKIN;
}
};
}
return
skinClassName;
}
static void
loadSkinClass(final
Skinnable control, final
String skinClassName) {
if (
skinClassName == null ||
skinClassName.
isEmpty()) {
final
String msg =
"Empty -fx-skin property specified for control " +
control;
final
List<
CssError>
errors =
StyleManager.
getErrors();
if (
errors != null) {
CssError error = new
CssError(
msg);
errors.
add(
error); // RT-19884
}
Logging.
getControlsLogger().
severe(
msg);
return;
}
try {
final
Class<?>
skinClass =
Control.
loadClass(
skinClassName,
control);
if (!
Skin.class.
isAssignableFrom(
skinClass)) {
final
String msg =
"'" +
skinClassName + "' is not a valid Skin class for control " +
control;
final
List<
CssError>
errors =
StyleManager.
getErrors();
if (
errors != null) {
CssError error = new
CssError(
msg);
errors.
add(
error); // RT-19884
}
Logging.
getControlsLogger().
severe(
msg);
return;
}
Constructor<?>[]
constructors =
skinClass.
getConstructors();
Constructor<?>
skinConstructor = null;
for (
Constructor<?>
c :
constructors) {
Class<?>[]
parameterTypes =
c.
getParameterTypes();
if (
parameterTypes.length == 1 &&
Skinnable.class.
isAssignableFrom(
parameterTypes[0])) {
skinConstructor =
c;
break;
}
}
if (
skinConstructor == null) {
final
String msg =
"No valid constructor defined in '" +
skinClassName + "' for control " +
control +
".\r\nYou must provide a constructor that accepts a single "
+ "Skinnable (e.g. Control or PopupControl) parameter in " +
skinClassName + ".";
final
List<
CssError>
errors =
StyleManager.
getErrors();
if (
errors != null) {
CssError error = new
CssError(
msg);
errors.
add(
error); // RT-19884
}
Logging.
getControlsLogger().
severe(
msg);
} else {
Skin<?>
skinInstance = (
Skin<?>)
skinConstructor.
newInstance(
control);
// Do not call setSkin here since it has the side effect of
// also setting the skinClassName!
control.
skinProperty().
set(
skinInstance);
}
} catch (
InvocationTargetException e) {
final
String msg =
"Failed to load skin '" +
skinClassName + "' for control " +
control;
final
List<
CssError>
errors =
StyleManager.
getErrors();
if (
errors != null) {
CssError error = new
CssError(
msg + " :" +
e.
getLocalizedMessage());
errors.
add(
error); // RT-19884
}
Logging.
getControlsLogger().
severe(
msg,
e.
getCause());
} catch (
Exception e) {
final
String msg =
"Failed to load skin '" +
skinClassName + "' for control " +
control;
final
List<
CssError>
errors =
StyleManager.
getErrors();
if (
errors != null) {
CssError error = new
CssError(
msg + " :" +
e.
getLocalizedMessage());
errors.
add(
error); // RT-19884
}
Logging.
getControlsLogger().
severe(
msg,
e);
}
}
/***************************************************************************
* *
* StyleSheet Handling *
* *
**************************************************************************/
private static class
StyleableProperties {
private static final
CssMetaData<
Control,
String>
SKIN =
new
CssMetaData<
Control,
String>("-fx-skin",
StringConverter.
getInstance()) {
@
Override
public boolean
isSettable(
Control n) {
return (
n.
skin == null || !
n.
skin.
isBound());
}
@
Override
public
StyleableProperty<
String>
getStyleableProperty(
Control n) {
return (
StyleableProperty<
String>)(
WritableValue<
String>)
n.
skinClassNameProperty();
}
};
private static final
List<
CssMetaData<? extends
Styleable, ?>>
STYLEABLES;
static {
final
List<
CssMetaData<? extends
Styleable, ?>>
styleables =
new
ArrayList<
CssMetaData<? extends
Styleable, ?>>(
Region.
getClassCssMetaData());
styleables.
add(
SKIN);
STYLEABLES =
Collections.
unmodifiableList(
styleables);
}
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
* @since JavaFX 8.0
*/
public static
List<
CssMetaData<? extends
Styleable, ?>>
getClassCssMetaData() {
return
StyleableProperties.
STYLEABLES;
}
/**
* This method returns a {@link List} containing all {@link CssMetaData} for
* both this Control (returned from {@link #getControlCssMetaData()} and its
* {@link Skin}, assuming the {@link #skinProperty() skin property} is a
* {@link SkinBase}.
*
* <p>Developers who wish to provide custom CssMetaData are therefore
* encouraged to override {@link Control#getControlCssMetaData()} or
* {@link SkinBase#getCssMetaData()}, depending on where the CssMetaData
* resides.
* @since JavaFX 8.0
*/
@
Override
public final
List<
CssMetaData<? extends
Styleable, ?>>
getCssMetaData() {
if (
styleableProperties == null) {
// RT-29162: make sure properties only show up once in the list
java.util.
Map<
String,
CssMetaData<? extends
Styleable, ?>>
map =
new java.util.
HashMap<
String,
CssMetaData<? extends
Styleable, ?>>();
List<
CssMetaData<? extends
Styleable, ?>>
list =
getControlCssMetaData();
for (int
n=0,
nMax =
list != null ?
list.
size() : 0;
n<
nMax;
n++) {
CssMetaData<? extends
Styleable, ?>
metaData =
list.
get(
n);
if (
metaData == null) continue;
map.
put(
metaData.
getProperty(),
metaData);
}
//
// if both control and skin base have the same property, use the
// one from skin base since it may be a specialization of the
// property in the control. For instance, Label has -fx-font and
// so does LabeledText which is Label's skin.
//
list =
skinBase != null ?
skinBase.
getCssMetaData() : null;
for (int
n=0,
nMax =
list != null ?
list.
size() : 0;
n<
nMax;
n++) {
CssMetaData<? extends
Styleable, ?>
metaData =
list.
get(
n);
if (
metaData == null) continue;
map.
put(
metaData.
getProperty(),
metaData);
}
styleableProperties = new
ArrayList<
CssMetaData<? extends
Styleable, ?>>();
styleableProperties.
addAll(
map.
values());
}
return
styleableProperties;
}
/**
* @return unmodifiable list of the controls css styleable properties
* @since JavaFX 8.0
*/
protected
List<
CssMetaData<? extends
Styleable, ?>>
getControlCssMetaData() {
return
getClassCssMetaData();
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
@
Override protected void
impl_processCSS(
WritableValue<
Boolean>
unused) {
super.impl_processCSS(
unused);
if (
getSkin() == null) {
// try to create default skin
final
Skin<?>
defaultSkin =
createDefaultSkin();
if (
defaultSkin != null) {
skinProperty().
set(
defaultSkin);
super.impl_processCSS(
unused);
} else {
final
String msg = "The -fx-skin property has not been defined in CSS for " + this +
" and createDefaultSkin() returned null.";
final
List<
CssError>
errors =
StyleManager.
getErrors();
if (
errors != null) {
CssError error = new
CssError(
msg);
errors.
add(
error); // RT-19884
}
Logging.
getControlsLogger().
severe(
msg);
}
}
}
/**
* Most Controls return true for focusTraversable initial value.
* This method is called from CSS code to get the correct initial value.
* @treatAsPrivate implementation detail
*/
@
Deprecated @
Override
protected /*do not make final*/
Boolean impl_cssGetFocusTraversableInitialValue() {
return
Boolean.
TRUE;
}
/***************************************************************************
* *
* Accessibility handling *
* *
**************************************************************************/
@
Override
public
Object queryAccessibleAttribute(
AccessibleAttribute attribute,
Object...
parameters) {
switch (
attribute) {
case
HELP:
String help =
getAccessibleHelp();
if (
help != null && !
help.
isEmpty()) return
help;
Tooltip tooltip =
getTooltip();
return
tooltip == null ? "" :
tooltip.
getText();
default:
}
if (
skinBase != null) {
Object result =
skinBase.
queryAccessibleAttribute(
attribute,
parameters);
if (
result != null) return
result;
}
return super.queryAccessibleAttribute(
attribute,
parameters);
}
@
Override
public void
executeAccessibleAction(
AccessibleAction action,
Object...
parameters) {
if (
skinBase != null) {
skinBase.
executeAccessibleAction(
action,
parameters);
}
super.executeAccessibleAction(
action,
parameters);
}
}