├── docs
├── index.adoc
└── images
│ └── demo.png
├── unitfx
├── .gitignore
├── src
│ └── main
│ │ ├── resources
│ │ └── com
│ │ │ └── dlsc
│ │ │ └── unitfx
│ │ │ ├── number-input-field.css
│ │ │ └── quantity-input-field.css
│ │ └── java
│ │ ├── com
│ │ └── dlsc
│ │ │ └── unitfx
│ │ │ ├── util
│ │ │ ├── Constants.java
│ │ │ ├── QuantitiesUtil.java
│ │ │ ├── ControlsUtil.java
│ │ │ └── Units.java
│ │ │ ├── DoubleInputField.java
│ │ │ ├── IntegerInputField.java
│ │ │ ├── CustomTextField.java
│ │ │ ├── skins
│ │ │ ├── CustomTextFieldSkin.java
│ │ │ └── QuantityInputFieldSkin.java
│ │ │ ├── QuantityInputControl.java
│ │ │ ├── NumberInputField.java
│ │ │ └── QuantityInputField.java
│ │ └── module-info.java
└── pom.xml
├── unitfx-demo
├── .gitignore
├── src
│ └── main
│ │ └── java
│ │ ├── module-info.java
│ │ └── com
│ │ └── dlsc
│ │ └── unitfx
│ │ └── demo
│ │ └── DemoApp.java
└── pom.xml
├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── .github
└── workflows
│ ├── build.yml
│ └── release.yml
├── CHANGELOG.md
├── pom.xml
├── mvnw.cmd
├── mvnw
└── LICENSE
/docs/index.adoc:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/unitfx/.gitignore:
--------------------------------------------------------------------------------
1 | target
--------------------------------------------------------------------------------
/unitfx-demo/.gitignore:
--------------------------------------------------------------------------------
1 | target
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | target/
3 | *.iml
--------------------------------------------------------------------------------
/docs/images/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlsc-software-consulting-gmbh/UnitFX/HEAD/docs/images/demo.png
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlsc-software-consulting-gmbh/UnitFX/HEAD/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/unitfx/src/main/resources/com/dlsc/unitfx/number-input-field.css:
--------------------------------------------------------------------------------
1 | .number-input-field:invalid {
2 | -fx-text-fill: red;
3 | -fx-border-color: red;
4 | }
--------------------------------------------------------------------------------
/unitfx-demo/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.dlsc.unitfx.demo {
2 | requires com.dlsc.unitfx;
3 | requires java.measure;
4 |
5 | exports com.dlsc.unitfx.demo;
6 | }
--------------------------------------------------------------------------------
/unitfx/src/main/java/com/dlsc/unitfx/util/Constants.java:
--------------------------------------------------------------------------------
1 | package com.dlsc.unitfx.util;
2 |
3 | public interface Constants {
4 |
5 | double CARDINAL_STEP = 45;
6 | double MAX_DEGREE = 360;
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/unitfx/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.dlsc.unitfx {
2 | requires transitive javafx.controls;
3 | requires tech.units.indriya;
4 | requires tech.uom.lib.common;
5 |
6 | exports com.dlsc.unitfx;
7 | exports com.dlsc.unitfx.skins;
8 | exports com.dlsc.unitfx.util;
9 |
10 | opens com.dlsc.unitfx.skins;
11 |
12 | }
--------------------------------------------------------------------------------
/unitfx/src/main/java/com/dlsc/unitfx/DoubleInputField.java:
--------------------------------------------------------------------------------
1 | package com.dlsc.unitfx;
2 |
3 | import javafx.util.converter.DoubleStringConverter;
4 |
5 | /**
6 | * Concrete implementation of {@link NumberInputField} for collecting {@link Double} type numbers.
7 | */
8 | public class DoubleInputField extends NumberInputField
37 | * Examples:
38 | * The type of Quantity to be created.
23 | * @return The quantity instance, {@code null} if either the value param or unit param is null.
24 | */
25 | public static
> Quantity
createQuantity(Number value, Unit
unit) {
26 | Quantity
valueQuantity = null;
27 | if (value != null && unit != null) {
28 | valueQuantity = Quantities.getQuantity(value, unit);
29 | }
30 | return valueQuantity;
31 | }
32 |
33 | /**
34 | * Truncates the given quantity value to the given precision, meaning that the value is floor rounded always.
35 | *
36 | *
39 | *
42 | *
The quantity type. 47 | * @return A new quantity instance having the value truncated. 48 | */ 49 | public static> QuantitytruncateQuantity(Quantityvalue, Quantityprecision) { 50 | QuantityvalueInSystemUnit = value.toSystemUnit(); 51 | QuantityprecisionInSystemUnit = precision.toSystemUnit(); 52 | 53 | Quantity> divideResult = valueInSystemUnit.divide(precisionInSystemUnit); 54 | 55 | QuantitytruncatedValueInPrecisionUnits = precision.multiply(divideResult.getValue().intValue()); 56 | QuantitytruncatedValueInValueUnits = truncatedValueInPrecisionUnits.to(value.getUnit()); 57 | 58 | return truncatedValueInValueUnits; 59 | } 60 | 61 | /** 62 | * Applies a rounding mechanism that returns the nearest value according to the precision. 63 | * 64 | *65 | * Examples: 66 | *
67 | *
71 | * 72 | * 73 | * @param value 74 | * @param precision 75 | * @param- Quantity(115 Metre), Precision(10 Metre) = Truncated(120 Metre)
68 | *- Quantity(88 Metre), Precision(5 Metre) = Truncated(90 Metre)
69 | *- Quantity(52 Metre), Precision(5 Metre) = Truncated(50 Metre)
70 | *76 | * @return 77 | */ 78 | public static> QuantityroundQuantity(Quantityvalue, Quantityprecision) { 79 | QuantityvalueInSystemUnit = value.toSystemUnit(); 80 | QuantityprecisionInSystemUnit = precision.toSystemUnit(); 81 | 82 | Quantity> divideResult = valueInSystemUnit.divide(precisionInSystemUnit); 83 | 84 | long multiplier = Math.round(divideResult.getValue().doubleValue()); 85 | 86 | QuantitytruncatedValueInPrecisionUnits = precision.multiply(multiplier); 87 | QuantitytruncatedValueInValueUnits = truncatedValueInPrecisionUnits.to(value.getUnit()); 88 | 89 | return truncatedValueInValueUnits; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/util/ControlsUtil.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx.util; 2 | 3 | import javafx.beans.value.ObservableValue; 4 | import javafx.css.PseudoClass; 5 | import javafx.geometry.Point2D; 6 | import javafx.scene.Node; 7 | 8 | 9 | /** 10 | * Utility methods for JavaFX controls. 11 | */ 12 | public final class ControlsUtil { 13 | 14 | private ControlsUtil() { 15 | } 16 | 17 | /** 18 | * Binds a boolean property to a style class in the given node. Doing this the style class is switched on/off 19 | * depending on the boolean property. 20 | * 21 | * @param node The node which the style class will be applied to. 22 | * @param booleanProperty The flag to switch on/off. 23 | * @param styleClass The style class to be applied. 24 | */ 25 | public static void bindBooleanToStyleClass(Node node, ObservableValuebooleanProperty, String styleClass) { 26 | booleanProperty.addListener((obs, oldV, newV) -> { 27 | if (Boolean.TRUE.equals(booleanProperty.getValue())) { 28 | if (!node.getStyleClass().contains(styleClass)) { 29 | node.getStyleClass().add(styleClass); 30 | } 31 | } else { 32 | node.getStyleClass().remove(styleClass); 33 | } 34 | }); 35 | } 36 | 37 | /** 38 | * Binds a boolean property to a pseudo class. Doing this the pseudo class is switched on/off in the given node. 39 | * 40 | * @param node The node which the pseudo class will be applied to. 41 | * @param booleanProperty The flag to switch on/off. 42 | * @param pseudoClass The style class to be applied. 43 | */ 44 | public static void bindBooleanToPseudoclass(Node node, ObservableValue booleanProperty, PseudoClass pseudoClass) { 45 | booleanProperty.addListener((obs, oldV, newV) -> node.pseudoClassStateChanged(pseudoClass, Boolean.TRUE.equals(booleanProperty.getValue()))); 46 | } 47 | 48 | /** 49 | * Calculates the distance between two angles in degrees, the result is a number between 0 - 180. 50 | * @param alpha The initial angle. 51 | * @param beta The final angle. 52 | * @return The distance between the angles. 53 | */ 54 | public static double distance(double alpha, double beta) { 55 | double phi = Math.abs(beta - alpha) % 360; 56 | return phi > 180 ? 360 - phi : phi; 57 | } 58 | 59 | /** 60 | * Calculates the rotation, which means the signed distance between two angles. 61 | * @param alpha The initial angle. 62 | * @param beta The final angle. 63 | * @return The rotation between the angles. 64 | */ 65 | public static double rotation(double alpha, double beta) { 66 | double distance = distance(alpha, beta); 67 | int sign = (alpha - beta >= 0 && alpha - beta <= 180) || (alpha - beta <=-180 && alpha - beta >= -360) ? 1 : -1; 68 | return distance * sign; 69 | } 70 | 71 | /** 72 | * Calculates the x,y position of the given angle in relation with the circle represented by the middle point and radius. 73 | * 74 | * @param middle The middle of the circle in pixel representation. 75 | * @param radius The radius of the circle. 76 | * @param angle The angle for which the point is going to be calculated. 77 | * @return The point calculated. 78 | */ 79 | public static Point2D calculatePointOnCircle(Point2D middle, double radius, double angle) { 80 | double newAngle = Math.abs(angle) % Constants.MAX_DEGREE; 81 | if (angle < 0) { 82 | newAngle = Constants.MAX_DEGREE - newAngle; 83 | } 84 | 85 | final double radians = Math.toRadians(newAngle); 86 | 87 | double tempX = radius * Math.cos(radians); 88 | double tempY = radius * Math.sin(radians); 89 | 90 | double x = middle.getX(); 91 | double y = middle.getY(); 92 | 93 | x += tempX; 94 | 95 | y = switchPixelAndCartesian(y); // to cartesian y 96 | y += tempY; 97 | y = switchPixelAndCartesian(y); // to pixel y 98 | 99 | return new Point2D(x, y); 100 | } 101 | 102 | private static double switchPixelAndCartesian(double value) { 103 | return value == 0 ? value : -value; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/util/Units.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx.util; 2 | 3 | import com.dlsc.unitfx.QuantityInputField; 4 | import tech.units.indriya.AbstractSystemOfUnits; 5 | import tech.units.indriya.AbstractUnit; 6 | import tech.units.indriya.format.UnitStyle; 7 | import tech.units.indriya.function.MultiplyConverter; 8 | import tech.units.indriya.unit.AlternateUnit; 9 | import tech.units.indriya.unit.TransformedUnit; 10 | 11 | import javax.measure.MetricPrefix; 12 | import javax.measure.Quantity; 13 | import javax.measure.Unit; 14 | import javax.measure.quantity.Angle; 15 | import javax.measure.quantity.Length; 16 | import javax.measure.quantity.Mass; 17 | import javax.measure.quantity.Speed; 18 | import javax.measure.quantity.Temperature; 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | /** 27 | * Customized system of units that incorporates all available units in all dimensions supported by 28 | * 29 | * the {@link QuantityInputField}. 30 | */ 31 | public final class Units extends AbstractSystemOfUnits { 32 | 33 | private static final Units INSTANCE = new Units(); 34 | 35 | public static Units getInstance() { 36 | return INSTANCE; 37 | } 38 | 39 | // Mass units 40 | public static final Unit GRAM = addUnit(Mass.class, tech.units.indriya.unit.Units.GRAM, null, true); 41 | public static final Unit MILLIGRAM = addUnit(Mass.class, MetricPrefix.MILLI(GRAM), null, false); 42 | public static final Unit KILOGRAM = addUnit(Mass.class, MetricPrefix.KILO(GRAM), null, false); 43 | 44 | 45 | // Length Units 46 | public static final Unit METRE = addUnit(Length.class, tech.units.indriya.unit.Units.METRE, null, true); 47 | public static final Unit CENTIMETRE = addUnit(Length.class, MetricPrefix.CENTI(METRE), null, false); 48 | public static final Unit MILLIMETRE = addUnit(Length.class, MetricPrefix.MILLI(METRE), null, false); 49 | public static final Unit KILOMETRE = addUnit(Length.class, MetricPrefix.KILO(METRE), null, false); 50 | public static final Unit FOOT = addUnit(Length.class, new TransformedUnit<>("ft", METRE, MultiplyConverter.ofRational(3048, 10000)), "ft", false); 51 | public static final Unit INCH = addUnit(Length.class, new TransformedUnit<>("in", FOOT, MultiplyConverter.ofRational(1, 12)), "in", false); 52 | public static final Unit NAUTICAL_MILE = addUnit(Length.class, new TransformedUnit<>("nm", METRE, MultiplyConverter.of(1852)), "nm", false); 53 | 54 | 55 | // Temperature 56 | public static final Unit CELSIUS = addUnit(Temperature.class, tech.units.indriya.unit.Units.CELSIUS, null, true); 57 | public static final Unit KELVIN = addUnit(Temperature.class, tech.units.indriya.unit.Units.KELVIN, null, false); 58 | 59 | 60 | // Angle 61 | public static final Unit DEGREE = addUnit(Angle.class, new AlternateUnit<>(AbstractUnit.ONE, "d"), null, true); 62 | 63 | 64 | // Speed 65 | public static final Unit METRE_PER_SECOND = addUnit(Speed.class, tech.units.indriya.unit.Units.METRE_PER_SECOND, null, false); 66 | public static final Unit KILOMETRE_PER_HOUR = addUnit(Speed.class, tech.units.indriya.unit.Units.KILOMETRE_PER_HOUR, null, true); 67 | public static final Unit KNOT = addUnit(Speed.class, NAUTICAL_MILE.divide(tech.units.indriya.unit.Units.HOUR).asType(Speed.class), "kt", false); 68 | 69 | 70 | private static > UnitaddUnit(Classtype, Unitunit, String symbol, boolean baseUnit) { 71 | List> units = INSTANCE.quantityToUnits.computeIfAbsent(type, t -> new ArrayList<>()); 72 | units.add(unit); 73 | if (baseUnit) { 74 | INSTANCE.quantityToUnit.put(type, unit); 75 | } 76 | 77 | if (symbol == null) { 78 | return Helper.addUnit(INSTANCE.units, unit, unit.toString()); 79 | } 80 | 81 | return Helper.addUnit(INSTANCE.units, unit, symbol, symbol, UnitStyle.SYMBOL_AND_LABEL); 82 | } 83 | 84 | private final Map >, List >> quantityToUnits = new HashMap<>(); 85 | 86 | private Units() { 87 | super(); 88 | } 89 | 90 | @Override 91 | public String getName() { 92 | return Units.class.getSimpleName(); 93 | } 94 | 95 | /** 96 | * Allows to get the list of units registered for the given quantity type. 97 | * 98 | * @param type The quantity type class. 99 | * @param The quantity type. 100 | * @return The list of registered units in this system of units. 101 | */ 102 | @SuppressWarnings("unchecked") 103 | public> List> getUnits(Class type) { 104 | @SuppressWarnings("rawtypes") 105 | List units = Collections.unmodifiableList(Optional 106 | .ofNullable(quantityToUnits.get(type)) 107 | .orElse(Collections.emptyList()) 108 | ); 109 | return units; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/skins/CustomTextFieldSkin.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx.skins; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.css.PseudoClass; 5 | import javafx.geometry.Pos; 6 | import javafx.scene.Node; 7 | import javafx.scene.control.TextField; 8 | import javafx.scene.control.skin.TextFieldSkin; 9 | import javafx.scene.layout.StackPane; 10 | import javafx.scene.text.HitInfo; 11 | 12 | public abstract class CustomTextFieldSkin extends TextFieldSkin { 13 | 14 | private static final PseudoClass HAS_NO_SIDE_NODE = PseudoClass.getPseudoClass("no-side-nodes"); //$NON-NLS-1$ 15 | private static final PseudoClass HAS_LEFT_NODE = PseudoClass.getPseudoClass("left-node-visible"); //$NON-NLS-1$ 16 | private static final PseudoClass HAS_RIGHT_NODE = PseudoClass.getPseudoClass("right-node-visible"); //$NON-NLS-1$ 17 | 18 | private Node left; 19 | private StackPane leftPane; 20 | private Node right; 21 | private StackPane rightPane; 22 | 23 | private final TextField control; 24 | 25 | public CustomTextFieldSkin(final TextField control) { 26 | super(control); 27 | this.control = control; 28 | updateChildren(); 29 | 30 | leftProperty().addListener(it -> updateChildren()); 31 | rightProperty().addListener(it -> updateChildren()); 32 | 33 | } 34 | 35 | public abstract ObjectPropertyleftProperty(); 36 | public abstract ObjectProperty rightProperty(); 37 | 38 | private void updateChildren() { 39 | Node newLeft = leftProperty().get(); 40 | if (newLeft != null) { 41 | getChildren().remove(leftPane); 42 | leftPane = new StackPane(newLeft); 43 | leftPane.setManaged(false); 44 | leftPane.setAlignment(Pos.CENTER_LEFT); 45 | leftPane.getStyleClass().add("left-pane"); //$NON-NLS-1$ 46 | getChildren().add(leftPane); 47 | left = newLeft; 48 | } 49 | Node newRight = rightProperty().get(); 50 | if (newRight != null) { 51 | getChildren().remove(rightPane); 52 | rightPane = new StackPane(newRight); 53 | rightPane.setManaged(false); 54 | rightPane.setAlignment(Pos.CENTER_RIGHT); 55 | rightPane.getStyleClass().add("right-pane"); //$NON-NLS-1$ 56 | getChildren().add(rightPane); 57 | right = newRight; 58 | } 59 | 60 | control.pseudoClassStateChanged(HAS_LEFT_NODE, left != null); 61 | control.pseudoClassStateChanged(HAS_RIGHT_NODE, right != null); 62 | control.pseudoClassStateChanged(HAS_NO_SIDE_NODE, left == null && right == null); 63 | } 64 | 65 | @Override protected void layoutChildren(double x, double y, double w, double h) { 66 | final double fullHeight = h + snappedTopInset() + snappedBottomInset(); 67 | 68 | final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.prefWidth(fullHeight)); 69 | final double rightWidth = rightPane == null ? 0.0 : snapSize(rightPane.prefWidth(fullHeight)); 70 | 71 | final double textFieldStartX = snapPosition(x) + snapSize(leftWidth); 72 | final double textFieldWidth = w - snapSize(leftWidth) - snapSize(rightWidth); 73 | 74 | super.layoutChildren(textFieldStartX, 0, textFieldWidth, fullHeight); 75 | 76 | if (leftPane != null) { 77 | final double leftStartX = 0; 78 | leftPane.resizeRelocate(leftStartX, 0, leftWidth, fullHeight); 79 | } 80 | 81 | if (rightPane != null) { 82 | final double rightStartX = rightPane == null ? 0.0 : w - rightWidth + snappedLeftInset(); 83 | rightPane.resizeRelocate(rightStartX, 0, rightWidth, fullHeight); 84 | } 85 | } 86 | 87 | @Override 88 | public HitInfo getIndex(double x, double y) { 89 | /** 90 | * This resolves https://bitbucket.org/controlsfx/controlsfx/issue/476 91 | * when we have a left Node and the click point is badly returned 92 | * because we weren't considering the shift induced by the leftPane. 93 | */ 94 | final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.prefWidth(getSkinnable().getHeight())); 95 | return super.getIndex(x - leftWidth, y); 96 | } 97 | 98 | @Override 99 | protected double computePrefWidth(double h, double topInset, double rightInset, double bottomInset, double leftInset) { 100 | final double pw = super.computePrefWidth(h, topInset, rightInset, bottomInset, leftInset); 101 | final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.prefWidth(h)); 102 | final double rightWidth = rightPane == null ? 0.0 : snapSize(rightPane.prefWidth(h)); 103 | 104 | return pw + leftWidth + rightWidth; 105 | } 106 | 107 | @Override 108 | protected double computePrefHeight(double w, double topInset, double rightInset, double bottomInset, double leftInset) { 109 | final double ph = super.computePrefHeight(w, topInset, rightInset, bottomInset, leftInset); 110 | final double leftHeight = leftPane == null ? 0.0 : snapSize(leftPane.prefHeight(-1)); 111 | final double rightHeight = rightPane == null ? 0.0 : snapSize(rightPane.prefHeight(-1)); 112 | 113 | return Math.max(ph, Math.max(leftHeight, rightHeight)); 114 | } 115 | 116 | @Override 117 | protected double computeMinWidth(double h, double topInset, double rightInset, double bottomInset, double leftInset) { 118 | final double mw = super.computeMinWidth(h, topInset, rightInset, bottomInset, leftInset); 119 | final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.minWidth(h)); 120 | final double rightWidth = rightPane == null ? 0.0 : snapSize(rightPane.minWidth(h)); 121 | 122 | return mw + leftWidth + rightWidth; 123 | } 124 | 125 | @Override 126 | protected double computeMinHeight(double w, double topInset, double rightInset, double bottomInset, double leftInset) { 127 | final double mh = super.computeMinHeight(w, topInset, rightInset, bottomInset, leftInset); 128 | final double leftHeight = leftPane == null ? 0.0 : snapSize(leftPane.minHeight(-1)); 129 | final double rightHeight = rightPane == null ? 0.0 : snapSize(rightPane.minHeight(-1)); 130 | 131 | return Math.max(mh, Math.max(leftHeight, rightHeight)); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/QuantityInputControl.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx; 2 | 3 | import com.dlsc.unitfx.util.QuantitiesUtil; 4 | import javafx.beans.InvalidationListener; 5 | import javafx.beans.property.BooleanProperty; 6 | import javafx.beans.property.IntegerProperty; 7 | import javafx.beans.property.ObjectProperty; 8 | import javafx.beans.property.ReadOnlyObjectProperty; 9 | import javafx.beans.property.ReadOnlyObjectWrapper; 10 | import javafx.beans.property.SimpleBooleanProperty; 11 | import javafx.beans.property.SimpleIntegerProperty; 12 | import javafx.beans.property.SimpleObjectProperty; 13 | import javafx.collections.FXCollections; 14 | import javafx.collections.ObservableList; 15 | import javafx.scene.control.Control; 16 | import javafx.util.StringConverter; 17 | 18 | import javax.measure.Quantity; 19 | import javax.measure.Unit; 20 | 21 | /** 22 | * Base class for any quantity input control that allows to enter Unit of Measurement quantity based on a JSR-363 23 | * specification. The input field is Double type based, so the value is a double property. 24 | * 25 | * @param In the JSR specification represents a quantity type in conjunction with a {@link javax.measure.Dimension}. 26 | * @see Quantity 27 | * @see Unit 28 | * @see javax.measure.Dimension 29 | * @see QuantityInputField 30 | */ 31 | public abstract class QuantityInputControl> extends Control { 32 | 33 | /** 34 | * Instances a new input field with no available units, null value and no precision. 35 | */ 36 | public QuantityInputControl() { 37 | bindQuantityValueProperty(); 38 | listenForDefaultUnit(); 39 | } 40 | 41 | /** 42 | * Represents the quantitative amount in the specific unit. Basically represents the number introduced by the 43 | * user. 44 | * @return A double object being the value. 45 | */ 46 | public final ObjectPropertyvalueProperty() { 47 | return value; 48 | } 49 | private final ObjectProperty value = new SimpleObjectProperty<>(this, "value"); 50 | public final Double getValue() { return valueProperty().get(); } 51 | public final void setValue(Double value) { valueProperty().set(value); } 52 | 53 | 54 | /** 55 | * The selected type for the quantity value. 56 | * 57 | * @return A unit object which represent the selected format. 58 | */ 59 | public final ObjectProperty > unitProperty() { 60 | return unit; 61 | } 62 | private final ObjectProperty > unit = new SimpleObjectProperty<>(this, "unit"); 63 | public final Unit getUnit() { return unitProperty().get(); } 64 | public final void setUnit(Unitunit) { unitProperty().set(unit); } 65 | 66 | 67 | /** 68 | * Represents the quantity which is the combination of {@link #valueProperty()} and {@link #unitProperty()}. This 69 | * value is calculated automatically by the control. It is refreshed every time the value or the unit change. 70 | * @return The read only property storing the quantity. {@code null} if no value or unit has been set. 71 | */ 72 | public final ReadOnlyObjectProperty> valueQuantityProperty() { return valueQuantity.getReadOnlyProperty(); } 73 | private final ReadOnlyObjectWrapper > valueQuantity = new ReadOnlyObjectWrapper<>(this, "quantityValue"); 74 | public final Quantity getValueQuantity() { return valueQuantityProperty().get(); } 75 | void setValueQuantity(QuantityvalueQuantity) { this.valueQuantity.set(valueQuantity); } 76 | 77 | 78 | /** 79 | * The list of available units supported within the control, this is the list of units the user has available 80 | * to select from. 81 | * @return The list of units available in the control. 82 | */ 83 | public final ObservableList> getAvailableUnits() { 84 | return availableUnits; 85 | } 86 | private final ObservableList > availableUnits = FXCollections.observableArrayList(); 87 | 88 | 89 | /** 90 | * Represents the system base unit, which is the default one for the control. If the {@link #unitProperty() unit} 91 | * selected is different to the base unit, the control will indicate visually (colored) that ambiguity. If no 92 | * base unit is set, not color effect will be applied in the skin. 93 | * 94 | * @return The base unit. 95 | */ 96 | public final ObjectProperty > baseUnitProperty() { 97 | return baseUnit; 98 | } 99 | private final ObjectProperty > baseUnit = new SimpleObjectProperty<>(this, "baseUnit"); 100 | public final Unit getBaseUnit() { return baseUnitProperty().get(); } 101 | public final void setBaseUnit(UnitbaseUnit) { baseUnitProperty().set(baseUnit); } 102 | 103 | 104 | /** 105 | * Boolean property used to restrict the edition in the control. If this is set to {@code true} the control will be 106 | * disabled. 107 | * @return The boolean property. 108 | */ 109 | public final BooleanProperty readOnlyProperty() { 110 | return readOnly; 111 | } 112 | private final BooleanProperty readOnly = new SimpleBooleanProperty(this, "readOnly"); 113 | public final boolean isReadOnly() { return readOnlyProperty().get(); } 114 | public final void setReadOnly(boolean readOnly) { readOnlyProperty().set(readOnly); } 115 | 116 | 117 | /** 118 | * String converter that allows customization of unit label on the skin. 119 | * @return The property storing string converter. 120 | */ 121 | public final ObjectProperty>> unitStringConverterProperty() { return unitStringConverter; } 122 | private final ObjectProperty >> unitStringConverter = new SimpleObjectProperty<>(this, "unitStringConverter"); 123 | public final StringConverter > getUnitStringConverter() { return unitStringConverterProperty().get(); } 124 | public final void setUnitStringConverter(StringConverter > unitStringConverter) { unitStringConverterProperty().set(unitStringConverter); } 125 | 126 | 127 | 128 | /** 129 | * The maximum digits in the integer part of the number. 130 | * @return The maximum value. 131 | */ 132 | public final IntegerProperty numberOfIntegersProperty() { return numberOfIntegers; } 133 | private final IntegerProperty numberOfIntegers = new SimpleIntegerProperty(this, "numberOfIntegers", 40); 134 | public final int getNumberOfIntegers() { return numberOfIntegersProperty().get(); } 135 | public final void setNumberOfIntegers(int numberOfIntegers) { numberOfIntegersProperty().set(numberOfIntegers); } 136 | 137 | 138 | /** 139 | * The maximum digits in the decimal part of the number. 140 | * @return The maximum value. 141 | */ 142 | public final IntegerProperty numberOfDecimalsProperty() { return numberOfDecimals; } 143 | private final IntegerProperty numberOfDecimals = new SimpleIntegerProperty(this, "numberOfDecimals", 3); 144 | public final int getNumberOfDecimals() { return numberOfDecimalsProperty().get(); } 145 | public final void setNumberOfDecimals(int numberOfDecimals) { numberOfDecimalsProperty().set(numberOfDecimals); } 146 | 147 | 148 | // listeners 149 | 150 | private void bindQuantityValueProperty() { 151 | InvalidationListener listener = obs -> updateValueQuantity(); 152 | valueProperty().addListener(listener); 153 | unitProperty().addListener(listener); 154 | } 155 | 156 | private void listenForDefaultUnit() { 157 | baseUnitProperty().addListener((obs, oldV, newV) -> updateDefaultUnit(newV)); 158 | } 159 | 160 | void updateValueQuantity() { 161 | Quantity quantity = QuantitiesUtil.createQuantity(getValue(), getUnit()); 162 | setValueQuantity(quantity); 163 | } 164 | 165 | void updateDefaultUnit(UnitbaseUnit) { 166 | if (getUnit() == null) { 167 | setUnit(baseUnit); 168 | } 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/skins/QuantityInputFieldSkin.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx.skins; 2 | 3 | import com.dlsc.unitfx.DoubleInputField; 4 | import com.dlsc.unitfx.QuantityInputField; 5 | import javafx.beans.InvalidationListener; 6 | import javafx.beans.binding.Bindings; 7 | import javafx.beans.property.BooleanProperty; 8 | import javafx.beans.property.SimpleBooleanProperty; 9 | import javafx.scene.control.ComboBox; 10 | import javafx.scene.control.ContentDisplay; 11 | import javafx.scene.control.Label; 12 | import javafx.scene.control.Labeled; 13 | import javafx.scene.control.ListCell; 14 | import javafx.scene.control.SkinBase; 15 | import javafx.scene.input.KeyCode; 16 | import javafx.scene.input.KeyEvent; 17 | import javafx.scene.layout.Region; 18 | 19 | import javax.measure.Quantity; 20 | import javax.measure.Unit; 21 | 22 | public class QuantityInputFieldSkin> extends SkinBase> { 23 | 24 | private final DoubleInputField editor; 25 | private final ComboBox > switcher; 26 | private final Label editorDisabled; 27 | private final Label switcherDisabled; 28 | 29 | public QuantityInputFieldSkin(QuantityInputField control) { 30 | super(control); 31 | 32 | Region dirtyIcon = new Region(); 33 | dirtyIcon.getStyleClass().add("dirty-icon"); 34 | dirtyIcon.visibleProperty().bind(control.valueDirtyProperty()); 35 | dirtyIcon.managedProperty().bind(dirtyIcon.visibleProperty()); 36 | dirtyIcon.setOnMouseClicked(evt -> control.restoreValueProperty()); 37 | 38 | editor = new DoubleInputField(); 39 | editor.getStyleClass().add("editor"); 40 | editor.setLeft(dirtyIcon); 41 | editor.valueProperty().bindBidirectional(control.valueProperty()); 42 | editor.numberOfIntegersProperty().bind(control.numberOfIntegersProperty()); 43 | editor.numberOfDecimalsProperty().bind(control.numberOfDecimalsProperty()); 44 | editor.allowNegativesProperty().bind(control.allowNegativesProperty()); 45 | editor.minimumValueProperty().bind(control.minimumValueProperty()); 46 | editor.maximumValueProperty().bind(control.maximumValueProperty()); 47 | editor.addEventHandler(KeyEvent.KEY_PRESSED, evt -> { 48 | if (evt.getCode() == KeyCode.ENTER) { 49 | control.restoreValueProperty(); 50 | } 51 | }); 52 | editor.focusedProperty().addListener((obs, oldV, newV) -> { 53 | if (!newV) { 54 | control.restoreValueProperty(); 55 | } 56 | }); 57 | editor.validatorProperty().bind(control.valueValidatorProperty()); 58 | editor.invalidProperty().addListener(obs -> control.getProperties().put("invalid", editor.isInvalid())); 59 | 60 | switcher = new ComboBox<>(); 61 | switcher.getStyleClass().add("unit-switcher"); 62 | switcher.setItems(control.getAvailableUnits()); 63 | switcher.valueProperty().bindBidirectional(control.unitProperty()); 64 | switcher.converterProperty().bind(control.unitStringConverterProperty()); 65 | switcher.setButtonCell(new UnitListCell()); 66 | switcher.setCellFactory(lv -> new UnitListCell()); 67 | 68 | editorDisabled = new Label(); 69 | editorDisabled.getStyleClass().add("editor"); 70 | editorDisabled.getStyleClass().add("editor-disabled"); 71 | editorDisabled.textProperty().bind(editor.textProperty()); 72 | 73 | BooleanProperty unitNotBaseUnit = new SimpleBooleanProperty(); 74 | InvalidationListener unitsListener = obs -> { 75 | UnitbaseUnit = control.getBaseUnit(); 76 | Unitunit = control.getUnit(); 77 | unitNotBaseUnit.set(unit != null && baseUnit != null && !unit.equals(baseUnit)); 78 | }; 79 | control.baseUnitProperty().addListener(unitsListener); 80 | control.unitProperty().addListener(unitsListener); 81 | unitsListener.invalidated(null); 82 | Label unitLbl = new Label(); 83 | unitLbl.textProperty().bind(Bindings.createStringBinding(() -> convertUnitToString(control.getUnit()), control.unitProperty())); 84 | decorateUnitLabel(unitLbl, unitNotBaseUnit); 85 | switcherDisabled = new Label(); 86 | switcherDisabled.setGraphic(unitLbl); 87 | switcherDisabled.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 88 | switcherDisabled.getStyleClass().add("unit-switcher"); 89 | switcherDisabled.getStyleClass().add("unit-switcher-disabled"); 90 | 91 | updateChildren(); 92 | control.readOnlyProperty().addListener(obs -> updateChildren()); 93 | control.autoFixValueProperty().addListener(obs -> updateChildren()); 94 | } 95 | 96 | private void updateChildren() { 97 | if (getSkinnable().isReadOnly()) { 98 | if (getSkinnable().isAutoFixValue()) { 99 | getChildren().setAll(editorDisabled, switcher); 100 | } 101 | else { 102 | getChildren().setAll(editorDisabled, switcherDisabled); 103 | } 104 | } 105 | else { 106 | getChildren().setAll(editor, switcher); 107 | } 108 | } 109 | 110 | private String convertUnitToString(Unitunit) { 111 | String text = ""; 112 | if (getSkinnable().getUnitStringConverter() != null) { 113 | text = getSkinnable().getUnitStringConverter().toString(unit); 114 | } 115 | else if (unit != null) { 116 | text = unit.toString(); 117 | } 118 | return text; 119 | } 120 | 121 | @Override 122 | protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { 123 | final double switcherWidth = snapSize(switcher.prefWidth(-1)); 124 | 125 | final double fieldX = snapPosition(contentX); 126 | final double fieldY = snapPosition(contentY); 127 | final double fieldWidth = snapSize(contentWidth); 128 | final double fieldHeight = snapSize(contentHeight); 129 | 130 | final double textBoxWidth = fieldWidth - switcherWidth; 131 | final double unitBoxX = fieldWidth - switcherWidth; 132 | 133 | editorDisabled.resizeRelocate(fieldX, fieldY, textBoxWidth, fieldHeight); 134 | switcherDisabled.resizeRelocate(unitBoxX, fieldY, switcherWidth, fieldHeight); 135 | 136 | editor.resizeRelocate(fieldX, fieldY, textBoxWidth, fieldHeight); 137 | switcher.resizeRelocate(unitBoxX, fieldY, switcherWidth, fieldHeight); 138 | } 139 | 140 | 141 | private class UnitListCell extends ListCell> { 142 | 143 | private final Label icon; 144 | private final BooleanProperty itemNoBaseUnit = new SimpleBooleanProperty(); 145 | 146 | UnitListCell() { 147 | icon = decorateUnitLabel(this, itemNoBaseUnit); 148 | InvalidationListener listener = obs -> { 149 | Unit baseUnit = getSkinnable().getBaseUnit(); 150 | Unitunit = getItem(); 151 | itemNoBaseUnit.set(unit != null && baseUnit != null && !unit.equals(baseUnit)); 152 | }; 153 | getSkinnable().baseUnitProperty().addListener(listener); 154 | itemProperty().addListener(listener); 155 | getStyleClass().add("unit-cell"); 156 | } 157 | 158 | @Override 159 | protected void updateItem(Unititem, boolean empty) { 160 | super.updateItem(item, empty); 161 | if (item != null && !empty) { 162 | setText(convertUnitToString(item)); 163 | setGraphic(icon); 164 | } 165 | else { 166 | setText(null); 167 | setGraphic(null); 168 | } 169 | } 170 | } 171 | 172 | 173 | private static Label decorateUnitLabel(Labeled unitLabel, BooleanProperty iconVisibleProperty) { 174 | Label icon = new Label(); 175 | icon.getStyleClass().add("indicator"); 176 | icon.setPrefWidth(5); 177 | icon.visibleProperty().bind(iconVisibleProperty); 178 | unitLabel.setGraphic(icon); 179 | unitLabel.setGraphicTextGap(5); 180 | unitLabel.getStyleClass().add("unit-label"); 181 | return icon; 182 | } 183 | 184 | } -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/NumberInputField.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx; 2 | 3 | import com.dlsc.unitfx.util.ControlsUtil; 4 | import javafx.beans.InvalidationListener; 5 | import javafx.beans.property.BooleanProperty; 6 | import javafx.beans.property.IntegerProperty; 7 | import javafx.beans.property.ObjectProperty; 8 | import javafx.beans.property.ReadOnlyBooleanProperty; 9 | import javafx.beans.property.ReadOnlyBooleanWrapper; 10 | import javafx.beans.property.SimpleBooleanProperty; 11 | import javafx.beans.property.SimpleIntegerProperty; 12 | import javafx.beans.property.SimpleObjectProperty; 13 | import javafx.css.PseudoClass; 14 | import javafx.scene.control.TextFormatter; 15 | import javafx.util.converter.NumberStringConverter; 16 | 17 | import java.lang.reflect.ParameterizedType; 18 | import java.text.NumberFormat; 19 | import java.text.ParsePosition; 20 | import java.util.function.Predicate; 21 | import java.util.function.UnaryOperator; 22 | 23 | /** 24 | * Base control to number inputs, defines all base functionality to collect any object whose class is 25 | * child of {@link Number}. 26 | * 27 | * @paramThe type of number. 28 | */ 29 | public abstract class NumberInputField extends CustomTextField { 30 | 31 | /** 32 | * Instances a new number input with with {@code null} {@link #valueProperty() value}, no 33 | * {@link #validatorProperty() validator} and allowing negatives. 34 | */ 35 | public NumberInputField() { 36 | NumberStringFilteredConverter converter = new NumberStringFilteredConverter(); 37 | setTextFormatter(new TextFormatter<>(converter, null, converter.getFilter())); 38 | 39 | listenForValueChanges(); 40 | listenForTextOrValidationChanges(); 41 | listenForAllowNegativeChanges(); 42 | 43 | getStyleClass().add("number-input-field"); 44 | ControlsUtil.bindBooleanToPseudoclass(this, invalidProperty(), PseudoClass.getPseudoClass("invalid")); 45 | } 46 | 47 | /** 48 | * Converts the text to the input field number type object. 49 | * @param text The text to be converted. 50 | * @return In case of impossible conversion, return {@code null} instead of throwing exception. 51 | */ 52 | protected abstract T convertTextToNumber(String text); 53 | 54 | /** 55 | * Converts the input number type object to string. 56 | * 57 | * @param number The number to be converted. 58 | * @return If the number is {@code null}, then return empty string. 59 | */ 60 | protected abstract String convertNumberToText(T number); 61 | 62 | /** 63 | * The number value representation of the text written. 64 | * @return The number value. 65 | */ 66 | public final ObjectProperty valueProperty() { return value; } 67 | private final ObjectProperty value = new SimpleObjectProperty<>(this, "value"); 68 | public final T getValue() { return valueProperty().get(); } 69 | public final void setValue(T value) { valueProperty().set(value); } 70 | 71 | 72 | /** 73 | * Boolean property that tells the control to allow introducing a negative number. 74 | * @return The boolean property. 75 | */ 76 | public final BooleanProperty allowNegativesProperty() { return allowNegatives; } 77 | private final BooleanProperty allowNegatives = new SimpleBooleanProperty(this, "allowNegatives", true); 78 | public final boolean isAllowNegatives() { return allowNegativesProperty().get(); } 79 | public final void setAllowNegatives(boolean allowNegatives) { allowNegativesProperty().set(allowNegatives); } 80 | 81 | 82 | /** 83 | * Stores a validator object for the value converted from text entered. If the value entered is not valid, a pseudo 84 | * class is added to the field, called ":invalid". This can be used for styling the field to indicate invalid inputs. 85 | * @return The property storing the validator predicate. 86 | */ 87 | public final ObjectProperty > validatorProperty() { return validator; } 88 | private final ObjectProperty > validator = new SimpleObjectProperty<>(this, "validator"); 89 | public final Predicate getValidator() { return validatorProperty().get(); } 90 | public final void setValidator(Predicate validator) { validatorProperty().set(validator); } 91 | 92 | 93 | /** 94 | * Property that indicates whether the {@link #valueProperty() value} is valid after being validated by 95 | * {@link #validatorProperty() validator}. 96 | * @return The read only boolean property. 97 | */ 98 | public final ReadOnlyBooleanProperty invalidProperty() { return invalid.getReadOnlyProperty(); } 99 | private final ReadOnlyBooleanWrapper invalid = new ReadOnlyBooleanWrapper(this, "invalid"); 100 | public final boolean isInvalid() { return invalid.get(); } 101 | private final void setInvalid(boolean invalid) { this.invalid.set(invalid); } 102 | 103 | 104 | /** 105 | * The maximum digits in the integer part of the number. 106 | * @return The maximum value. 107 | */ 108 | public final IntegerProperty numberOfIntegersProperty() { return numberOfIntegers; } 109 | private final IntegerProperty numberOfIntegers = new SimpleIntegerProperty(this, "numberOfIntegers", 40); 110 | public final int getNumberOfIntegers() { return numberOfIntegersProperty().get(); } 111 | public final void setNumberOfIntegers(int numberOfIntegers) { numberOfIntegersProperty().set(numberOfIntegers); } 112 | 113 | 114 | /** 115 | * The maximum digits in the decimal part of the number. 116 | * @return The maximum value. 117 | */ 118 | public final IntegerProperty numberOfDecimalsProperty() { return numberOfDecimals; } 119 | private final IntegerProperty numberOfDecimals = new SimpleIntegerProperty(this, "numberOfDecimals", 3); 120 | public final int getNumberOfDecimals() { return numberOfDecimalsProperty().get(); } 121 | public final void setNumberOfDecimals(int numberOfDecimals) { numberOfDecimalsProperty().set(numberOfDecimals); } 122 | 123 | 124 | /** 125 | * The minimum value that can be entered in this field. If this property is set, once the user enters a number 126 | * lower than this minimum, the field becomes invalid and the {@link #valueProperty()} is set to {@code null}. 127 | * @return The minimum value. 128 | */ 129 | public final ObjectProperty minimumValueProperty() { return minimumValue; } 130 | private final ObjectProperty minimumValue = new SimpleObjectProperty<>(this, "minimumValue"); 131 | public final T getMinimumValue() { return minimumValueProperty().get(); } 132 | public final void setMinimumValue(T minimumValue) { minimumValueProperty().set(minimumValue); } 133 | 134 | 135 | /** 136 | * The maximum value that can be entered in this field. If this property is set, once the user enters a number 137 | * greater than this minimum, the field becomes invalid and the {@link #valueProperty()} is set to {@code null}. 138 | * @return The maximum value. 139 | */ 140 | public final ObjectProperty maximumValueProperty() { return maximumValue; } 141 | private final ObjectProperty maximumValue = new SimpleObjectProperty<>(this, "maximumValue"); 142 | public final T getMaximumValue() { return maximumValueProperty().get(); } 143 | public final void setMaximumValue(T maximumValue) { maximumValueProperty().set(maximumValue); } 144 | 145 | 146 | /** 147 | * Number string converter that provides a filter for text changes. 148 | */ 149 | class NumberStringFilteredConverter extends NumberStringConverter { 150 | 151 | NumberStringFilteredConverter() { 152 | super(isIntegerTypedField() ? NumberFormat.getIntegerInstance() : NumberFormat.getNumberInstance()); 153 | NumberFormat nFormat = getNumberFormat(); 154 | nFormat.setGroupingUsed(false); 155 | numberOfIntegersProperty().addListener(obs -> nFormat.setMaximumIntegerDigits(getNumberOfIntegers())); 156 | numberOfDecimalsProperty().addListener(obs -> nFormat.setMaximumFractionDigits(getNumberOfDecimals())); 157 | } 158 | 159 | UnaryOperator getFilter() { 160 | return change -> { 161 | String newText = change.getControlNewText(); 162 | if (newText.isEmpty()) { 163 | return change; 164 | } 165 | 166 | if (isAllowNegatives()) { 167 | if (newText.equals("-")) { 168 | return change; 169 | } 170 | 171 | if (newText.startsWith("-")) { 172 | newText = newText.substring(1); 173 | if (newText.startsWith("-")) { 174 | return null; 175 | } 176 | } 177 | } 178 | else if (newText.startsWith("-")) { 179 | return null; 180 | } 181 | 182 | ParsePosition parsePosition = new ParsePosition( 0); 183 | Number number = getNumberFormat().parse(newText, parsePosition); 184 | if (number == null || parsePosition.getIndex() < newText.length()) { 185 | return null; 186 | } 187 | 188 | return change; 189 | }; 190 | } 191 | } 192 | 193 | 194 | // listeners 195 | 196 | private boolean updatingValue; 197 | 198 | private void listenForValueChanges() { 199 | valueProperty().addListener((obs, oldV, newV) -> { 200 | if (!updatingValue) { 201 | setText(convertNumberToText(newV)); 202 | } 203 | }); 204 | } 205 | 206 | private void listenForTextOrValidationChanges() { 207 | InvalidationListener textListener = obs -> { 208 | try { 209 | updatingValue = true; 210 | T number = convertTextToNumber(getText()); 211 | setInvalid(isInvalidNumber(number)); 212 | setValue(!isInvalid() ? number : null); 213 | } 214 | finally { 215 | updatingValue = false; 216 | } 217 | }; 218 | 219 | textProperty().addListener(textListener); 220 | minimumValueProperty().addListener(textListener); 221 | maximumValueProperty().addListener(textListener); 222 | validatorProperty().addListener(textListener); 223 | } 224 | 225 | private void listenForAllowNegativeChanges() { 226 | allowNegativesProperty().addListener((obs, oldV, newV) -> { 227 | if (!newV && getValue() != null && getValue().doubleValue() < 0) { 228 | setValue(null); 229 | } 230 | }); 231 | } 232 | 233 | private boolean isInvalidNumber(T number) { 234 | boolean invalid = false; 235 | 236 | if (getValidator() != null) { 237 | invalid = !getValidator().test(number); 238 | } 239 | 240 | if (getMinimumValue() != null && number != null) { 241 | invalid |= getMinimumValue().doubleValue() > number.doubleValue(); 242 | } 243 | 244 | if (getMaximumValue() != null && number != null) { 245 | invalid |= getMaximumValue().doubleValue() < number.doubleValue(); 246 | } 247 | 248 | return invalid; 249 | } 250 | 251 | @SuppressWarnings("unchecked") 252 | private boolean isIntegerTypedField() { 253 | Class typeClass = (Class ) ((ParameterizedType) NumberInputField.this.getClass() 254 | .getGenericSuperclass()) 255 | .getActualTypeArguments()[0]; 256 | 257 | return Integer.class.equals(typeClass) || 258 | Long.class.equals(typeClass) || 259 | Short.class.equals(typeClass) || 260 | Byte.class.equals(typeClass); 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /unitfx/src/main/java/com/dlsc/unitfx/QuantityInputField.java: -------------------------------------------------------------------------------- 1 | package com.dlsc.unitfx; 2 | 3 | import com.dlsc.unitfx.skins.QuantityInputFieldSkin; 4 | import com.dlsc.unitfx.util.ControlsUtil; 5 | import com.dlsc.unitfx.util.QuantitiesUtil; 6 | import javafx.beans.InvalidationListener; 7 | import javafx.beans.property.BooleanProperty; 8 | import javafx.beans.property.ObjectProperty; 9 | import javafx.beans.property.ReadOnlyBooleanProperty; 10 | import javafx.beans.property.ReadOnlyBooleanWrapper; 11 | import javafx.beans.property.ReadOnlyObjectProperty; 12 | import javafx.beans.property.ReadOnlyObjectWrapper; 13 | import javafx.beans.property.SimpleBooleanProperty; 14 | import javafx.beans.property.SimpleObjectProperty; 15 | import javafx.collections.MapChangeListener; 16 | import javafx.css.PseudoClass; 17 | import javafx.scene.control.Skin; 18 | 19 | import javax.measure.Quantity; 20 | import javax.measure.Unit; 21 | import java.util.function.Predicate; 22 | 23 | /** 24 | * Concrete implementation of {@link QuantityInputControl} that uses a {@link NumberInputField< Double >} to let users enter 25 | * values. 26 | */ 27 | public class QuantityInputField > extends QuantityInputControl{ 28 | 29 | /** 30 | * Instances a new input field with no available units, null value and no precision. 31 | */ 32 | public QuantityInputField() { 33 | bindQuantityValueProperty(); 34 | bindPrecisionQuantityProperty(); 35 | bindValueDirtyProperty(); 36 | listenForInvalidChanges(); 37 | listenForAutoFixProperty(); 38 | 39 | ControlsUtil.bindBooleanToPseudoclass(this, valueDirtyProperty(), PseudoClass.getPseudoClass("dirty")); 40 | getStyleClass().add("quantity-input-field"); 41 | } 42 | 43 | @Override 44 | protected Skin> createDefaultSkin() { 45 | return new QuantityInputFieldSkin<>(this); 46 | } 47 | 48 | @Override 49 | public String getUserAgentStylesheet() { 50 | return QuantityInputField.class.getResource("quantity-input-field.css").toExternalForm(); 51 | } 52 | 53 | /** 54 | * Applies the {@link #valueQuantityProperty()} to the {@link #valueProperty()} simulating a restore operation. This 55 | * method not only cleans the value but also cleans the {@link #valueDirtyProperty()}. 56 | */ 57 | public final void restoreValueProperty() { 58 | Quantityquantity = getValueQuantity(); 59 | if (quantity != null) { 60 | setValue(quantity.getValue().doubleValue()); 61 | setValueDirty(false); 62 | } 63 | } 64 | 65 | 66 | /** 67 | * Integer used to truncate the {@link #valueProperty() value} entered by the user and calculate the 68 | * {@link #valueQuantityProperty() quantity}. Default precision is '1' which means no truncation. 69 | * Bellow some examples: 70 | * 71 | *72 | *
77 | * @return 78 | */ 79 | public final ObjectProperty- Value=340
73 | *- Unit=Metre
74 | *- Precision=100
75 | *- ValueQuantity=300 Metre
76 | *precisionProperty() { 80 | return precision; 81 | } 82 | private final ObjectProperty precision = new SimpleObjectProperty (this, "precision") { 83 | @Override 84 | public void set(Double newValue) { 85 | if (newValue != null && newValue <= 0) { 86 | throw new IllegalArgumentException("Precision must be greater than 0."); 87 | } 88 | super.set(newValue); 89 | } 90 | }; 91 | public final Double getPrecision() { return precisionProperty().get(); } 92 | public final void setPrecision(Double precision) { precisionProperty().set(precision); } 93 | 94 | 95 | /** 96 | * Represents the system base unit, which is the default one for the control. If the {@link #unitProperty() unit} 97 | * selected is different to the base unit, the control will indicate visually (colored) that ambiguity. If no 98 | * base unit is set, not color effect will be applied in the skin. 99 | * 100 | * @return The base unit. 101 | */ 102 | public final ObjectProperty > precisionUnitProperty() { 103 | return precisionUnit; 104 | } 105 | private final ObjectProperty > precisionUnit = new SimpleObjectProperty<>(this, "precisionUnit"); 106 | public final Unit getPrecisionUnit() { return precisionUnitProperty().get(); } 107 | public final void setPrecisionUnit(UnitprecisionUnit) { precisionUnitProperty().set(precisionUnit); } 108 | 109 | 110 | /** 111 | * Represents the precision as combination of {@link #precisionProperty()} ()} and {@link #precisionUnitProperty()}. 112 | * This value is calculated automatically by the control. It is refreshed every time the precision or the precision 113 | * unit change. 114 | * @return The read only property storing the precision quantity. 115 | */ 116 | public final ReadOnlyObjectProperty> precisionQuantityProperty() { return precisionQuantity.getReadOnlyProperty(); } 117 | private final ReadOnlyObjectWrapper > precisionQuantity = new ReadOnlyObjectWrapper<>(this, "precisionQuantity"); 118 | public final Quantity getPrecisionQuantity() { return precisionQuantityProperty().get(); } 119 | private void setPrecisionQuantity(QuantityprecisionQuantity) { this.precisionQuantity.set(precisionQuantity); } 120 | 121 | 122 | /** 123 | * Boolean property that indicates when the {@link #valueProperty() value} and the 124 | * {@link #valueQuantityProperty() valueQuantity} are different because after a precision was applied. 125 | * 126 | * @return The boolean property. 127 | */ 128 | public final ReadOnlyBooleanProperty valueDirtyProperty() { 129 | return valueDirty.getReadOnlyProperty(); 130 | } 131 | private final ReadOnlyBooleanWrapper valueDirty = new ReadOnlyBooleanWrapper(this, "valueDirty"); 132 | public final boolean isValueDirty() { return valueDirtyProperty().get(); } 133 | private void setValueDirty(boolean valueDirty) { this.valueDirty.set(valueDirty); } 134 | 135 | 136 | /** 137 | * Boolean property that tells the control to allow introducing a negative quantity. 138 | * @return The boolean property. 139 | */ 140 | public final BooleanProperty allowNegativesProperty() { return allowNegatives; } 141 | private final BooleanProperty allowNegatives = new SimpleBooleanProperty(this, "allowNegatives"); 142 | public final boolean isAllowNegatives() { return allowNegativesProperty().get(); } 143 | public final void setAllowNegatives(boolean allowNegatives) { allowNegativesProperty().set(allowNegatives); } 144 | 145 | 146 | /** 147 | * The minimum value that can be entered in this field. If this property is set, once the user enters a number 148 | * lower than this minimum the field becomes invalid and the {@link #valueProperty()} is set to {@code null}. 149 | * @return The minimum value. 150 | */ 151 | public final ObjectPropertyminimumValueProperty() { return minimumValue; } 152 | private final ObjectProperty minimumValue = new SimpleObjectProperty<>(this, "minimumValue"); 153 | public final Double getMinimumValue() { return minimumValueProperty().get(); } 154 | public final void setMinimumValue(Double minimumValue) { minimumValueProperty().set(minimumValue); } 155 | 156 | 157 | /** 158 | * The maximum value that can be entered in this field. If this property is set, once the user enters a number 159 | * greater than this minimum the field becomes invalid and the {@link #valueProperty()} is set to {@code null}. 160 | * @return The maximum value. 161 | */ 162 | public final ObjectProperty maximumValueProperty() { return maximumValue; } 163 | private final ObjectProperty maximumValue = new SimpleObjectProperty<>(this, "maximumValue"); 164 | public final Double getMaximumValue() { return maximumValueProperty().get(); } 165 | public final void setMaximumValue(Double maximumValue) { maximumValueProperty().set(maximumValue); } 166 | 167 | 168 | /** 169 | * Property that indicates when the value entered violates restrictions of minimum and maximum values. 170 | * @return The property that holds the flag. 171 | */ 172 | public final ReadOnlyBooleanProperty invalidProperty() { return invalid.getReadOnlyProperty(); } 173 | private final ReadOnlyBooleanWrapper invalid = new ReadOnlyBooleanWrapper(this, "invalid"); 174 | public final boolean isInvalid() { return invalid.get(); } 175 | private void setInvalid(boolean invalid) { this.invalid.set(invalid); } 176 | 177 | 178 | 179 | /** 180 | * Stores a validator object for the value converted from text entered. If the value entered is not valid, a pseudo 181 | * class is added to the field, called ":invalid". This can be used for styling the field to indicate invalid inputs. 182 | * @return The property storing the validator predicate. 183 | */ 184 | public final ObjectProperty > valueValidatorProperty() { return valueValidator; } 185 | private final ObjectProperty > valueValidator = new SimpleObjectProperty<>(this, "valueValidator"); 186 | public final Predicate getValueValidator() { return valueValidatorProperty().get(); } 187 | public final void setValueValidator(Predicate valueValidator) { valueValidatorProperty().set(valueValidator); } 188 | 189 | 190 | /** 191 | * Boolean property used to automatically convert the {@link #valueProperty()} when the {@link #unitProperty()} is changed. 192 | * @return The boolean property. 193 | */ 194 | public final BooleanProperty autoFixValueProperty() { 195 | return autoFixValue; 196 | } 197 | private final BooleanProperty autoFixValue = new SimpleBooleanProperty(this, "autoFixValue"); 198 | public final boolean isAutoFixValue() { return autoFixValueProperty().get(); } 199 | public final void setAutoFixValue(boolean autoFixValue) { autoFixValueProperty().set(autoFixValue); } 200 | 201 | 202 | // listeners 203 | 204 | private void bindQuantityValueProperty() { 205 | precisionQuantityProperty().addListener(obs -> updateValueQuantity()); 206 | } 207 | 208 | @Override 209 | void updateValueQuantity() { 210 | Quantity quantity = QuantitiesUtil.createQuantity(getValue(), getUnit()); 211 | Quantityprecision = getPrecisionQuantity(); 212 | if (quantity != null && precision != null) { 213 | quantity = QuantitiesUtil.roundQuantity(quantity, precision); 214 | } 215 | setValueQuantity(quantity); 216 | } 217 | 218 | @Override 219 | void updateDefaultUnit(UnitbaseUnit) { 220 | super.updateDefaultUnit(baseUnit); 221 | if (getPrecisionUnit() == null) { 222 | setPrecisionUnit(baseUnit); 223 | } 224 | } 225 | 226 | private void bindPrecisionQuantityProperty() { 227 | InvalidationListener listener = o -> { 228 | QuantityprecisionQ = QuantitiesUtil.createQuantity(getPrecision(), getPrecisionUnit()); 229 | setPrecisionQuantity(precisionQ); 230 | }; 231 | precisionProperty().addListener(listener); 232 | precisionUnitProperty().addListener(listener); 233 | } 234 | 235 | private void bindValueDirtyProperty() { 236 | InvalidationListener listener = obs -> { 237 | QuantityvalueQuantity = getValueQuantity(); 238 | Double value = getValue(); 239 | Unitunit = getUnit(); 240 | 241 | if (unit != null) { 242 | setValueDirty( 243 | (valueQuantity != null && value == null) || 244 | (valueQuantity == null && value != null) || 245 | (value != null && valueQuantity != null && value.compareTo(valueQuantity.getValue().doubleValue()) != 0) 246 | ); 247 | } 248 | }; 249 | 250 | valueProperty().addListener(listener); 251 | valueQuantityProperty().addListener(listener); 252 | } 253 | 254 | private void listenForInvalidChanges() { 255 | getProperties().addListener((MapChangeListener