/*
* 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.util.
ArrayList;
import java.util.
Collections;
import java.util.
List;
import javafx.beans.property.
BooleanProperty;
import javafx.beans.property.
DoubleProperty;
import javafx.beans.property.
DoublePropertyBase;
import javafx.beans.property.
IntegerProperty;
import javafx.beans.property.
ObjectProperty;
import javafx.beans.property.
SimpleBooleanProperty;
import javafx.beans.property.
SimpleObjectProperty;
import javafx.beans.value.
WritableValue;
import javafx.geometry.
Orientation;
import javafx.scene.
AccessibleAction;
import javafx.scene.
AccessibleAttribute;
import javafx.scene.
AccessibleRole;
import javafx.util.
StringConverter;
import com.sun.javafx.util.
Utils;
import javafx.css.
CssMetaData;
import javafx.css.
PseudoClass;
import javafx.css.
StyleableBooleanProperty;
import javafx.css.
StyleableDoubleProperty;
import javafx.css.
StyleableIntegerProperty;
import javafx.css.
StyleableObjectProperty;
import com.sun.javafx.css.converters.
BooleanConverter;
import com.sun.javafx.css.converters.
EnumConverter;
import com.sun.javafx.css.converters.
SizeConverter;
import com.sun.javafx.scene.control.skin.
SliderSkin;
import javafx.css.
Styleable;
import javafx.css.
StyleableProperty;
/**
* The Slider Control is used to display a continuous or discrete range of
* valid numeric choices and allows the user to interact with the control. It is
* typically represented visually as having a "track" and a "knob" or "thumb"
* which is dragged within the track. The Slider can optionally show tick marks
* and labels indicating the different slider position values.
* <p>
* The three fundamental variables of the slider are <code>min</code>,
* <code>max</code>, and <code>value</code>. The <code>value</code> should always
* be a number within the range defined by <code>min</code> and
* <code>max</code>. <code>min</code> should always be less than or equal to
* <code>max</code> (although a slider who's <code>min</code> and
* <code>max</code> are equal is a degenerate case that makes no sense).
* <code>min</code> defaults to 0, whereas <code>max</code> defaults to 100.
* <p>
* This first example creates a slider who's range, or span, goes from 0 to 1,
* and who's value defaults to .5:
*
* <pre>
* import javafx.scene.control.Slider;
*
* Slider slider = new Slider(0, 1, 0.5);
* </pre>
*
* <p>
* This next example shows a slider with customized tick marks and tick mark
* labels, which also spans from 0 to 1:
*
* <pre>
* import javafx.scene.control.Slider;
*
* Slider slider = new Slider(0, 1, 0.5);
* slider.setShowTickMarks(true);
* slider.setShowTickLabels(true);
* slider.setMajorTickUnit(0.25f);
* slider.setBlockIncrement(0.1f);
* </pre>
* @since JavaFX 2.0
*/
public class
Slider extends
Control {
/**
* Creates a default Slider instance.
*/
public
Slider() {
initialize();
}
/**
* Constructs a Slider control with the specified slider min, max and current value values.
* @param min Slider minimum value
* @param max Slider maximum value
* @param value Slider current value
*/
public
Slider(double
min, double
max, double
value) {
setMax(
max);
setMin(
min);
setValue(
value);
adjustValues();
initialize();
}
private void
initialize() {
//Initialize the style class to be 'slider'.
getStyleClass().
setAll(
DEFAULT_STYLE_CLASS);
setAccessibleRole(
AccessibleRole.
SLIDER);
}
/**
* The maximum value represented by this Slider. This must be a
* value greater than {@link #minProperty() min}.
*/
private
DoubleProperty max;
public final void
setMax(double
value) {
maxProperty().
set(
value);
}
public final double
getMax() {
return
max == null ? 100 :
max.
get();
}
public final
DoubleProperty maxProperty() {
if (
max == null) {
max = new
DoublePropertyBase(100) {
@
Override protected void
invalidated() {
if (
get() <
getMin()) {
setMin(
get());
}
adjustValues();
notifyAccessibleAttributeChanged(
AccessibleAttribute.
MAX_VALUE);
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "max";
}
};
}
return
max;
}
/**
* The minimum value represented by this Slider. This must be a
* value less than {@link #maxProperty() max}.
*/
private
DoubleProperty min;
public final void
setMin(double
value) {
minProperty().
set(
value);
}
public final double
getMin() {
return
min == null ? 0 :
min.
get();
}
public final
DoubleProperty minProperty() {
if (
min == null) {
min = new
DoublePropertyBase(0) {
@
Override protected void
invalidated() {
if (
get() >
getMax()) {
setMax(
get());
}
adjustValues();
notifyAccessibleAttributeChanged(
AccessibleAttribute.
MIN_VALUE);
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "min";
}
};
}
return
min;
}
/**
* The current value represented by this Slider. This value must
* always be between {@link #minProperty() min} and {@link #maxProperty() max},
* inclusive. If it is ever out of bounds either due to {@code min} or
* {@code max} changing or due to itself being changed, then it will
* be clamped to always remain valid.
*/
private
DoubleProperty value;
public final void
setValue(double
value) {
if (!
valueProperty().
isBound())
valueProperty().
set(
value);
}
public final double
getValue() {
return
value == null ? 0 :
value.
get();
}
public final
DoubleProperty valueProperty() {
if (
value == null) {
value = new
DoublePropertyBase(0) {
@
Override protected void
invalidated() {
adjustValues();
notifyAccessibleAttributeChanged(
AccessibleAttribute.
VALUE);
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "value";
}
};
}
return
value;
}
/**
* When true, indicates the current value of this Slider is changing.
* It provides notification that the value is changing. Once the value is
* computed, it is reset back to false.
*/
private
BooleanProperty valueChanging;
public final void
setValueChanging(boolean
value) {
valueChangingProperty().
set(
value);
}
public final boolean
isValueChanging() {
return
valueChanging == null ? false :
valueChanging.
get();
}
public final
BooleanProperty valueChangingProperty() {
if (
valueChanging == null) {
valueChanging = new
SimpleBooleanProperty(this, "valueChanging", false);
}
return
valueChanging;
}
// /**
// * The {@code span} is the distance, or quantity, between min and max value.
// * This will be strictly non-negative, since both {@code min} and
// * {@code max} are forced to maintain a proper relationship.
// */
// // public def span = bind max - min;
/**
* The orientation of the {@code Slider} can either be horizontal
* or vertical.
*/
private
ObjectProperty<
Orientation>
orientation;
public final void
setOrientation(
Orientation value) {
orientationProperty().
set(
value);
}
public final
Orientation getOrientation() {
return
orientation == null ?
Orientation.
HORIZONTAL :
orientation.
get();
}
public final
ObjectProperty<
Orientation>
orientationProperty() {
if (
orientation == null) {
orientation = new
StyleableObjectProperty<
Orientation>(
Orientation.
HORIZONTAL) {
@
Override protected void
invalidated() {
final boolean
vertical = (
get() ==
Orientation.
VERTICAL);
pseudoClassStateChanged(
VERTICAL_PSEUDOCLASS_STATE,
vertical);
pseudoClassStateChanged(
HORIZONTAL_PSEUDOCLASS_STATE, !
vertical);
}
@
Override
public
CssMetaData<
Slider,
Orientation>
getCssMetaData() {
return
StyleableProperties.
ORIENTATION;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "orientation";
}
};
}
return
orientation;
}
/**
* Indicates that the labels for tick marks should be shown. Typically a
* {@link Skin} implementation will only show labels if
* {@link #showTickMarksProperty() showTickMarks} is also true.
*/
private
BooleanProperty showTickLabels;
public final void
setShowTickLabels(boolean
value) {
showTickLabelsProperty().
set(
value);
}
public final boolean
isShowTickLabels() {
return
showTickLabels == null ? false :
showTickLabels.
get();
}
public final
BooleanProperty showTickLabelsProperty() {
if (
showTickLabels == null) {
showTickLabels = new
StyleableBooleanProperty(false) {
@
Override
public
CssMetaData<
Slider,
Boolean>
getCssMetaData() {
return
StyleableProperties.
SHOW_TICK_LABELS;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "showTickLabels";
}
};
}
return
showTickLabels;
}
/**
* Specifies whether the {@link Skin} implementation should show tick marks.
*/
private
BooleanProperty showTickMarks;
public final void
setShowTickMarks(boolean
value) {
showTickMarksProperty().
set(
value);
}
public final boolean
isShowTickMarks() {
return
showTickMarks == null ? false :
showTickMarks.
get();
}
public final
BooleanProperty showTickMarksProperty() {
if (
showTickMarks == null) {
showTickMarks = new
StyleableBooleanProperty(false) {
@
Override
public
CssMetaData<
Slider,
Boolean>
getCssMetaData() {
return
StyleableProperties.
SHOW_TICK_MARKS;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "showTickMarks";
}
};
}
return
showTickMarks;
}
/**
* The unit distance between major tick marks. For example, if
* the {@link #minProperty() min} is 0 and the {@link #maxProperty() max} is 100 and the
* {@link #majorTickUnitProperty() majorTickUnit} is 25, then there would be 5 tick marks: one at
* position 0, one at position 25, one at position 50, one at position
* 75, and a final one at position 100.
* <p>
* This value should be positive and should be a value less than the
* span. Out of range values are essentially the same as disabling
* tick marks.
*/
private
DoubleProperty majorTickUnit;
public final void
setMajorTickUnit(double
value) {
if (
value <= 0) {
throw new
IllegalArgumentException("MajorTickUnit cannot be less than or equal to 0.");
}
majorTickUnitProperty().
set(
value);
}
public final double
getMajorTickUnit() {
return
majorTickUnit == null ? 25 :
majorTickUnit.
get();
}
public final
DoubleProperty majorTickUnitProperty() {
if (
majorTickUnit == null) {
majorTickUnit = new
StyleableDoubleProperty(25) {
@
Override
public void
invalidated() {
if (
get() <= 0) {
throw new
IllegalArgumentException("MajorTickUnit cannot be less than or equal to 0.");
}
}
@
Override
public
CssMetaData<
Slider,
Number>
getCssMetaData() {
return
StyleableProperties.
MAJOR_TICK_UNIT;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "majorTickUnit";
}
};
}
return
majorTickUnit;
}
/**
* The number of minor ticks to place between any two major ticks. This
* number should be positive or zero. Out of range values will disable
* disable minor ticks, as will a value of zero.
*/
private
IntegerProperty minorTickCount;
public final void
setMinorTickCount(int
value) {
minorTickCountProperty().
set(
value);
}
public final int
getMinorTickCount() {
return
minorTickCount == null ? 3 :
minorTickCount.
get();
}
public final
IntegerProperty minorTickCountProperty() {
if (
minorTickCount == null) {
minorTickCount = new
StyleableIntegerProperty(3) {
@
Override
public
CssMetaData<
Slider,
Number>
getCssMetaData() {
return
StyleableProperties.
MINOR_TICK_COUNT;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "minorTickCount";
}
};
}
return
minorTickCount;
}
/**
* Indicates whether the {@link #valueProperty() value} of the {@code Slider} should always
* be aligned with the tick marks. This is honored even if the tick marks
* are not shown.
*/
private
BooleanProperty snapToTicks;
public final void
setSnapToTicks(boolean
value) {
snapToTicksProperty().
set(
value);
}
public final boolean
isSnapToTicks() {
return
snapToTicks == null ? false :
snapToTicks.
get();
}
public final
BooleanProperty snapToTicksProperty() {
if (
snapToTicks == null) {
snapToTicks = new
StyleableBooleanProperty(false) {
@
Override
public
CssMetaData<
Slider,
Boolean>
getCssMetaData() {
return
StyleableProperties.
SNAP_TO_TICKS;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "snapToTicks";
}
};
}
return
snapToTicks;
}
/**
* A function for formatting the label for a major tick. The number
* representing the major tick will be passed to the function. If this
* function is not specified, then a default function will be used by
* the {@link Skin} implementation.
*/
private
ObjectProperty<
StringConverter<
Double>>
labelFormatter;
public final void
setLabelFormatter(
StringConverter<
Double>
value) {
labelFormatterProperty().
set(
value);
}
public final
StringConverter<
Double>
getLabelFormatter() {
return
labelFormatter == null ? null :
labelFormatter.
get();
}
public final
ObjectProperty<
StringConverter<
Double>>
labelFormatterProperty() {
if (
labelFormatter == null) {
labelFormatter = new
SimpleObjectProperty<
StringConverter<
Double>>(this, "labelFormatter");
}
return
labelFormatter;
}
/**
* The amount by which to adjust the slider if the track of the slider is
* clicked. This is used when manipulating the slider position using keys. If
* {@link #snapToTicksProperty() snapToTicks} is true then the nearest tick mark to the adjusted
* value will be used.
*/
private
DoubleProperty blockIncrement;
public final void
setBlockIncrement(double
value) {
blockIncrementProperty().
set(
value);
}
public final double
getBlockIncrement() {
return
blockIncrement == null ? 10 :
blockIncrement.
get();
}
public final
DoubleProperty blockIncrementProperty() {
if (
blockIncrement == null) {
blockIncrement = new
StyleableDoubleProperty(10) {
@
Override
public
CssMetaData<
Slider,
Number>
getCssMetaData() {
return
StyleableProperties.
BLOCK_INCREMENT;
}
@
Override
public
Object getBean() {
return
Slider.this;
}
@
Override
public
String getName() {
return "blockIncrement";
}
};
}
return
blockIncrement;
}
/**
* Adjusts {@link #valueProperty() value} to match <code>newValue</code>. The
* <code>value</code>is the actual amount between the
* {@link #minProperty() min} and {@link #maxProperty() max}. This function
* also takes into account {@link #snapToTicksProperty() snapToTicks}, which
* is the main difference between adjustValue and setValue. It also ensures
* that the value is some valid number between min and max.
*
* @expert This function is intended to be used by experts, primarily
* by those implementing new Skins or Behaviors. It is not common
* for developers or designers to access this function directly.
*/
public void
adjustValue(double
newValue) {
// figure out the "value" associated with the specified position
final double
_min =
getMin();
final double
_max =
getMax();
if (
_max <=
_min) return;
newValue =
newValue <
_min ?
_min :
newValue;
newValue =
newValue >
_max ?
_max :
newValue;
setValue(
snapValueToTicks(
newValue));
}
/**
* Increments the value by {@link #blockIncrementProperty() blockIncrement}, bounded by max. If the
* max is less than or equal to the min, then this method does nothing.
*/
public void
increment() {
adjustValue(
getValue() +
getBlockIncrement());
}
/**
* Decrements the value by {@link #blockIncrementProperty() blockIncrement}, bounded by max. If the
* max is less than or equal to the min, then this method does nothing.
*/
public void
decrement() {
adjustValue(
getValue() -
getBlockIncrement());
}
/**
* Ensures that min is always < max, that value is always
* somewhere between the two, and that if snapToTicks is set then the
* value will always be set to align with a tick mark.
*/
private void
adjustValues() {
if ((
getValue() <
getMin() ||
getValue() >
getMax()) /* && !isReadOnly(value)*/)
setValue(
Utils.
clamp(
getMin(),
getValue(),
getMax()));
}
/**
* Utility function which, given the specified value, will position it
* either aligned with a tick, or simply clamp between min & max value,
* depending on whether snapToTicks is set.
*
* @expert This function is intended to be used by experts, primarily
* by those implementing new Skins or Behaviors. It is not common
* for developers or designers to access this function directly.
*/
private double
snapValueToTicks(double
val) {
double
v =
val;
if (
isSnapToTicks()) {
double
tickSpacing = 0;
// compute the nearest tick to this value
if (
getMinorTickCount() != 0) {
tickSpacing =
getMajorTickUnit() / (
Math.
max(
getMinorTickCount(),0)+1);
} else {
tickSpacing =
getMajorTickUnit();
}
int
prevTick = (int)((
v -
getMin())/
tickSpacing);
double
prevTickValue = (
prevTick) *
tickSpacing +
getMin();
double
nextTickValue = (
prevTick + 1) *
tickSpacing +
getMin();
v =
Utils.
nearest(
prevTickValue,
v,
nextTickValue);
}
return
Utils.
clamp(
getMin(),
v,
getMax());
}
/** {@inheritDoc} */
@
Override protected
Skin<?>
createDefaultSkin() {
return new
SliderSkin(this);
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static final
String DEFAULT_STYLE_CLASS = "slider";
private static class
StyleableProperties {
private static final
CssMetaData<
Slider,
Number>
BLOCK_INCREMENT =
new
CssMetaData<
Slider,
Number>("-fx-block-increment",
SizeConverter.
getInstance(), 10.0) {
@
Override
public boolean
isSettable(
Slider n) {
return
n.
blockIncrement == null || !
n.
blockIncrement.
isBound();
}
@
Override
public
StyleableProperty<
Number>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Number>)(
WritableValue<
Number>)
n.
blockIncrementProperty();
}
};
private static final
CssMetaData<
Slider,
Boolean>
SHOW_TICK_LABELS =
new
CssMetaData<
Slider,
Boolean>("-fx-show-tick-labels",
BooleanConverter.
getInstance(),
Boolean.
FALSE) {
@
Override
public boolean
isSettable(
Slider n) {
return
n.
showTickLabels == null || !
n.
showTickLabels.
isBound();
}
@
Override
public
StyleableProperty<
Boolean>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Boolean>)(
WritableValue<
Boolean>)
n.
showTickLabelsProperty();
}
};
private static final
CssMetaData<
Slider,
Boolean>
SHOW_TICK_MARKS =
new
CssMetaData<
Slider,
Boolean>("-fx-show-tick-marks",
BooleanConverter.
getInstance(),
Boolean.
FALSE) {
@
Override
public boolean
isSettable(
Slider n) {
return
n.
showTickMarks == null || !
n.
showTickMarks.
isBound();
}
@
Override
public
StyleableProperty<
Boolean>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Boolean>)(
WritableValue<
Boolean>)
n.
showTickMarksProperty();
}
};
private static final
CssMetaData<
Slider,
Boolean>
SNAP_TO_TICKS =
new
CssMetaData<
Slider,
Boolean>("-fx-snap-to-ticks",
BooleanConverter.
getInstance(),
Boolean.
FALSE) {
@
Override
public boolean
isSettable(
Slider n) {
return
n.
snapToTicks == null || !
n.
snapToTicks.
isBound();
}
@
Override
public
StyleableProperty<
Boolean>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Boolean>)(
WritableValue<
Boolean>)
n.
snapToTicksProperty();
}
};
private static final
CssMetaData<
Slider,
Number>
MAJOR_TICK_UNIT =
new
CssMetaData<
Slider,
Number>("-fx-major-tick-unit",
SizeConverter.
getInstance(), 25.0) {
@
Override
public boolean
isSettable(
Slider n) {
return
n.
majorTickUnit == null || !
n.
majorTickUnit.
isBound();
}
@
Override
public
StyleableProperty<
Number>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Number>)(
WritableValue<
Number>)
n.
majorTickUnitProperty();
}
};
private static final
CssMetaData<
Slider,
Number>
MINOR_TICK_COUNT =
new
CssMetaData<
Slider,
Number>("-fx-minor-tick-count",
SizeConverter.
getInstance(), 3.0) {
@
Override
public boolean
isSettable(
Slider n) {
return
n.
minorTickCount == null || !
n.
minorTickCount.
isBound();
}
@
Override
public
StyleableProperty<
Number>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Number>)(
WritableValue<
Number>)
n.
minorTickCountProperty();
}
};
private static final
CssMetaData<
Slider,
Orientation>
ORIENTATION =
new
CssMetaData<
Slider,
Orientation>("-fx-orientation",
new
EnumConverter<
Orientation>(
Orientation.class),
Orientation.
HORIZONTAL) {
@
Override
public
Orientation getInitialValue(
Slider node) {
// A vertical Slider should remain vertical
return
node.
getOrientation();
}
@
Override
public boolean
isSettable(
Slider n) {
return
n.
orientation == null || !
n.
orientation.
isBound();
}
@
Override
public
StyleableProperty<
Orientation>
getStyleableProperty(
Slider n) {
return (
StyleableProperty<
Orientation>)(
WritableValue<
Orientation>)
n.
orientationProperty();
}
};
private static final
List<
CssMetaData<? extends
Styleable, ?>>
STYLEABLES;
static {
final
List<
CssMetaData<? extends
Styleable, ?>>
styleables =
new
ArrayList<
CssMetaData<? extends
Styleable, ?>>(
Control.
getClassCssMetaData());
styleables.
add(
BLOCK_INCREMENT);
styleables.
add(
SHOW_TICK_LABELS);
styleables.
add(
SHOW_TICK_MARKS);
styleables.
add(
SNAP_TO_TICKS);
styleables.
add(
MAJOR_TICK_UNIT);
styleables.
add(
MINOR_TICK_COUNT);
styleables.
add(
ORIENTATION);
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;
}
/**
* RT-19263
* @treatAsPrivate implementation detail
* @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
* @since JavaFX 8.0
*/
@
Deprecated
@
Override protected
List<
CssMetaData<? extends
Styleable, ?>>
getControlCssMetaData() {
return
getClassCssMetaData();
}
private static final
PseudoClass VERTICAL_PSEUDOCLASS_STATE =
PseudoClass.
getPseudoClass("vertical");
private static final
PseudoClass HORIZONTAL_PSEUDOCLASS_STATE =
PseudoClass.
getPseudoClass("horizontal");
/***************************************************************************
* *
* Accessibility handling *
* *
**************************************************************************/
@
Override
public
Object queryAccessibleAttribute(
AccessibleAttribute attribute,
Object...
parameters) {
switch (
attribute) {
case
VALUE: return
getValue();
case
MAX_VALUE: return
getMax();
case
MIN_VALUE: return
getMin();
case
ORIENTATION: return
getOrientation();
default: return super.queryAccessibleAttribute(
attribute,
parameters);
}
}
@
Override
public void
executeAccessibleAction(
AccessibleAction action,
Object...
parameters) {
switch (
action) {
case
INCREMENT:
increment(); break;
case
DECREMENT:
decrement(); break;
case
SET_VALUE: {
Double value = (
Double)
parameters[0];
if (
value != null)
setValue(
value);
break;
}
default: super.executeAccessibleAction(
action,
parameters);
}
}
}