All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.internal.scene.control.gauge.linear.skin.AbstractLinearGaugeSkin Maven / Gradle / Ivy

The newest version!
/**
 * AbstractLinearGaugeSkin.java
 *
 * Copyright (c) 2011-2016, JFXtras
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the organization nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jfxtras.internal.scene.control.gauge.linear.skin;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.beans.property.ObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.SimpleStyleableStringProperty;
import javafx.css.Styleable;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import jfxtras.css.CssMetaDataForSkinProperty;
import jfxtras.scene.control.gauge.linear.AbstractLinearGauge;
import jfxtras.scene.control.gauge.linear.elements.Indicator;
import jfxtras.scene.control.gauge.linear.elements.Marker;
import jfxtras.scene.control.gauge.linear.elements.Segment;

import javafx.css.converter.EnumConverter;
import javafx.css.converter.StringConverter;

/**
 * 
 */
public class AbstractLinearGaugeSkin> extends SkinBase {

	// ==================================================================================================================
	// CONSTRUCTOR
	
	/**
	 * 
	 */
	public AbstractLinearGaugeSkin(C control) {
		super(control);
	}

	// ==================================================================================================================
	// StyleableProperties
	
    /**
     * animated
     */
    public final ObjectProperty animatedProperty() { return animatedProperty; }
    private ObjectProperty animatedProperty = new SimpleStyleableObjectProperty(StyleableProperties.ANIMATED_CSSMETADATA, StyleableProperties.ANIMATED_CSSMETADATA.getInitialValue(null));
    public final void setAnimated(Animated value) { animatedProperty().set(value); }
    public final Animated getAnimated() { return animatedProperty.get(); }
    public final T withAnimated(Animated value) { setAnimated(value); return (T)this; }
    public enum Animated {YES, NO}

    /**
     * valueFormat
     */
    public final SimpleStyleableStringProperty valueFormatProperty() { return valueFormatProperty; }
    private SimpleStyleableStringProperty valueFormatProperty = new SimpleStyleableStringProperty(StyleableProperties.VALUE_FORMAT_CSSMETADATA, StyleableProperties.VALUE_FORMAT_CSSMETADATA.getInitialValue(null)) {
//		{ // anonymous constructor
//			addListener( (invalidationEvent) -> {
//				System.out.println("ValueFormat changed " + valueFormatProperty.get());
//				System.out.println("Style " + getSkinnable().getStyle());
//				new Throwable().printStackTrace();
//			});
//		}
	};
    public final void setValueFormat(String value) { valueFormatProperty.set(value); }
    public final String getValueFormat() { return valueFormatProperty.get(); }
    public final T withValueFormat(String value) { setValueFormat(value); return (T)this; }
    protected String valueFormat(double value) {
    	// TBEERNOT do not create a decimal format every time
    	return new DecimalFormat(getValueFormat()).format(value);
    }
    

    // -------------------------
        
    private static class StyleableProperties 
    {
        private static final CssMetaData, Animated> ANIMATED_CSSMETADATA = new CssMetaDataForSkinProperty, AbstractLinearGaugeSkin, Animated>("-fxx-animated", new EnumConverter(Animated.class), Animated.YES ) {
        	@Override 
        	protected ObjectProperty getProperty(AbstractLinearGaugeSkin s) {
            	return s.animatedProperty;
            }
        };
        
        private static final CssMetaData, String> VALUE_FORMAT_CSSMETADATA = new CssMetaDataForSkinProperty, AbstractLinearGaugeSkin, String>("-fxx-value-format", StringConverter.getInstance(), "0" ) {
        	@Override 
        	protected SimpleStyleableStringProperty getProperty(AbstractLinearGaugeSkin s) {
            	return s.valueFormatProperty;
            }
        };
        
        private static final List> STYLEABLES;
        static  {
            final List> styleables = new ArrayList>(SkinBase.getClassCssMetaData());
            styleables.add(ANIMATED_CSSMETADATA);
            styleables.add(VALUE_FORMAT_CSSMETADATA);
            STYLEABLES = Collections.unmodifiableList(styleables);                
        }
    }
    
    /** 
     * @return The CssMetaData associated with this class, which may include the
     * CssMetaData of its super classes.
     */    
    public static List> getClassCssMetaData() {
        return StyleableProperties.STYLEABLES;
    }

    /**
     * This method should delegate to {@link Node#getClassCssMetaData()} so that
     * a Node's CssMetaData can be accessed without the need for reflection.
     * @return The CssMetaData associated with this node, which may include the
     * CssMetaData of its super classes.
     */
    public List> getCssMetaData() {
        return getClassCssMetaData();
    }
        

    // ==================================================================================================================
	// VALIDATE
	
