forked from j62/ctbrec
Add range slider for the recording resolution
This commit is contained in:
parent
229fc1f432
commit
6e25f98b2b
|
@ -0,0 +1,46 @@
|
|||
package ctbrec.ui.controls.range;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DiscreteRange<T> implements Range<T> {
|
||||
|
||||
private List<T> values;
|
||||
private List<?> labels;
|
||||
|
||||
|
||||
public DiscreteRange(List<T> values, List<?> labels) {
|
||||
this.values = values;
|
||||
this.labels = labels;
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException("Values cannot be null");
|
||||
}
|
||||
if (labels == null) {
|
||||
throw new IllegalArgumentException("Labels cannot be null");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getMinimum() {
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getMaximum() {
|
||||
return values.get(values.size()-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getTicks() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public List<?> getLabels() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return labels.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package ctbrec.ui.controls.range;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javafx.scene.chart.ValueAxis;
|
||||
|
||||
public class LabeledNumberAxis extends ValueAxis<Number> {
|
||||
|
||||
private DiscreteRange<Number> range;
|
||||
|
||||
public LabeledNumberAxis(DiscreteRange<Number> range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Number> calculateMinorTickMarks() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void setRange(Object range, boolean animate) {
|
||||
if (!(range instanceof DiscreteRange)) {
|
||||
throw new IllegalArgumentException("Range has to be of type DiscreteRange");
|
||||
}
|
||||
this.range = (DiscreteRange<Number>) range;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Number> calculateTickValues(double length, Object range) {
|
||||
if (!(range instanceof Range)) {
|
||||
throw new IllegalArgumentException("Range has to be of type ctbrec.ui.controls.range.Range<T>");
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Range<Number> discreteRange = (Range<Number>) range;
|
||||
return discreteRange.getTicks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTickMarkLabel(Number value) {
|
||||
return range.getLabels().get(value.intValue()).toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package ctbrec.ui.controls.range;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Range<T> {
|
||||
|
||||
public T getMinimum();
|
||||
public T getMaximum();
|
||||
public List<T> getTicks();
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package ctbrec.ui.controls.range;
|
||||
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
public class RangeSlider<T extends Number> extends Control {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "rangeslider";
|
||||
|
||||
private Range<T> range;
|
||||
private DoubleProperty low;
|
||||
private DoubleProperty high;
|
||||
private boolean showTickMarks = false;
|
||||
private boolean showTickLabels = false;
|
||||
private Orientation orientation = Orientation.HORIZONTAL;
|
||||
|
||||
public RangeSlider(Range<T> range) {
|
||||
this.range = range;
|
||||
low = new SimpleDoubleProperty(getMinimum().doubleValue());
|
||||
high = new SimpleDoubleProperty(getMaximum().doubleValue());
|
||||
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new RangeSliderSkin(this, new RangeSliderBehavior<>(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserAgentStylesheet() {
|
||||
return RangeSlider.class.getResource("rangeslider.css").toExternalForm();
|
||||
}
|
||||
|
||||
public DoubleProperty getLow() {
|
||||
return low;
|
||||
}
|
||||
|
||||
public void setLow(T newPosition) {
|
||||
low.set(newPosition.doubleValue());
|
||||
}
|
||||
|
||||
public DoubleProperty getHigh() {
|
||||
return high;
|
||||
}
|
||||
|
||||
public void setHigh(T newPosition) {
|
||||
this.high.set(newPosition.doubleValue());
|
||||
}
|
||||
|
||||
public T getMinimum() {
|
||||
return range.getMinimum();
|
||||
}
|
||||
|
||||
public T getMaximum() {
|
||||
return range.getMaximum();
|
||||
}
|
||||
|
||||
public boolean isShowTickMarks() {
|
||||
return showTickMarks;
|
||||
}
|
||||
|
||||
public void setShowTickMarks(boolean showTickMarks) {
|
||||
this.showTickMarks = showTickMarks;
|
||||
}
|
||||
|
||||
public boolean isShowTickLabels() {
|
||||
return showTickLabels;
|
||||
}
|
||||
|
||||
public void setShowTickLabels(boolean showTickLabels) {
|
||||
this.showTickLabels = showTickLabels;
|
||||
}
|
||||
|
||||
public Orientation getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(Orientation orientation) {
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public Range<T> getRange() {
|
||||
return range;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package ctbrec.ui.controls.range;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.javafx.scene.control.behavior.BehaviorBase; // NOSONAR
|
||||
import com.sun.javafx.scene.control.inputmap.InputMap; // NOSONAR
|
||||
|
||||
public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSlider<T>> {
|
||||
|
||||
private RangeSlider<T> rangeSlider;
|
||||
|
||||
public RangeSliderBehavior(RangeSlider<T> rangeSlider) {
|
||||
super(rangeSlider);
|
||||
this.rangeSlider = rangeSlider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputMap<RangeSlider<T>> getInputMap() {
|
||||
InputMap<RangeSlider<T>> inputMap = new InputMap<>(rangeSlider);
|
||||
return inputMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param position
|
||||
* The mouse position on track with 0.0 being beginning of track and 1.0 being the end
|
||||
*/
|
||||
public void lowThumbDragged(double position) {
|
||||
rangeSlider.setLow(getNewPosition(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param position
|
||||
* The mouse position on track with 0.0 being beginning of track and 1.0 being the end
|
||||
*/
|
||||
public void highThumbDragged(double position) {
|
||||
rangeSlider.setHigh(getNewPosition(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the new position of the thumb given the clicked/dragged position
|
||||
*
|
||||
* @param position
|
||||
* clicked position
|
||||
* @return new position
|
||||
*/
|
||||
private T getNewPosition(double position) {
|
||||
List<T> ticks = rangeSlider.getRange().getTicks();
|
||||
double percentPerTick = 1d / (ticks.size() - 1);
|
||||
int index = (int) Math.round(position / percentPerTick);
|
||||
return ticks.get(index);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
package ctbrec.ui.controls.range;
|
||||
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ValueAxis;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
|
||||
|
||||
private static final double TRACK_TO_TICK_GAP = 2;
|
||||
private Axis<?> tickLine = null;
|
||||
private StackPane track;
|
||||
private ThumbRange thumbRange = new ThumbRange();
|
||||
private RangeSliderBehavior<?> behavior;
|
||||
private double thumbWidth;
|
||||
private double thumbHeight;
|
||||
private boolean showTickMarks;
|
||||
private double trackStart;
|
||||
private double trackLength;
|
||||
private double lowThumbPos;
|
||||
|
||||
private double preDragPos; // used as a temp value for low and high thumbsRange
|
||||
private Point2D preDragThumbPoint; // in skin coordinates
|
||||
|
||||
protected RangeSliderSkin(RangeSlider<?> control, RangeSliderBehavior<?> behavior) {
|
||||
super(control);
|
||||
this.behavior = behavior;
|
||||
initTrack();
|
||||
initThumbs(thumbRange);
|
||||
|
||||
;
|
||||
registerChangeListener(control.getLow(), (obsVal) -> getSkinnable().requestLayout());
|
||||
registerChangeListener(control.getHigh(), (obsVal) -> getSkinnable().requestLayout());
|
||||
}
|
||||
|
||||
private void initThumbs(ThumbRange t) {
|
||||
setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels());
|
||||
|
||||
getChildren().addAll(t.low, t.high, t.rangeBar);
|
||||
|
||||
t.low.setOnMousePressed(me -> {
|
||||
preDragThumbPoint = t.low.localToParent(me.getX(), me.getY());
|
||||
preDragPos = (getSkinnable().getLow().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
|
||||
});
|
||||
|
||||
|
||||
t.low.setOnMouseDragged(me -> {
|
||||
Point2D cur = t.low.localToParent(me.getX(), me.getY());
|
||||
double dragPos = (isHorizontal()) ? cur.getX() - preDragThumbPoint.getX() : -(cur.getY() - preDragThumbPoint.getY());
|
||||
behavior.lowThumbDragged(preDragPos + dragPos / trackLength);
|
||||
});
|
||||
|
||||
t.high.setOnMousePressed(me -> {
|
||||
preDragThumbPoint = t.high.localToParent(me.getX(), me.getY());
|
||||
preDragPos = (getSkinnable().getHigh().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero());
|
||||
});
|
||||
|
||||
|
||||
t.high.setOnMouseDragged(me -> {
|
||||
boolean orientation = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
|
||||
double trackLen = orientation ? track.getWidth() : track.getHeight();
|
||||
Point2D cur = t.high.localToParent(me.getX(), me.getY());
|
||||
double dragPos = getSkinnable().getOrientation() != Orientation.HORIZONTAL ? -(cur.getY() - preDragThumbPoint.getY()) : cur.getX() - preDragThumbPoint.getX();
|
||||
behavior.highThumbDragged(preDragPos + dragPos / trackLen);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isHorizontal() {
|
||||
return getSkinnable().getOrientation() == Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
private void initTrack() {
|
||||
track = new StackPane();
|
||||
track.getStyleClass().setAll("track");
|
||||
|
||||
getChildren().clear();
|
||||
getChildren().add(track);
|
||||
}
|
||||
|
||||
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
|
||||
showTickMarks = (ticksVisible || labelsVisible);
|
||||
var rangeSlider = getSkinnable();
|
||||
if (showTickMarks) {
|
||||
if (tickLine == null) {
|
||||
var range = rangeSlider.getRange();
|
||||
tickLine = createAxis(range, ticksVisible, labelsVisible);
|
||||
getChildren().addAll(tickLine);
|
||||
} else {
|
||||
tickLine.setTickLabelsVisible(labelsVisible);
|
||||
tickLine.setTickMarkVisible(ticksVisible);
|
||||
}
|
||||
}
|
||||
|
||||
getSkinnable().requestLayout();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Axis<?> createAxis(Range<? extends Number> range, boolean ticksVisible, boolean labelsVisible) {
|
||||
ValueAxis<Number> axis;
|
||||
if (range instanceof DiscreteRange) {
|
||||
axis = new LabeledNumberAxis((DiscreteRange<Number>) range);
|
||||
} else {
|
||||
axis = new NumberAxis();
|
||||
}
|
||||
|
||||
axis.setUpperBound(range.getMaximum().doubleValue());
|
||||
axis.setLowerBound(range.getMinimum().doubleValue());
|
||||
axis.setMinorTickVisible(false);
|
||||
axis.setMinorTickCount(0);
|
||||
axis.setAutoRanging(false);
|
||||
axis.setAnimated(false);
|
||||
axis.setSide(isHorizontal() ? Side.BOTTOM : Side.RIGHT);
|
||||
axis.setTickMarkVisible(ticksVisible);
|
||||
axis.setTickLabelsVisible(labelsVisible);
|
||||
return axis;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren(final double x, final double y, final double w, final double h) {
|
||||
if (thumbRange != null) {
|
||||
thumbWidth = thumbRange.low.prefWidth(-1);
|
||||
thumbHeight = thumbRange.low.prefHeight(-1);
|
||||
thumbRange.low.resize(thumbWidth, thumbHeight);
|
||||
thumbRange.high.resize(thumbWidth, thumbHeight);
|
||||
}
|
||||
|
||||
// we are assuming the is common radius's for all corners on the track
|
||||
double trackRadius = track.getBackground() == null ? 0
|
||||
: !track.getBackground().getFills().isEmpty() ? track.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius() : 0;
|
||||
|
||||
double tickLineHeight = (showTickMarks) ? tickLine.prefHeight(-1) : 0;
|
||||
double trackHeight = 5;// track.prefHeight(-1);
|
||||
double trackAreaHeight = Math.max(trackHeight, thumbHeight);
|
||||
double totalHeightNeeded = trackAreaHeight + ((showTickMarks) ? TRACK_TO_TICK_GAP + 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));
|
||||
lowThumbPos = (int) (startY + ((trackAreaHeight - thumbHeight) / 2));
|
||||
|
||||
// layout track
|
||||
track.resizeRelocate(trackStart - trackRadius, trackTop, trackLength + trackRadius + trackRadius, trackHeight);
|
||||
|
||||
positionThumbs();
|
||||
|
||||
if (showTickMarks) {
|
||||
tickLine.setLayoutX(trackStart);
|
||||
tickLine.setLayoutY(trackTop + trackHeight + TRACK_TO_TICK_GAP);
|
||||
tickLine.resize(trackLength, tickLineHeight);
|
||||
tickLine.requestAxisLayout();
|
||||
} else {
|
||||
if (tickLine != null) {
|
||||
tickLine.resize(0, 0);
|
||||
tickLine.requestAxisLayout();
|
||||
}
|
||||
tickLine = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void positionThumbs() {
|
||||
RangeSlider<?> s = getSkinnable();
|
||||
|
||||
double lxl = trackStart + (trackLength * ((s.getLow().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
|
||||
double lxh = trackStart + (trackLength * ((s.getHigh().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
|
||||
double ly = lowThumbPos;
|
||||
|
||||
if (thumbRange != null) {
|
||||
thumbRange.low.setLayoutX(lxl);
|
||||
thumbRange.low.setLayoutY(ly);
|
||||
|
||||
thumbRange.high.setLayoutX(lxh);
|
||||
thumbRange.high.setLayoutY(ly);
|
||||
|
||||
thumbRange.rangeBar.resizeRelocate(thumbRange.low.getLayoutX() + thumbRange.low.getWidth(), track.getLayoutY(),
|
||||
thumbRange.high.getLayoutX() - thumbRange.low.getLayoutX() - thumbRange.low.getWidth(), track.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private double minTrackLength() {
|
||||
return 2 * ((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (isHorizontal()) {
|
||||
return (leftInset + minTrackLength() + ((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1) + rightInset);
|
||||
} else {
|
||||
return (leftInset + ((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1) + rightInset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (isHorizontal()) {
|
||||
return (topInset + ((thumbRange != null) ? thumbRange.low.prefHeight(-1) : 1) + bottomInset);
|
||||
} else {
|
||||
return (topInset + minTrackLength() + ((thumbRange != null) ? thumbRange.low.prefHeight(-1) : 1) + bottomInset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (isHorizontal()) {
|
||||
if (showTickMarks) {
|
||||
double w = Math.max(140, tickLine.prefWidth(-1));
|
||||
System.err.println("computePrefWidth " + w);
|
||||
return w;
|
||||
} else {
|
||||
return 140;
|
||||
}
|
||||
} else {
|
||||
return leftInset + Math.max(((thumbRange != null) ? thumbRange.low.prefWidth(-1) : 1), track.prefWidth(-1))
|
||||
+ ((showTickMarks) ? (TRACK_TO_TICK_GAP + tickLine.prefWidth(-1)) : 0) + rightInset;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (isHorizontal()) {
|
||||
return getSkinnable().getInsets().getTop() + Math.max(((thumbRange != null) ? thumbRange.low.prefHeight(-1) : 1), track.prefHeight(-1))
|
||||
+ ((showTickMarks) ? (TRACK_TO_TICK_GAP + tickLine.prefHeight(-1)) : 0) + bottomInset;
|
||||
} else {
|
||||
if (showTickMarks) {
|
||||
return Math.max(140, tickLine.prefHeight(-1));
|
||||
} else {
|
||||
return 140;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (isHorizontal()) {
|
||||
return Double.MAX_VALUE;
|
||||
} else {
|
||||
return getSkinnable().prefWidth(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
if (isHorizontal()) {
|
||||
return getSkinnable().prefHeight(width);
|
||||
} else {
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private double getMaxMinusMinNoZero() {
|
||||
RangeSlider<?> s = getSkinnable();
|
||||
return s.getMaximum().doubleValue() - s.getMinimum().doubleValue() == 0 ? 1 : s.getMaximum().doubleValue() - s.getMinimum().doubleValue();
|
||||
}
|
||||
|
||||
private static class ThumbPane extends StackPane {
|
||||
}
|
||||
|
||||
private static class ThumbRange {
|
||||
ThumbPane low;
|
||||
ThumbPane high;
|
||||
StackPane rangeBar;
|
||||
|
||||
ThumbRange() {
|
||||
low = new ThumbPane();
|
||||
low.getStyleClass().setAll("low-thumb");
|
||||
low.setFocusTraversable(false);
|
||||
|
||||
high = new ThumbPane();
|
||||
high.getStyleClass().setAll("high-thumb");
|
||||
high.setFocusTraversable(false);
|
||||
|
||||
rangeBar = new StackPane();
|
||||
rangeBar.getStyleClass().setAll("range-bar");
|
||||
rangeBar.setFocusTraversable(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
.rangeslider .low-thumb,
|
||||
.rangeslider .high-thumb {
|
||||
-fx-background-color:
|
||||
linear-gradient(to bottom, derive(-fx-text-box-border, -20%), derive(-fx-text-box-border, -30%)),
|
||||
-fx-inner-border,
|
||||
-fx-body-color;
|
||||
-fx-background-insets: 0, 1, 2;
|
||||
-fx-background-radius: 1.0em; /* makes sure this remains circular */
|
||||
-fx-padding: 0.583333em; /* 7 */
|
||||
-fx-effect: dropshadow(two-pass-box , rgba(0, 0, 0, 0.1), 5, 0.0 , 0, 2);
|
||||
}
|
||||
|
||||
.rangeslider:focused .low-thumb,
|
||||
.rangeslider:focused .high-thumb {
|
||||
-fx-background-radius: 1.0em; /* makes sure this remains circular */
|
||||
}
|
||||
|
||||
.rangeslider .low-thumb:focused,
|
||||
.rangeslider .high-thumb:focused {
|
||||
-fx-background-color:
|
||||
-fx-focus-color,
|
||||
derive(-fx-color,-36%),
|
||||
derive(-fx-color,73%),
|
||||
linear-gradient(to bottom, derive(-fx-color,-19%),derive(-fx-color,61%));
|
||||
-fx-background-insets: -1.4, 0, 1, 2;
|
||||
-fx-background-radius: 1.0em; /* makes sure this remains circular */
|
||||
}
|
||||
|
||||
.rangeslider .low-thumb:hover,
|
||||
.rangeslider .high-thumb:hover {
|
||||
-fx-color: -fx-hover-base;
|
||||
}
|
||||
|
||||
.rangeslider .range-bar {
|
||||
-fx-background-color: -fx-accent;
|
||||
}
|
||||
|
||||
.rangeslider .low-thumb:pressed,
|
||||
.rangeslider .high-thumb:pressed {
|
||||
-fx-color: -fx-pressed-base;
|
||||
}
|
||||
|
||||
.rangeslider .track {
|
||||
-fx-background-color:
|
||||
-fx-shadow-highlight-color,
|
||||
linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
|
||||
linear-gradient(to bottom,
|
||||
derive(-fx-control-inner-background, -9%),
|
||||
derive(-fx-control-inner-background, 0%),
|
||||
derive(-fx-control-inner-background, -5%),
|
||||
derive(-fx-control-inner-background, -12%)
|
||||
);
|
||||
-fx-background-insets: 0 0 -1 0, 0, 1;
|
||||
-fx-background-radius: 0.25em, 0.25em, 0.166667em; /* 3 3 2 */
|
||||
-fx-padding: 0.25em; /* 3 */
|
||||
}
|
||||
|
||||
.rangeslider:vertical .track {
|
||||
-fx-background-color:
|
||||
-fx-shadow-highlight-color,
|
||||
-fx-text-box-border,
|
||||
linear-gradient(to right,
|
||||
derive(-fx-control-inner-background, -9%),
|
||||
-fx-control-inner-background,
|
||||
derive(-fx-control-inner-background, -9%)
|
||||
);
|
||||
}
|
||||
|
||||
.rangeslider .axis {
|
||||
-fx-tick-label-fill: derive(-fx-text-background-color, 30%);
|
||||
-fx-tick-length: 5px;
|
||||
-fx-minor-tick-length: 3px;
|
||||
-fx-border-color: null;
|
||||
}
|
||||
|
||||
.rangeslider:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
}
|
|
@ -36,6 +36,8 @@ import ctbrec.ui.SiteUiFactory;
|
|||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.controls.DirectorySelectionBox;
|
||||
import ctbrec.ui.controls.ProgramSelectionBox;
|
||||
import ctbrec.ui.controls.range.DiscreteRange;
|
||||
import ctbrec.ui.controls.range.RangeSlider;
|
||||
import ctbrec.ui.sites.ConfigUI;
|
||||
import ctbrec.ui.tabs.TabSelectionListener;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -95,7 +97,6 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private CheckBox removeRecordingAfterPp = new CheckBox();
|
||||
private RadioButton recordLocal;
|
||||
private ProxySettingsPane proxySettingsPane;
|
||||
private TextField maxResolution;
|
||||
private TextField concurrentRecordings;
|
||||
private ComboBox<SplitAfterOption> splitAfter;
|
||||
private ComboBox<DirectoryStructure> directoryStructure;
|
||||
|
@ -346,29 +347,32 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
GridPane.setMargin(l, new Insets(0, 0, 0, 0));
|
||||
GridPane.setMargin(splitAfter, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||
|
||||
l = new Label("Maximum resolution (0 = unlimited)");
|
||||
Tooltip tt = new Tooltip("video height, e.g. 720 or 1080\n!Caution: If the resolution is unknown, ctbrec will not record the stream!");
|
||||
l = new Label("Restrict Resolution");
|
||||
Tooltip tt = new Tooltip("Only record streams with resolution within the given range");
|
||||
l.setTooltip(tt);
|
||||
layout.add(l, 0, row);
|
||||
maxResolution = new TextField(Integer.toString(Config.getInstance().getSettings().maximumResolution));
|
||||
maxResolution.setTooltip(tt);
|
||||
maxResolution.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue.matches("\\d*")) {
|
||||
maxResolution.setText(newValue.replaceAll(PATTERN_NOT_A_DIGIT, ""));
|
||||
}
|
||||
if (!maxResolution.getText().isEmpty()) {
|
||||
int newRes = Integer.parseInt(maxResolution.getText());
|
||||
if (newRes != Config.getInstance().getSettings().maximumResolution) {
|
||||
Config.getInstance().getSettings().maximumResolution = newRes;
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
});
|
||||
maxResolution.prefWidthProperty().bind(directoryStructure.widthProperty());
|
||||
layout.add(maxResolution, 1, row++);
|
||||
List<Integer> labels = Arrays.asList(0, 240, 360, 480, 600, 720, 960, 1080, 1440, 2160, 4320, 8640);
|
||||
List<Integer> values = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
||||
DiscreteRange<Integer> rangeValues = new DiscreteRange<>(values, labels);
|
||||
RangeSlider<Integer> resolutionRange = new RangeSlider<>(rangeValues);
|
||||
resolutionRange.prefWidthProperty().bind(directoryStructure.widthProperty());
|
||||
resolutionRange.setShowTickMarks(true);
|
||||
resolutionRange.setShowTickLabels(true);
|
||||
resolutionRange.setLow(labels.indexOf(Config.getInstance().getSettings().minimumResolution));
|
||||
resolutionRange.setHigh(labels.indexOf(Config.getInstance().getSettings().maximumResolution));
|
||||
layout.add(resolutionRange, 1, row++);
|
||||
GridPane.setMargin(l, new Insets(0, 0, 0, 0));
|
||||
GridPane.setColumnSpan(maxResolution, 3);
|
||||
GridPane.setMargin(maxResolution, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||
GridPane.setColumnSpan(resolutionRange, 3);
|
||||
GridPane.setMargin(resolutionRange, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||
resolutionRange.getLow().addListener((obs, oldV, newV) -> {
|
||||
Config.getInstance().getSettings().minimumResolution = labels.get(newV.intValue());
|
||||
saveConfig();
|
||||
});
|
||||
resolutionRange.getHigh().addListener((obs, oldV, newV) -> {
|
||||
Config.getInstance().getSettings().maximumResolution = labels.get(newV.intValue());
|
||||
saveConfig();
|
||||
});
|
||||
|
||||
|
||||
l = new Label("Concurrent Recordings (0 = unlimited)");
|
||||
layout.add(l, 0, row);
|
||||
|
@ -626,12 +630,6 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
layout.add(mediaPlayer, 1, row);
|
||||
Button mediaPlayerParamsButton = new Button("⚙");
|
||||
mediaPlayerParamsButton.setOnAction(e -> {
|
||||
// Optional<String> playerParams = Dialogs.showTextInput(mediaPlayerParamsButton.getScene(), "Media Player Parameters",
|
||||
// "Media Player Start Parameters", settings.mediaPlayerParams);
|
||||
// playerParams.ifPresent(p -> {
|
||||
// settings.mediaPlayerParams = p;
|
||||
// saveConfig();
|
||||
// });
|
||||
PlayerSettingsDialog dialog = new PlayerSettingsDialog(getTabPane().getScene(), Config.getInstance());
|
||||
Optional<Exception> exception = dialog.showAndWait();
|
||||
if (exception.isPresent()) {
|
||||
|
@ -809,7 +807,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
useTLS.setDisable(local);
|
||||
recordingsDirectory.setDisable(!local);
|
||||
splitAfter.setDisable(!local);
|
||||
maxResolution.setDisable(!local);
|
||||
//maxResolution.setDisable(!local);
|
||||
directoryStructure.setDisable(!local);
|
||||
onlineCheckIntervalInSecs.setDisable(!local);
|
||||
leaveSpaceOnDevice.setDisable(!local);
|
||||
|
|
|
@ -69,6 +69,7 @@ public class Settings {
|
|||
public String livejasminUsername = "";
|
||||
public boolean livePreviews = false;
|
||||
public boolean localRecording = true;
|
||||
public int minimumResolution = 0;
|
||||
public int maximumResolution = 0;
|
||||
public int maximumResolutionPlayer = 0;
|
||||
public String mediaPlayer = "/usr/bin/mpv";
|
||||
|
|
|
@ -336,10 +336,11 @@ public class DashDownload extends AbstractDownload {
|
|||
|
||||
private AdaptationSetType chooseBestVideo(List<AdaptationSetType> videoStreams) {
|
||||
AdaptationSetType best = null;
|
||||
int minHeight = config.getSettings().minimumResolution;
|
||||
int maxHeight = config.getSettings().maximumResolution;
|
||||
long bestHeight = 0;
|
||||
for (AdaptationSetType stream : videoStreams) {
|
||||
if (stream.getHeight() > bestHeight && (maxHeight == 0 || stream.getHeight() <= maxHeight)) {
|
||||
if (stream.getHeight() > bestHeight && (minHeight == 0 || stream.getHeight() >= minHeight) && (maxHeight == 0 || stream.getHeight() <= maxHeight)) {
|
||||
bestHeight = stream.getHeight();
|
||||
best = stream;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -22,6 +21,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.JAXBException;
|
||||
|
||||
|
@ -161,21 +161,18 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
|
|||
url = streamSources.get(model.getStreamUrlIndex()).getMediaPlaylistUrl();
|
||||
} else {
|
||||
// filter out stream resolutions, which are too high
|
||||
int minRes = Config.getInstance().getSettings().minimumResolution;
|
||||
int maxRes = Config.getInstance().getSettings().maximumResolution;
|
||||
if (maxRes > 0) {
|
||||
for (Iterator<StreamSource> iterator = streamSources.iterator(); iterator.hasNext();) {
|
||||
StreamSource streamSource = iterator.next();
|
||||
if (streamSource.height > 0 && maxRes < streamSource.height) {
|
||||
LOG.trace("Res too high {} > {}", streamSource.height, maxRes);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (streamSources.isEmpty()) {
|
||||
List<StreamSource> filteredStreamSources = streamSources.stream()
|
||||
.filter(src -> src.height == 0 || minRes <= src.height)
|
||||
.filter(src -> src.height == 0 || maxRes >= src.height)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (filteredStreamSources.isEmpty()) {
|
||||
throw new ExecutionException(new RuntimeException("No stream left in playlist"));
|
||||
} else {
|
||||
LOG.debug("{} selected {}", model.getName(), streamSources.get(streamSources.size() - 1));
|
||||
url = streamSources.get(streamSources.size() - 1).getMediaPlaylistUrl();
|
||||
LOG.debug("{} selected {}", model.getName(), filteredStreamSources.get(filteredStreamSources.size() - 1));
|
||||
url = filteredStreamSources.get(filteredStreamSources.size() - 1).getMediaPlaylistUrl();
|
||||
}
|
||||
}
|
||||
LOG.debug("Segment playlist url {}", url);
|
||||
|
|
|
@ -55,6 +55,7 @@ public class ConfigServlet extends AbstractCtbrecServlet {
|
|||
addParameter("httpUserAgent", "User-Agent", DataType.STRING, settings.httpUserAgent, json);
|
||||
addParameter("httpUserAgentMobile", "Mobile User-Agent", DataType.STRING, settings.httpUserAgentMobile, json);
|
||||
addParameter("generatePlaylist", "Generate Playlist", DataType.BOOLEAN, settings.generatePlaylist, json);
|
||||
addParameter("minimumResolution", "Minimum Resolution", DataType.INTEGER, settings.minimumResolution, json);
|
||||
addParameter("maximumResolution", "Maximum Resolution", DataType.INTEGER, settings.maximumResolution, json);
|
||||
addParameter("minimumLengthInSeconds", "Minimum Length (secs)", DataType.INTEGER, settings.minimumLengthInSeconds, json);
|
||||
addParameter("minimumSpaceLeftInBytes", "Leave Space On Device (GiB)", DataType.LONG, settings.minimumSpaceLeftInBytes, json);
|
||||
|
|
Loading…
Reference in New Issue