/*
* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
* Modified by Altug Uzunali (altug.uzunali@gmail.com) at 08/02/2012
*
* Added two thumbs functionality
*
* Modified by Jose Pereda (pereda@eii.uva.es) at 14/09/2012
*
* Added a TickLabelFormatter and a highlighted track
*
*/
package weeklyschedulerfx.doubleSlider;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Side;
import javafx.scene.chart.NumberAxis;
import javafx.scene.layout.StackPane;
import com.sun.javafx.scene.control.skin.SkinBase;
import javafx.util.StringConverter;
public class DoubleSliderSkin extends SkinBase<DoubleSlider, DoubleSliderBehavior>{
public enum ThumbNumber {
Thumb1,
Thumb2
}
private double trackStart;
private double thumbTop;
private double thumbLeft;
private double thumbWidth;
private double thumbHeight;
private double trackLength;
private double preDragThumbPos;
private Point2D dragStart;
private boolean showTickMarks;
private NumberAxis tickLine = null;
private double trackToTickGap = 2;
private StackPane thumb1;
private StackPane thumb2;
private StackPane track;
private StackPane range;
/**
* @param doubleSlider
* @param doubleSliderBehavior
*/
public DoubleSliderSkin(DoubleSlider doubleSlider) {
super(doubleSlider, new DoubleSliderBehavior(doubleSlider));
initialize();
requestLayout();
registerChangeListener(doubleSlider.minProperty(), "MIN");
registerChangeListener(doubleSlider.maxProperty(), "MAX");
registerChangeListener(doubleSlider.value1Property(), "VALUE1");
registerChangeListener(doubleSlider.value2Property(), "VALUE2");
registerChangeListener(doubleSlider.orientationProperty(), "ORIENTATION");
registerChangeListener(doubleSlider.showTickMarksProperty(), "SHOW_TICK_MARKS");
registerChangeListener(doubleSlider.showTickLabelsProperty(), "SHOW_TICK_LABELS");
registerChangeListener(doubleSlider.majorTickUnitProperty(), "MAJOR_TICK_UNIT");
registerChangeListener(doubleSlider.minorTickCountProperty(), "MINOR_TICK_COUNT");
}
private void initialize() {
thumb1 = new StackPane();
thumb1.getStyleClass().setAll("thumb1");
thumb2 = new StackPane();
thumb2.getStyleClass().setAll("thumb2");
track = new StackPane();
track.getStyleClass().setAll("track");
range = new StackPane();
range.getStyleClass().setAll("range");
getChildren().clear();
getChildren().addAll(track, thumb1, thumb2, range);
setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
track.setOnMousePressed( new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
if (!thumb1.isPressed()) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
getBehavior().trackPress(me, (me.getX() / trackLength));
} else {
getBehavior().trackPress(me, (me.getY() / trackLength));
}
}
}
});
track.setOnMouseReleased( new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
//Nothing being done with the second param in sliderBehavior
//So, passing a dummy value
getBehavior().trackRelease(me, 0.0f);
}
});
thumb1.setOnMousePressed(new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
getBehavior().thumbPressed(me, 0.0f);
dragStart = thumb1.localToParent(me.getX(), me.getY());
preDragThumbPos = (getSkinnable().getValue1() - getSkinnable().getMin()) /
(getSkinnable().getMax() - getSkinnable().getMin());
}
});
thumb1.setOnMouseReleased(new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
getBehavior().thumbReleased(ThumbNumber.Thumb1, me);
}
});
thumb1.setOnMouseDragged(new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
Point2D cur = thumb1.localToParent(me.getX(), me.getY());
double dragPos = (getSkinnable().getOrientation() == Orientation.HORIZONTAL)?
cur.getX() - dragStart.getX() : -(cur.getY() - dragStart.getY());
getBehavior().thumbDragged(ThumbNumber.Thumb1, me, preDragThumbPos + dragPos / trackLength);
}
});
thumb2.setOnMousePressed(new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
getBehavior().thumbPressed(me, 0.0f);
dragStart = thumb2.localToParent(me.getX(), me.getY());
preDragThumbPos = (getSkinnable().getValue2() - getSkinnable().getMin()) /
(getSkinnable().getMax() - getSkinnable().getMin());
}
});
thumb2.setOnMouseReleased(new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
getBehavior().thumbReleased(ThumbNumber.Thumb2, me);
}
});
thumb2.setOnMouseDragged(new EventHandler<javafx.scene.input.MouseEvent>() {
@Override public void handle(javafx.scene.input.MouseEvent me) {
Point2D cur = thumb2.localToParent(me.getX(), me.getY());
double dragPos = (getSkinnable().getOrientation() == Orientation.HORIZONTAL)?
cur.getX() - dragStart.getX() : -(cur.getY() - dragStart.getY());
getBehavior().thumbDragged(ThumbNumber.Thumb2, me, preDragThumbPos + dragPos / trackLength);
}
});
}
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
showTickMarks = (ticksVisible || labelsVisible);
DoubleSlider doubleSlider = getSkinnable();
if (showTickMarks) {
if (tickLine == null) {
tickLine = new NumberAxis();
tickLine.setAutoRanging(false);
tickLine.setSide(doubleSlider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (doubleSlider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
tickLine.setUpperBound(doubleSlider.getMax());
tickLine.setLowerBound(doubleSlider.getMin());
tickLine.setTickUnit(doubleSlider.getMajorTickUnit());
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setMinorTickVisible(ticksVisible);
// tickLine.setTickLabelFill(Color.WHITE);
// add 1 to the slider minor tick count since the axis draws one
// less minor ticks than the number given.
tickLine.setMinorTickCount(Math.max(doubleSlider.getMinorTickCount(),0) + 1);
if (doubleSlider.getLabelFormatter() != null){
tickLine.setTickLabelFormatter(new StringConverter<Number>(){
@Override
public String toString(Number object) {
return getSkinnable().getLabelFormatter().toString((Double)object);
}
@Override
public Number fromString(String string) {
return getSkinnable().getLabelFormatter().fromString(string);
}
});
}
getChildren().clear();
getChildren().addAll(tickLine, track, thumb1, thumb2, range);
} else {
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setMinorTickVisible(ticksVisible);
}
}
else {
getChildren().clear();
getChildren().addAll(track, thumb1, thumb2, range);
// tickLine = null;
}
requestLayout();
}
/**
* Called when ever either min, max or value changes, so thumb's layoutX, Y is recomputed.
*/
void positionThumb() {
DoubleSlider s = getSkinnable();
boolean horizontal = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
double lx1 = (horizontal) ? trackStart + (((trackLength * ((s.getValue1() - s.getMin()) /
(s.getMax() - s.getMin()))) - thumbWidth/2)) : thumbLeft + (thumbWidth / 4);
double ly1 = (horizontal) ? thumbTop :
getInsets().getTop() + trackLength - (trackLength * ((s.getValue1() - s.getMin()) /
(s.getMax() - s.getMin()))) + thumbHeight / 4;
double lx2 = (horizontal) ? trackStart + (((trackLength * ((s.getValue2() - s.getMin()) /
(s.getMax() - s.getMin()))) - thumbWidth/2)) : thumbLeft - (thumbWidth / 4);
double ly2 = (horizontal) ? thumbTop :
getInsets().getTop() + trackLength - (trackLength * ((s.getValue2() - s.getMin()) /
(s.getMax() - s.getMin()))) - thumbHeight / 4;
thumb1.setLayoutX(lx1);
thumb1.setLayoutY(ly1);
thumb2.setLayoutX(lx2);
thumb2.setLayoutY(ly2);
if(horizontal){
range.setLayoutX(lx1+thumbWidth/2);
range.setLayoutY(track.getLayoutY());
range.setPrefWidth(lx2-lx1);
range.resize(lx2-lx1, track.getHeight());
} else{
range.setLayoutX(track.getLayoutX());
range.setLayoutY(ly2+3*thumbHeight/4);
range.setPrefHeight(Math.abs(ly2-ly1+thumbHeight/2));
range.resize(track.getWidth(),Math.abs(ly2-ly1+thumbHeight/2));
}
}
@Override protected void layoutChildren() {
// calculate the available space
double x = getInsets().getLeft();
double y = getInsets().getTop();
double w = getWidth() - (getInsets().getLeft() + getInsets().getRight());
double h = getHeight() - (getInsets().getTop() + getInsets().getBottom());
// resize thumb to preferred size
thumbWidth = thumb2.prefWidth(-1);
thumbHeight = thumb2.prefHeight(-1);
// thumb1.resize(thumbWidth, thumbHeight);
thumb1.resize(thumbWidth / 2, thumbHeight);
thumb2.resize(thumbWidth / 2, thumbHeight);
thumb2.setTranslateX(thumbWidth / 2);
// we are assuming the is common radius's for all corners on the track
// double trackRadius = (track.impl_getBackgroundFills() != null && track.impl_getBackgroundFills().size() > 0) ?
// track.impl_getBackgroundFills().get(0).getTopLeftCornerRadius() : 0;
double trackRadius = 0;
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
double tickLineHeight = (showTickMarks) ? tickLine.prefHeight(-1) : 0;
double trackHeight = track.prefHeight(-1);
double trackAreaHeight = Math.max(trackHeight,thumbHeight);
double totalHeightNeeded = trackAreaHeight + ((showTickMarks) ? trackToTickGap + tickLineHeight : 0);
double startY = y + ((h - totalHeightNeeded) / 2); // center slider in available height vertically
trackLength = w - thumbWidth;
trackStart = x + (thumbWidth / 2);
double trackTop = (int)(startY + ((trackAreaHeight - trackHeight) / 2));
thumbTop = (int)(startY + ((trackAreaHeight - thumbHeight) / 2));
// layout track
track.resizeRelocate(trackStart - trackRadius, trackTop , trackLength + trackRadius + trackRadius, trackHeight);
// layout thumbs and range
positionThumb();
// layout tick line
if (showTickMarks) {
tickLine.setLayoutX(trackStart);
tickLine.setLayoutY(trackTop + trackHeight + trackToTickGap);
tickLine.resize(trackLength, tickLineHeight);
tickLine.requestAxisLayout();
} else {
if (tickLine != null) {
tickLine.resize(0,0);
tickLine.requestAxisLayout();
}
tickLine = null;
}
} else {
double tickLineWidth = (showTickMarks) ? tickLine.prefWidth(-1) : 0;
double trackWidth = track.prefWidth(-1);
double trackAreaWidth = Math.max(trackWidth,thumbWidth);
double totalWidthNeeded = trackAreaWidth + ((showTickMarks) ? trackToTickGap + tickLineWidth : 0) ;
double startX = x + ((w - totalWidthNeeded)/2); // center slider in available width horizontally
trackLength = h - thumbHeight;
trackStart = y + (thumbHeight / 2);
double trackLeft = (int)(startX + ((trackAreaWidth - trackWidth) / 2));
thumbLeft = (int)(startX + ((trackAreaWidth - thumbWidth) / 2));
// Thumbs are rotated in vertical orientation (Altug Uzunali)
thumb1.setRotate(-90);
thumb2.setRotate(-90);
// layout track
track.resizeRelocate(trackLeft, trackStart - trackRadius, trackWidth, trackLength + trackRadius + trackRadius);
// layout thumbs and range
positionThumb();
// layout tick line
if (showTickMarks) {
tickLine.setLayoutX(trackLeft + trackWidth + trackToTickGap);
tickLine.setLayoutY(trackStart);
tickLine.resize(tickLineWidth, trackLength);
tickLine.requestAxisLayout();
} else {
if (tickLine != null) {
tickLine.resize(0,0);
tickLine.requestAxisLayout();
}
tickLine = null;
}
}
}
@Override protected void handleControlPropertyChanged(String p) {
super.handleControlPropertyChanged(p);
if ("ORIENTATION".equals(p)) {
if (showTickMarks && tickLine != null) {
tickLine.setSide(getSkinnable().getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (getSkinnable().getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
}
requestLayout();
} else if ("VALUE1".equals(p)) {
positionThumb();
} else if ("VALUE2".equals(p)) {
positionThumb();
} else if ("MIN".equals(p) ) {
if (showTickMarks && tickLine != null) {
tickLine.setLowerBound(getSkinnable().getMin());
}
requestLayout();
} else if ("MAX".equals(p)) {
if (showTickMarks && tickLine != null) {
tickLine.setUpperBound(getSkinnable().getMax());
}
requestLayout();
} else if ("SHOW_TICK_MARKS".equals(p) || "SHOW_TICK_LABELS".equals(p)) {
setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
} else if ("MAJOR_TICK_UNIT".equals(p)) {
if (tickLine != null) {
tickLine.setTickUnit(getSkinnable().getMajorTickUnit());
requestLayout();
}
} else if ("MINOR_TICK_COUNT".equals(p)) {
if (tickLine != null) {
tickLine.setMinorTickCount(Math.max(getSkinnable().getMinorTickCount(),0) + 1);
requestLayout();
}
}
}
double minTrackLength() {
return 2*thumb1.prefWidth(-1);
}
@Override protected double computeMinWidth(double height) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return (getInsets().getLeft() + minTrackLength() + thumb1.minWidth(-1) + getInsets().getRight());
} else {
return(getInsets().getLeft() + thumb1.prefWidth(-1) + getInsets().getRight());
}
}
@Override protected double computeMinHeight(double width) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return(getInsets().getTop() + thumb1.prefHeight(-1) + getInsets().getBottom());
} else {
return(getInsets().getTop() + minTrackLength() + thumb1.prefHeight(-1) + getInsets().getBottom());
}
}
@Override protected double computePrefWidth(double height) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
if(showTickMarks) {
return Math.max(140, tickLine.prefWidth(-1));
} else {
return 140;
}
} else {
//return (padding.getLeft()) + Math.max(thumb.prefWidth(-1), track.prefWidth(-1)) + padding.getRight();
return (getInsets().getLeft()) + Math.max(thumb1.prefWidth(-1), track.prefWidth(-1)) +
((showTickMarks) ? (trackToTickGap+tickLine.prefWidth(-1)) : 0) + getInsets().getRight();
}
}
@Override protected double computePrefHeight(double width) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return getInsets().getTop() + Math.max(thumb1.prefHeight(-1), track.prefHeight(-1)) +
((showTickMarks) ? (trackToTickGap+tickLine.prefHeight(-1)) : 0) + getInsets().getBottom();
} else {
if(showTickMarks) {
return Math.max(140, tickLine.prefHeight(-1));
} else {
return 140;
}
}
}
@Override protected double computeMaxWidth(double height) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return Double.MAX_VALUE;
} else {
return getSkinnable().prefWidth(-1);
}
}
@Override protected double computeMaxHeight(double width) {
if (getSkinnable().getOrientation() == Orientation.HORIZONTAL) {
return getSkinnable().prefHeight(width);
} else {
return Double.MAX_VALUE;
}
}
}