    /**
     * 
     * @return
     */
	protected String validateValue() {
 		double controlMinValue = getSkinnable().getMinValue();
 		double controlMaxValue = getSkinnable().getMaxValue();
 		double controlValue = getSkinnable().getValue();
		if (controlMinValue > controlMaxValue) {
			return String.format("Min-value (%f) cannot be greater than max-value (%f)", controlMinValue, controlMaxValue);
		}
		if (controlMinValue > controlValue) {
			return String.format("Min-value (%f) cannot be greater than the value (%f)", controlMinValue, controlValue);
		}
		if (controlValue > controlMaxValue) {
			return String.format("Value (%f) cannot be greater than max-value (%f)", controlValue, controlMaxValue);
		}
		return null;
	}
	
	/**
	 * 
	 * @param segment
	 * @return
	 */
	protected String validateSegment(Segment segment) {
 		double controlMinValue = getSkinnable().getMinValue();
 		double controlMaxValue = getSkinnable().getMaxValue();

 		double segmentMinValue = segment.getMinValue();
 		double segmentMaxValue = segment.getMaxValue();
		if (segmentMinValue < controlMinValue) {
			return String.format("Segments min-value (%f) cannot be less than the controls min-value (%f)", segmentMinValue, controlMinValue);
		}
		if (segmentMaxValue > controlMaxValue) {
			return String.format("Segments max-value (%f) cannot be greater than the controls max-value (%f)", segmentMaxValue, controlMaxValue);
		}
 		return null;
	}

	/**
	 * 
	 * @param marker
	 * @return
	 */
	protected String validateMarker(Marker marker) {
 		double controlMinValue = getSkinnable().getMinValue();
 		double controlMaxValue = getSkinnable().getMaxValue();

 		double markerValue = marker.getValue();
		if (markerValue < controlMinValue) {
			return String.format("Marker value (%f) cannot be less than the controls min-value (%f)", markerValue, controlMinValue);
		}
		if (markerValue > controlMaxValue) {
			return String.format("Marker max-value (%f) cannot be greater than the controls max-value (%f)", markerValue, controlMaxValue);
		}
 		return null;
	}

	
	// ==================================================================================================================
	// Value

	abstract protected class AbstractValuePane extends Pane {
		final protected Text valueText = new Text("");
		final protected Pane valueTextPane = new StackPane(valueText);
		final protected Scale valueScale = new Scale(1.0, 1.0);
		final protected Text hiddenText = new Text("");

		/**
		 * 
		 */
		protected AbstractValuePane() {
	        
	        // value text
	        getChildren().add(valueTextPane);
			valueText.getStyleClass().add("value");
			valueTextPane.getTransforms().setAll(valueScale);
			
			// react to changes
			getSkinnable().valueProperty().addListener( (observable) -> {
				if (!validateValueAndHandleInvalid()) {
					return;
				}
				setValueText();
			});
			
	        // min and max value text need to be added to the scene in order to have the CSS applied
			getSkinnable().minValueProperty().addListener( (observable) -> {
				if (!validateValueAndHandleInvalid()) {
					return;
				}
				scaleValueText();
			});
			getSkinnable().maxValueProperty().addListener( (observable) -> {
				if (!validateValueAndHandleInvalid()) {
					return;
				}
				scaleValueText();
			});

			// a hidden text to determine how large the min and max value would be
			hiddenText.getStyleClass().add("value");
	        hiddenText.setVisible(false);
	        getChildren().add(hiddenText);

			// init
			setValueText();
			scaleValueText();
		}
		
		/**
		 * 
		 */
		@Override
		protected void layoutChildren() {
			super.layoutChildren();

	        // layout value
			setValueText();
			scaleValueText();
		}

		/**
		 * 
		 */
		private void setValueText() {
			if (!validateValueAndHandleInvalid()) {
				return;
			}
			valueText.setText(valueFormat(getSkinnable().getValue()));
		}

		/**
		 * The value should automatically fill the needle as much as possible.
		 * But it should not constantly switch font size, so it cannot be based on the current content of value's Text node.
		 * So to determine how much the Text node must be scaled, the calculation is based on value's extremes: min and max value.
		 * The smallest scale factor is the one to use (using the larger would make the other extreme go out of the circle).   
		 */
		protected void scaleValueText() {
		}
	}
	
	protected boolean validateValueAndHandleInvalid() {
		return true;
	}

	// ==================================================================================================================
	// Segments

	/**
	 * Make segments active
	 */
	void activateSegments(Map segmentToNode) {
 		// make those segments active that fall under the needle
		double lValue = getSkinnable().getValue();
 		int cnt = 0;
 		for (Segment segment : segmentToNode.keySet()) {
 			
 			// layout the arc for this segment
 	 		double segmentMinValue = segment.getMinValue();
 	 		double segmentMaxValue = segment.getMaxValue();
 	 		String lSegmentActiveId = "segment" + cnt + "-active";
 	 		String lSegmentIdActiveId = "segment-" + segment.getId() + "-active";
 	 		// remove classes
 	 		getSkinnable().getStyleClass().remove(lSegmentActiveId);
 	 		segmentToNode.get(segment).getStyleClass().remove("segment-active");
 	 		if (segment.getId() != null) {
 	 			getSkinnable().getStyleClass().remove(lSegmentIdActiveId);
 	 		}
 	 		// add classes if active
 	 		if (segmentMinValue <= lValue && lValue <= segmentMaxValue) {
 	 			getSkinnable().getStyleClass().add(lSegmentActiveId);
 	 			segmentToNode.get(segment).getStyleClass().add("segment-active");
	 	 		if (segment.getId() != null) {
	 	 			getSkinnable().getStyleClass().add(lSegmentIdActiveId);
	 	 		}
 	 		}
 			cnt++;
 		}
	}

	// ==================================================================================================================
	// Marker
	
	abstract protected class AbstractMarkerPane extends Pane {

		final protected Map markerToRegion = new HashMap<>();
		
		/**
		 * 
		 */
		protected AbstractMarkerPane() {

			// react to changes in the markers
			getSkinnable().markers().addListener( (ListChangeListener.Change change) -> {
				createAndAddMarkers();
			});
			createAndAddMarkers();
		}
		
		/**
		 * 
		 */
		private void createAndAddMarkers() {
	 		// create the nodes representing each marker
			getChildren().clear();
	 		markerToRegion.clear();
	 		int markerCnt = 0;
	 		for (Marker marker : getSkinnable().markers()) {

	 			// create an svg path for this marker
	 			Region region = new Region();
				getChildren().add(region);
				markerToRegion.put(marker, region);
				
				// setup rotation
				Rotate rotate = new Rotate(0.0);
				rotate.setPivotX(0.0);
				rotate.setPivotY(0.0);
				region.getTransforms().add(rotate);
				
				// setup scaling
				Scale scale = new Scale();
				region.getTransforms().add(scale);
				
				// setup CSS on the path
				region.getStyleClass().addAll("marker", "marker" + markerCnt);
		        if (marker.getId() != null) {
		        	region.setId(marker.getId());
		        }
	 			markerCnt++;
	 		}
		}
		
		/**
		 * 
		 */
		@Override
		protected void layoutChildren() {
			super.layoutChildren();
			
			// layout the markers
	 		for (Marker marker : getSkinnable().markers()) {
	 			String message = validateMarker(marker);
	 			if (message != null) {
	 				new Throwable(message).printStackTrace();
	 				continue;
	 			}

	 			// layout the svg shape 
	 			Region region = markerToRegion.get(marker);
	 			Rotate rotate = (Rotate)region.getTransforms().get(0);
	 			Scale scale = (Scale)region.getTransforms().get(1);
	 			positionAndScaleMarker(marker, rotate, scale);
	 		}
		}

		abstract protected void positionAndScaleMarker(Marker marker, Rotate rotate, Scale scale);
	}
		
	// ==================================================================================================================
	// Indicators
	
	abstract protected class AbstractIndicatorPane extends Pane {
		final private Map indicatorToRegion = new HashMap<>();

		/**
		 * 
		 */
		protected AbstractIndicatorPane() {

			// react to changes in the markers
			getSkinnable().indicators().addListener( (ListChangeListener.Change change) -> {
				createAndAddIndicators();
			});
			createAndAddIndicators();
		}
		
		/**
		 * 
		 */
		private void createAndAddIndicators() {
	 		// create the nodes representing each marker
			getChildren().clear();
			indicatorToRegion.clear();
	 		for (Indicator indicator : getSkinnable().indicators()) {
	 			
	 			// create an svg path for this marker
	 			Region region = new Region();
				getChildren().add(region);
				indicatorToRegion.put(indicator, region);
				
				// setup scaling
				Scale scale = new Scale();
				region.getTransforms().add(scale);
				
				// setup CSS on the path
				region.getStyleClass().addAll("indicator", indicator.getId() + "-indicator");
	        	region.setId(indicator.getId());
	 		}
		}

		/**
		 * 
		 */
		@Override
		protected void layoutChildren() {
			super.layoutChildren();

	 		// size & position the indicators
	 		double scaleFactor = calculateScaleFactor();
	 		for (Indicator indicator : getSkinnable().indicators()) {
	 			
	 			int idx = indicator.getIdx();
	 			Region region = indicatorToRegion.get(indicator);
	 			if (region != null) {
	 				Point2D point2D = calculateLocation(idx);
					if (point2D != null) {
				 		region.layoutXProperty().set(point2D.getX());
				 		region.layoutYProperty().set(point2D.getY());
		 			}
	 				Scale scale = (Scale)region.getTransforms().get(0);
		 			scale.setX(scaleFactor); 
		 			scale.setY(scale.getX()); 
	 			}
	 		}
		}
		
		abstract protected double calculateScaleFactor();
		abstract protected Point2D calculateLocation(int idx);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy