From b528bb32fbf3a69b57a98fedefb7656b0569f637 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 16 Aug 2024 18:09:12 -0500 Subject: Add named unit sets to unit converter --- src/main/resources/metric_exceptions.txt | 1 + src/main/resources/unitsfile.txt | 3 +++ 2 files changed, 4 insertions(+) (limited to 'src/main/resources') diff --git a/src/main/resources/metric_exceptions.txt b/src/main/resources/metric_exceptions.txt index 73748c0..433dbb5 100644 --- a/src/main/resources/metric_exceptions.txt +++ b/src/main/resources/metric_exceptions.txt @@ -10,6 +10,7 @@ min minute h hour +hms d day wk diff --git a/src/main/resources/unitsfile.txt b/src/main/resources/unitsfile.txt index 543c821..ecd9bb6 100644 --- a/src/main/resources/unitsfile.txt +++ b/src/main/resources/unitsfile.txt @@ -155,6 +155,7 @@ minute 60 second min minute hour 3600 second h hour +hms hour; minute; second day 86400 second d day week 7 day @@ -189,6 +190,8 @@ yard 3 foot yd yard mile 1760 yard mi mile +ftin foot; inch +ydftin yard; foot; inch # Compressed notation kph km / hour -- cgit v1.2.3 From 6f1bbc1024eae98f1815ab5f9e9cb3399f5eef9c Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 10:12:37 -0500 Subject: Allow fractional exponents --- docs/roadmap.org | 1 - src/main/java/sevenUnits/unit/LinearUnit.java | 84 ++++++++++++--------- src/main/java/sevenUnits/unit/LinearUnitValue.java | 11 +++ src/main/java/sevenUnits/unit/UnitDatabase.java | 43 ++++------- src/main/java/sevenUnits/utils/ObjectProduct.java | 87 +++++++++++++++------- src/main/resources/unitsfile.txt | 7 ++ 6 files changed, 139 insertions(+), 94 deletions(-) (limited to 'src/main/resources') diff --git a/docs/roadmap.org b/docs/roadmap.org index 5a3888f..bd0ccee 100644 --- a/docs/roadmap.org +++ b/docs/roadmap.org @@ -8,7 +8,6 @@ Feature Requirements: (It should not be required to handle features that aren't in 7Units; those definitions should be ignored with a warning) - 7Units's expression converter should support most or all of the conversion features supported by GNU Units: - Converting to sums of units (it should also be possible to do this in the unit converter with preset combinations) - - Non-integer exponents - (/Optional/) Inverse nonlinear conversion with the tilde prefix - (/Optional/) Nonlinear units should be specifiable in unit files. - /Any other feature not listed should be considered optional./ diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 6489229..a230f28 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -46,7 +46,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit and a value. For example, converts * '59 °F' to a linear unit with the value of '288.15 K' @@ -64,7 +64,7 @@ public final class LinearUnit extends Unit { Objects.requireNonNull(unit, "unit must not be null.").getBase(), unit.convertToBase(value), ns); } - + /** * @return the base unit associated with {@code unit}, as a * {@code LinearUnit}. @@ -73,7 +73,7 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unit unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * @return the base unit associated with {@code unitlike}, as a * {@code LinearUnit}. @@ -82,7 +82,7 @@ public final class LinearUnit extends Unit { public static LinearUnit getBase(final Unitlike unit) { return new LinearUnit(unit.getBase(), 1, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and @@ -98,7 +98,7 @@ public final class LinearUnit extends Unit { final double conversionFactor) { return new LinearUnit(unitBase, conversionFactor, NameSymbol.EMPTY); } - + /** * Gets a {@code LinearUnit} from a unit base and a conversion factor. In * other words, gets the product of {@code unitBase} and @@ -115,7 +115,7 @@ public final class LinearUnit extends Unit { final double conversionFactor, final NameSymbol ns) { return new LinearUnit(unitBase, conversionFactor, ns); } - + /** * The value of this unit as represented in its base form. Mathematically, * @@ -126,7 +126,7 @@ public final class LinearUnit extends Unit { * @since 2019-10-16 */ private final double conversionFactor; - + /** * Creates the {@code LinearUnit}. * @@ -139,7 +139,7 @@ public final class LinearUnit extends Unit { super(unitBase, ns); this.conversionFactor = conversionFactor; } - + /** * {@inheritDoc} * @@ -149,7 +149,7 @@ public final class LinearUnit extends Unit { protected double convertFromBase(final double value) { return value / this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} value expressed in this unit to an * {@code UncertainValue} value expressed in {@code other}. @@ -172,9 +172,9 @@ public final class LinearUnit extends Unit { else throw new IllegalArgumentException( String.format("Cannot convert from %s to %s.", this, other)); - + } - + /** * {@inheritDoc} * @@ -184,7 +184,7 @@ public final class LinearUnit extends Unit { protected double convertToBase(final double value) { return value * this.getConversionFactor(); } - + /** * Converts an {@code UncertainDouble} to the base unit. * @@ -193,7 +193,7 @@ public final class LinearUnit extends Unit { UncertainDouble convertToBase(final UncertainDouble value) { return value.timesExact(this.getConversionFactor()); } - + /** * Divides this unit by a scalar. * @@ -205,7 +205,7 @@ public final class LinearUnit extends Unit { public LinearUnit dividedBy(final double divisor) { return valueOf(this.getBase(), this.getConversionFactor() / divisor); } - + /** * Returns the quotient of this unit and another. * @@ -217,14 +217,14 @@ public final class LinearUnit extends Unit { */ public LinearUnit dividedBy(final LinearUnit divisor) { Objects.requireNonNull(divisor, "other must not be null"); - + // divide the units final ObjectProduct base = this.getBase() .dividedBy(divisor.getBase()); return valueOf(base, this.getConversionFactor() / divisor.getConversionFactor()); } - + /** * {@inheritDoc} * @@ -239,7 +239,7 @@ public final class LinearUnit extends Unit { && DecimalComparison.equals(this.getConversionFactor(), other.getConversionFactor()); } - + /** * @return conversion factor * @since 2019-10-16 @@ -247,7 +247,7 @@ public final class LinearUnit extends Unit { public double getConversionFactor() { return this.conversionFactor; } - + /** * {@inheritDoc} * @@ -258,7 +258,7 @@ public final class LinearUnit extends Unit { return 31 * this.getBase().hashCode() + DecimalComparison.hash(this.getConversionFactor()); } - + /** * @return whether this unit is equivalent to a {@code BaseUnit} (i.e. there * is a {@code BaseUnit b} where @@ -268,7 +268,7 @@ public final class LinearUnit extends Unit { public boolean isBase() { return this.isCoherent() && this.getBase().isSingleObject(); } - + /** * @return whether this unit is coherent (i.e. has conversion factor 1) * @since 2019-10-16 @@ -276,7 +276,7 @@ public final class LinearUnit extends Unit { public boolean isCoherent() { return this.getConversionFactor() == 1; } - + /** * Returns the difference of this unit and another. *

@@ -296,18 +296,18 @@ public final class LinearUnit extends Unit { */ public LinearUnit minus(final LinearUnit subtrahend) { Objects.requireNonNull(subtrahend, "addend must not be null."); - + // reject subtrahends that cannot be added to this unit if (!this.getBase().equals(subtrahend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for subtraction \"%s\" and \"%s\".", this, subtrahend)); - + // subtract the units return valueOf(this.getBase(), this.getConversionFactor() - subtrahend.getConversionFactor()); } - + /** * Returns the sum of this unit and another. *

@@ -327,18 +327,18 @@ public final class LinearUnit extends Unit { */ public LinearUnit plus(final LinearUnit addend) { Objects.requireNonNull(addend, "addend must not be null."); - + // reject addends that cannot be added to this unit if (!this.getBase().equals(addend.getBase())) throw new IllegalArgumentException(String.format( "Incompatible units for addition \"%s\" and \"%s\".", this, addend)); - + // add the units return valueOf(this.getBase(), this.getConversionFactor() + addend.getConversionFactor()); } - + /** * Multiplies this unit by a scalar. * @@ -350,7 +350,7 @@ public final class LinearUnit extends Unit { public LinearUnit times(final double multiplier) { return valueOf(this.getBase(), this.getConversionFactor() * multiplier); } - + /** * Returns the product of this unit and another. * @@ -362,21 +362,21 @@ public final class LinearUnit extends Unit { */ public LinearUnit times(final LinearUnit multiplier) { Objects.requireNonNull(multiplier, "other must not be null"); - + // multiply the units final ObjectProduct base = this.getBase() .times(multiplier.getBase()); return valueOf(base, this.getConversionFactor() * multiplier.getConversionFactor()); } - + @Override public String toDefinitionString() { return Double.toString(this.conversionFactor) + (this.getBase().equals(ObjectProduct.empty()) ? "" : " " + this.getBase().toString(BaseUnit::getShortName)); } - + /** * Returns this unit but to an exponent. * @@ -389,12 +389,24 @@ public final class LinearUnit extends Unit { return valueOf(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); } - + + /** + * Returns this unit to an exponent, rounding the resulting dimensions to the + * nearest integer. + * + * @since 2024-08-22 + * @see ObjectProduct#toExponentRounded + */ + public LinearUnit toExponentRounded(final double exponent) { + return valueOf(this.getBase().toExponentRounded(exponent), + Math.pow(this.conversionFactor, exponent)); + } + @Override public LinearUnit withName(final NameSymbol ns) { return valueOf(this.getBase(), this.getConversionFactor(), ns); } - + /** * Returns the result of applying {@code prefix} to this unit. *

@@ -413,7 +425,7 @@ public final class LinearUnit extends Unit { */ public LinearUnit withPrefix(final UnitPrefix prefix) { final LinearUnit unit = this.times(prefix.getMultiplier()); - + // create new name and symbol, if possible final String name; if (this.getPrimaryName().isPresent() @@ -422,14 +434,14 @@ public final class LinearUnit extends Unit { } else { name = null; } - + final String symbol; if (this.getSymbol().isPresent() && prefix.getSymbol().isPresent()) { symbol = prefix.getSymbol().get() + this.getSymbol().get(); } else { symbol = null; } - + return unit.withName(NameSymbol.ofNullable(name, symbol)); } } diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java index fad3eb0..db2936c 100644 --- a/src/main/java/sevenUnits/unit/LinearUnitValue.java +++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java @@ -338,6 +338,17 @@ public final class LinearUnitValue { this.value.toExponentExact(exponent)); } + /** + * Raises this value to an exponent, rounding all dimensions to integers. + * + * @since 2024-08-22 + * @see ObjectProduct#toExponentRounded + */ + public LinearUnitValue toExponentRounded(final double exponent) { + return LinearUnitValue.of(this.unit.toExponentRounded(exponent), + this.value.toExponentExact(exponent)); + } + @Override public String toString() { return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN); diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 05c31c4..514b27d 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -44,7 +44,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import sevenUnits.utils.ConditionalExistenceCollections; -import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.ExpressionParser; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; @@ -1117,20 +1116,13 @@ public final class UnitDatabase { */ private static final LinearUnit exponentiateUnits(final LinearUnit base, final LinearUnit exponentUnit) { - // exponent function - first check if o2 is a number, - if (exponentUnit.getBase().equals(Metric.ONE.getBase())) { - // then check if it is an integer, - final double exponent = exponentUnit.getConversionFactor(); - if (DecimalComparison.equals(exponent % 1, 0)) - // then exponentiate - return base.toExponent((int) (exponent + 0.5)); - else - // not an integer - throw new UnsupportedOperationException( - "Decimal exponents are currently not supported."); - } else - // not a number - throw new IllegalArgumentException("Exponents must be numbers."); + if (!exponentUnit.getBase().equals(Metric.ONE.getBase())) + throw new IllegalArgumentException(String.format( + "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", + base, exponentUnit)); + + final double exponent = exponentUnit.getConversionFactor(); + return base.toExponentRounded(exponent); } /** @@ -1143,20 +1135,13 @@ public final class UnitDatabase { */ private static final LinearUnitValue exponentiateUnitValues( final LinearUnitValue base, final LinearUnitValue exponentValue) { - // exponent function - first check if o2 is a number, - if (exponentValue.canConvertTo(Metric.ONE)) { - // then check if it is an integer, - final double exponent = exponentValue.getValueExact(); - if (DecimalComparison.equals(exponent % 1, 0)) - // then exponentiate - return base.toExponent((int) (exponent + 0.5)); - else - // not an integer - throw new UnsupportedOperationException( - "Decimal exponents are currently not supported."); - } else - // not a number - throw new IllegalArgumentException("Exponents must be numbers."); + if (!exponentValue.canConvertTo(Metric.ONE)) + throw new IllegalArgumentException(String.format( + "Tried to exponentiate %s^%s, but exponents must be dimensionless numbers.", + base, exponentValue)); + + final double exponent = exponentValue.getValueExact(); + return base.toExponentRounded(exponent); } /** diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 5a29d79..4ed70be 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -34,6 +34,12 @@ import java.util.function.Function; * @since 2019-10-16 */ public class ObjectProduct implements Nameable { + /** + * If {@link #toExponentRounded}'s rounding changes exponents by more than + * this value, a warning will be printed to standard error. + */ + private static final double ROUND_WARN_THRESHOLD = 1e-12; + /** * Returns an empty ObjectProduct of a certain type * @@ -44,7 +50,7 @@ public class ObjectProduct implements Nameable { public static final ObjectProduct empty() { return new ObjectProduct<>(new HashMap<>()); } - + /** * Gets an {@code ObjectProduct} from an object-to-integer mapping * @@ -57,7 +63,7 @@ public class ObjectProduct implements Nameable { final Map map) { return new ObjectProduct<>(new HashMap<>(map)); } - + /** * Gets an ObjectProduct that has one of the inputted argument, and nothing * else. @@ -73,7 +79,7 @@ public class ObjectProduct implements Nameable { map.put(object, 1); return new ObjectProduct<>(map); } - + /** * The objects that make up the product, mapped to their exponents. This map * treats zero as null, and is immutable. @@ -81,12 +87,12 @@ public class ObjectProduct implements Nameable { * @since 2019-10-16 */ final Map exponents; - + /** * The object's name and symbol */ private final NameSymbol nameSymbol; - + /** * Creates a {@code ObjectProduct} without a name/symbol. * @@ -96,7 +102,7 @@ public class ObjectProduct implements Nameable { ObjectProduct(final Map exponents) { this(exponents, NameSymbol.EMPTY); } - + /** * Creates the {@code ObjectProduct}. * @@ -110,7 +116,7 @@ public class ObjectProduct implements Nameable { e -> !Integer.valueOf(0).equals(e.getValue()))); this.nameSymbol = nameSymbol; } - + /** * Calculates the quotient of two products * @@ -125,17 +131,17 @@ public class ObjectProduct implements Nameable { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) - other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + // this method relies on the use of ZeroIsNullMap @Override public boolean equals(final Object obj) { @@ -146,7 +152,7 @@ public class ObjectProduct implements Nameable { final ObjectProduct other = (ObjectProduct) obj; return Objects.equals(this.exponents, other.exponents); } - + /** * @return immutable map mapping objects to exponents * @since 2019-10-16 @@ -154,7 +160,7 @@ public class ObjectProduct implements Nameable { public Map exponentMap() { return this.exponents; } - + /** * @return a set of all of the base objects with non-zero exponents that make * up this dimension. @@ -163,7 +169,7 @@ public class ObjectProduct implements Nameable { */ public final Set getBaseSet() { final Set dimensions = new HashSet<>(); - + // add all dimensions with a nonzero exponent - zero exponents shouldn't // be there in the first place for (final T dimension : this.exponents.keySet()) { @@ -171,10 +177,10 @@ public class ObjectProduct implements Nameable { dimensions.add(dimension); } } - + return dimensions; } - + /** * Gets the exponent for a specific dimension. * @@ -186,17 +192,17 @@ public class ObjectProduct implements Nameable { public int getExponent(final T dimension) { return this.exponents.getOrDefault(dimension, 0); } - + @Override public NameSymbol getNameSymbol() { return this.nameSymbol; } - + @Override public int hashCode() { return Objects.hash(this.exponents); } - + /** * @return true if this product is a single object, i.e. it has one exponent * of one and no other nonzero exponents @@ -214,7 +220,7 @@ public class ObjectProduct implements Nameable { } return oneCount == 1 && !twoOrMore; } - + /** * Multiplies this product by another * @@ -229,17 +235,17 @@ public class ObjectProduct implements Nameable { final Set objects = new HashSet<>(); objects.addAll(this.getBaseSet()); objects.addAll(other.getBaseSet()); - + // get a list of all exponents final Map map = new HashMap<>(objects.size()); for (final T key : objects) { map.put(key, this.getExponent(key) + other.getExponent(key)); } - + // create the product return new ObjectProduct<>(map); } - + /** * Returns this product, but to an exponent * @@ -254,7 +260,32 @@ public class ObjectProduct implements Nameable { } return new ObjectProduct<>(map); } - + + /** + * Returns this product to an exponent, where every dimension is rounded to + * the nearest integer. + * + * This function will send a warning (via {@link System.err}) if the rounding + * significantly changes the value. + * + * @since 2024-08-22 + */ + public ObjectProduct toExponentRounded(final double exponent) { + final Map map = new HashMap<>(this.exponents); + for (final T key : this.exponents.keySet()) { + final double newExponent = this.getExponent(key) * exponent; + if (Math.abs( + newExponent - Math.round(newExponent)) > ROUND_WARN_THRESHOLD) { + System.err.printf( + "Exponent Rounding Warning: Dimension exponents must be integers, so %d ^ %g = %g was rounded to %d.\n", + this.getExponent(key), exponent, newExponent, + Math.round(newExponent)); + } + map.put(key, (int) Math.round(newExponent)); + } + return new ObjectProduct<>(map); + } + /** * Converts this product to a string using the objects' * {@link Object#toString()} method (or {@link Nameable#getShortName} if @@ -271,7 +302,7 @@ public class ObjectProduct implements Nameable { .toString(o -> o instanceof Nameable ? ((Nameable) o).getShortName() : o.toString()); } - + /** * Converts this product to a string. The objects that make up this product * are represented by {@code objectToString} @@ -283,7 +314,7 @@ public class ObjectProduct implements Nameable { public String toString(final Function objectToString) { final List positiveStringComponents = new ArrayList<>(); final List negativeStringComponents = new ArrayList<>(); - + // for each base object that makes up this object, add it and its exponent for (final T object : this.getBaseSet()) { final int exponent = this.exponents.get(object); @@ -297,15 +328,15 @@ public class ObjectProduct implements Nameable { objectToString.apply(object), -exponent)); } } - + final String positiveString = positiveStringComponents.isEmpty() ? "1" : String.join(" * ", positiveStringComponents); final String negativeString = negativeStringComponents.isEmpty() ? "" : " / " + String.join(" * ", negativeStringComponents); - + return positiveString + negativeString; } - + /** * @return named version of this {@code ObjectProduct}, using data from * {@code nameSymbol} diff --git a/src/main/resources/unitsfile.txt b/src/main/resources/unitsfile.txt index ecd9bb6..dc33abd 100644 --- a/src/main/resources/unitsfile.txt +++ b/src/main/resources/unitsfile.txt @@ -165,6 +165,7 @@ gregorianyear 365.2425 day gregorianmonth gregorianyear / 12 # Other non-SI "metric" units +are 100 m^2 litre 0.001 m^3 liter litre l litre @@ -188,11 +189,17 @@ inch foot / 12 in inch yard 3 foot yd yard +chain 66 ft +ch chain +furlong 10 chain mile 1760 yard mi mile ftin foot; inch ydftin yard; foot; inch +# Imperial area units +acre chain * furlong + # Compressed notation kph km / hour mph mile / hour -- cgit v1.2.3 From b0242b898653f2dc23f2187deec9db0bb652751d Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 22 Aug 2024 12:19:30 -0500 Subject: Add loading counts to About tab --- CHANGELOG.org | 2 +- src/main/java/sevenUnitsGUI/Presenter.java | 88 ++++++++++++++++++++++------- src/main/java/sevenUnitsGUI/TabbedView.java | 2 +- src/main/resources/about.txt | 4 ++ 4 files changed, 73 insertions(+), 23 deletions(-) (limited to 'src/main/resources') diff --git a/CHANGELOG.org b/CHANGELOG.org index 344b4a0..78bb9a1 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -5,7 +5,7 @@ All notable changes in this project will be shown in this file. - *Allowed conversion to a sum of units (e.g. 4/3 ft \rightarrow 1 ft + 4 in).* - *Allowed exponents on units to be non-integer numbers.* The resulting exponents are rounded to the nearest integer, and the user is warned if this rounding changes the value by more than normal floating-point error. -- Add more information to the loading-success message. +- Added more information to the loading-success message, and added it to the about tab. *** Changed - *Errors in unit/dimension files are shown in popups, rather than crashing the program.* *** Fixed diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 4ff2d65..c46ee53 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -74,6 +74,9 @@ public final class Presenter { private static final String DEFAULT_DIMENSIONS_FILEPATH = "/dimensionfile.txt"; /** The default place where exceptions are stored. */ private static final String DEFAULT_EXCEPTIONS_FILEPATH = "/metric_exceptions.txt"; + /** A Predicate that returns true iff the argument is a full base unit */ + private static final Predicate IS_FULL_BASE = unit -> unit instanceof LinearUnit + && ((LinearUnit) unit).isBase(); /** * Adds default units and dimensions to a database. @@ -119,13 +122,17 @@ public final class Presenter { } /** - * @return text in About file - * @since 2022-02-19 + * Determines where to wrap {@code toWrap} with a max line length of + * {@code maxLineLength}. If no good spot is found, returns -1. + * + * @since 2024-08-22 */ - public static final String getAboutText() { - return Presenter.getLinesFromResource("/about.txt").stream() - .map(Presenter::withoutComments).collect(Collectors.joining("\n")) - .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()); + private static final int findLineSplit(String toWrap, int maxLineLength) { + for (int i = maxLineLength - 1; i >= 0; i--) { + if (Character.isWhitespace(toWrap.charAt(i))) + return i; + } + return -1; } /** @@ -241,6 +248,30 @@ public final class Presenter { return index == -1 ? line : line.substring(0, index); } + /** + * Wraps a string, ensuring no line is longer than {@code maxLineLength}. + * + * @since 2024-08-22 + */ + private static final String wrapString(String toWrap, int maxLineLength) { + final StringBuilder wrapped = new StringBuilder(toWrap.length()); + String remaining = toWrap; + while (remaining.length() > maxLineLength) { + final int spot = findLineSplit(toWrap, maxLineLength); + if (spot == -1) { + wrapped.append(remaining.substring(0, maxLineLength)); + wrapped.append("-\n"); + remaining = remaining.substring(maxLineLength).stripLeading(); + } else { + wrapped.append(remaining.substring(0, spot)); + wrapped.append("\n"); + remaining = remaining.substring(spot + 1).stripLeading(); + } + } + wrapped.append(remaining); + return wrapped.toString(); + } + /** * The view that this presenter communicates with */ @@ -349,21 +380,8 @@ public final class Presenter { this.loadSettings(CONFIG_FILE); } - // a Predicate that returns true iff the argument is a full base unit - final Predicate isFullBase = unit -> unit instanceof LinearUnit - && ((LinearUnit) unit).isBase(); - // print out unit counts - System.out.printf( - "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.%n", - this.database.unitMapPrefixless(false).size(), - this.database.unitMapPrefixless(true).size(), - this.database.unitMapPrefixless(false).values().stream() - .filter(isFullBase).count(), - this.database.prefixMap(false).size(), - this.database.prefixMap(true).size(), - this.database.unitSetMap().size(), - this.database.dimensionMap().size()); + System.out.println(this.loadStatMsg()); } /** @@ -706,6 +724,17 @@ public final class Presenter { return this.showDuplicates; } + /** + * @return text in About file + * @since 2022-02-19 + */ + public final String getAboutText() { + return Presenter.getLinesFromResource("/about.txt").stream() + .map(Presenter::withoutComments).collect(Collectors.joining("\n")) + .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) + .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); + } + /** * Gets a name for this dimension using the database * @@ -891,6 +920,23 @@ public final class Presenter { } } + /** + * @return a message showing how much stuff has been loaded + * @since 2024-08-22 + */ + private String loadStatMsg() { + return String.format( + "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.", + this.database.unitMapPrefixless(false).size(), + this.database.unitMapPrefixless(true).size(), + this.database.unitMapPrefixless(false).values().stream() + .filter(IS_FULL_BASE).count(), + this.database.prefixMap(false).size(), + this.database.prefixMap(true).size(), + this.database.unitSetMap().size(), + this.database.dimensionMap().size()); + } + /** * @return true iff the One-Way Conversion feature is available (views that * show units as a list will have metric units removed from the From @@ -1072,7 +1118,7 @@ public final class Presenter { * @param u unit to show * @since 2022-04-16 */ - private final void showUnit(Unit u) { + private void showUnit(Unit u) { final var nameSymbol = u.getNameSymbol(); final boolean isBase = u instanceof BaseUnit || u instanceof LinearUnit && ((LinearUnit) u).isBase(); diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index a71de44..6542541 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -357,7 +357,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { infoTextArea.setEditable(false); infoTextArea.setOpaque(false); infoPanel.add(infoTextArea); - infoTextArea.setText(Presenter.getAboutText()); + infoTextArea.setText(this.presenter.getAboutText()); // ============ SETTINGS PANEL ============ this.masterPane.addTab("\u2699", diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt index 782b422..4c33f8c 100644 --- a/src/main/resources/about.txt +++ b/src/main/resources/about.txt @@ -9,6 +9,10 @@ like "10 m/s + (25^2 - 5^2) mi/hr". This software was written by Adrien Hopkins . +Unit/Prefix/Dimension Statistics: + +[LOADSTATS] + Copyright Notice: Unit Converter Copyright (C) 2018-2024 Adrien Hopkins -- cgit v1.2.3 From d41c05feaaf543c473a9db7aa5a3e564cee0e4ed Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 22 Feb 2025 17:29:28 -0500 Subject: Load locales from text files --- src/main/java/sevenUnitsGUI/Presenter.java | 705 +++++++++++++++------------- src/main/java/sevenUnitsGUI/TabbedView.java | 3 +- src/main/resources/locales/en.txt | 1 + src/main/resources/locales/fr.txt | 1 + 4 files changed, 379 insertions(+), 331 deletions(-) create mode 100644 src/main/resources/locales/en.txt create mode 100644 src/main/resources/locales/fr.txt (limited to 'src/main/resources') diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 21f2951..1cbac47 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -16,7 +16,6 @@ */ package sevenUnitsGUI; -import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -58,7 +57,7 @@ import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased; /** * An object that handles interactions between the view and the backend code - * + * * @author Adrien Hopkins * @since 2021-12-15 */ @@ -78,19 +77,23 @@ public final class Presenter { /** A Predicate that returns true iff the argument is a full base unit */ private static final Predicate IS_FULL_BASE = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase(); - /** + /** * The default locale, used in two situations: *

    - *
  • If no text is available in your locale, - * uses text from this locale. - *
  • Users are initialized with this locale. + *
  • If no text is available in your locale, uses text from this locale. + *
  • Users are initialized with this locale. *
*/ - static final String DEFAULT_LOCALE = "en"; - + static final String DEFAULT_LOCALE = "eo"; + + private static final List LOCAL_LOCALES = List.of("en", "fr"); + private static final Path USER_LOCALES_DIR = userConfigDir() + .resolve("SevenUnits").resolve("locales"); + + /** * Adds default units and dimensions to a database. - * + * * @param database database to add to * @since 2019-04-14 * @since v0.2.0 @@ -109,20 +112,20 @@ public final class Presenter { // nonlinear units - must be loaded manually database.addUnit("tempCelsius", Metric.CELSIUS); database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT); - + // load initial dimensions database.addDimension("Length", Metric.Dimensions.LENGTH); database.addDimension("Mass", Metric.Dimensions.MASS); database.addDimension("Time", Metric.Dimensions.TIME); database.addDimension("Temperature", Metric.Dimensions.TEMPERATURE); } - + private static String displayRuleToString( Function numberDisplayRule) { if (numberDisplayRule instanceof FixedDecimals) return String.format("FIXED_DECIMALS %d", ((FixedDecimals) numberDisplayRule).decimalPlaces()); - else if (numberDisplayRule instanceof FixedPrecision) + if (numberDisplayRule instanceof FixedPrecision) return String.format("FIXED_PRECISION %d", ((FixedPrecision) numberDisplayRule).significantFigures()); else if (numberDisplayRule instanceof UncertaintyBased) @@ -130,21 +133,21 @@ public final class Presenter { else return numberDisplayRule.toString(); } - + /** * Determines where to wrap {@code toWrap} with a max line length of * {@code maxLineLength}. If no good spot is found, returns -1. * * @since 2024-08-22 */ - private static final int findLineSplit(String toWrap, int maxLineLength) { - for (int i = maxLineLength - 1; i >= 0; i--) { + private static int findLineSplit(String toWrap, int maxLineLength) { + for (var i = maxLineLength - 1; i >= 0; i--) { if (Character.isWhitespace(toWrap.charAt(i))) return i; } return -1; } - + /** * Gets the text of a resource file as a set of strings (each one is one line * of the text). @@ -153,11 +156,11 @@ public final class Presenter { * @return contents of file * @since 2021-03-27 */ - private static final List getLinesFromResource(String filename) { + private static List getLinesFromResource(String filename) { final List lines = new ArrayList<>(); - - try (InputStream stream = inputStream(filename); - Scanner scanner = new Scanner(stream)) { + + try (var stream = inputStream(filename); + var scanner = new Scanner(stream)) { while (scanner.hasNextLine()) { lines.add(scanner.nextLine()); } @@ -165,10 +168,10 @@ public final class Presenter { throw new AssertionError( "Error occurred while loading file " + filename, e); } - + return lines; } - + /** * Gets an input stream for a resource file. * @@ -176,98 +179,97 @@ public final class Presenter { * @return obtained Path * @since 2021-03-27 */ - private static final InputStream inputStream(String filepath) { + private static InputStream inputStream(String filepath) { return Presenter.class.getResourceAsStream(filepath); } - + /** * Convert a linear unit value to a string, where the number is rounded to * the nearest integer. * * @since 2024-08-16 */ - private static final String linearUnitValueIntToString(LinearUnitValue uv) { + private static String linearUnitValueIntToString(LinearUnitValue uv) { return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit(); } - + private static Map.Entry parseSettingLine(String line) { - final int equalsIndex = line.indexOf('='); + final var equalsIndex = line.indexOf('='); if (equalsIndex == -1) throw new IllegalStateException( "Settings file is malformed at line: " + line); - - final String param = line.substring(0, equalsIndex); - final String value = line.substring(equalsIndex + 1); - + + final var param = line.substring(0, equalsIndex); + final var value = line.substring(equalsIndex + 1); + return Map.entry(param, value); } - + /** Gets a Path from a pathname in the config file. */ private static Path pathFromConfig(String pathname) { return CONFIG_FILE.getParent().resolve(pathname); } - + // ====== SETTINGS ====== - + private static String searchRuleToString( Function, Map> searchRule) { if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) return "NO_PREFIXES"; - else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) + if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) return "COMMON_PREFIXES"; else if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule)) return "ALL_METRIC_PREFIXES"; else return searchRule.toString(); } - + /** * @return true iff a and b have any elements in common * @since 2022-04-19 */ - private static final boolean sharesAnyElements(Set a, Set b) { + private static boolean sharesAnyElements(Set a, Set b) { for (final Object e : a) { if (b.contains(e)) return true; } return false; } - - private static final Path userConfigDir() { + + private static Path userConfigDir() { if (System.getProperty("os.name").startsWith("Windows")) { - final String envFolder = System.getenv("LOCALAPPDATA"); + final var envFolder = System.getenv("LOCALAPPDATA"); if (envFolder == null || "".equals(envFolder)) return Path.of(System.getenv("USERPROFILE"), "AppData", "Local"); else return Path.of(envFolder); - } else { - final String envFolder = System.getenv("XDG_CONFIG_HOME"); - if (envFolder == null || "".equals(envFolder)) - return Path.of(System.getenv("HOME"), ".config"); - else - return Path.of(envFolder); } + final var envFolder = System.getenv("XDG_CONFIG_HOME"); + if (envFolder == null || "".equals(envFolder)) + return Path.of(System.getenv("HOME"), ".config"); + else + return Path.of(envFolder); } - + /** * @return {@code line} with any comments removed. * @since 2021-03-13 */ - private static final String withoutComments(String line) { - final int index = line.indexOf('#'); + private static String withoutComments(String line) { + final var index = line.indexOf('#'); return index == -1 ? line : line.substring(0, index); } - + /** * Wraps a string, ensuring no line is longer than {@code maxLineLength}. - * + * * @since 2024-08-22 */ - private static final String wrapString(String toWrap, int maxLineLength) { - final StringBuilder wrapped = new StringBuilder(toWrap.length()); - String remaining = toWrap; + private static String wrapString(String toWrap, int maxLineLength) { + final var wrapped = new StringBuilder(toWrap.length()); + var remaining = toWrap; while (remaining.length() > maxLineLength) { - final int spot = findLineSplit(toWrap, maxLineLength); + final var spot = findLineSplit(toWrap, maxLineLength); if (spot == -1) { wrapped.append(remaining.substring(0, maxLineLength)); wrapped.append("-\n"); @@ -281,23 +283,23 @@ public final class Presenter { wrapped.append(remaining); return wrapped.toString(); } - + /** * The view that this presenter communicates with */ private final View view; - + /** * The database that this presenter communicates with (effectively the model) */ final UnitDatabase database; - + /** * The rule used for parsing input numbers. Any number-string inputted into * this program will be parsed using this method. Not implemented yet. */ private Function numberParsingRule; - + /** * The rule used for displaying the results of unit conversions. The result * of unit conversions will be put into this function, and the resulting @@ -305,50 +307,50 @@ public final class Presenter { */ private Function numberDisplayRule = StandardDisplayRules .uncertaintyBased(); - + /** * A predicate that determines whether or not a certain combination of * prefixes is allowed. If it returns false, a combination of prefixes will * not be allowed. Prefixes are put in the list from right to left. */ private Predicate> prefixRepetitionRule = DefaultPrefixRepetitionRule.NO_RESTRICTION; - + /** * A rule that accepts a prefixless name-unit pair and returns a map mapping * names to prefixed versions of that unit (including the unit itself) that * should be searchable. */ private Function, Map> searchRule = PrefixSearchRule.NO_PREFIXES; - + /** * The set of units that is considered neither metric nor nonmetric for the * purposes of the metric-imperial one-way conversion. These units are * included in both From and To, even if One Way Conversion is enabled. */ private final Set metricExceptions; - + /** maps locale names (e.g. 'en') to key-text maps */ final Map> locales; - + /** name of locale in locales to use */ String userLocale; - + /** * If this is true, views that show units as a list will have metric units * removed from the From unit list and imperial/USC units removed from the To * unit list. */ private boolean oneWayConversionEnabled = false; - + /** * If this is false, duplicate units and prefixes will be removed from the * unit view in views that show units as a list to choose from. */ private boolean showDuplicates = false; - + /** * Creates a Presenter - * + * * @param view the view that this presenter communicates with * @since 2021-12-15 */ @@ -356,30 +358,30 @@ public final class Presenter { this.view = view; this.database = new UnitDatabase(); addDefaults(this.database); - + // load units and prefixes - try (final InputStream units = inputStream(DEFAULT_UNITS_FILEPATH)) { + try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { this.handleLoadErrors(this.database.loadUnitsFromStream(units)); } catch (final IOException e) { throw new AssertionError("Loading of unitsfile.txt failed.", e); } - + // load dimensions - try (final InputStream dimensions = inputStream( + try (final var dimensions = inputStream( DEFAULT_DIMENSIONS_FILEPATH)) { this.handleLoadErrors( this.database.loadDimensionsFromStream(dimensions)); } catch (final IOException e) { throw new AssertionError("Loading of dimensionfile.txt failed.", e); } - + // load metric exceptions try { this.metricExceptions = new HashSet<>(); - try (InputStream exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); - Scanner scanner = new Scanner(exceptions)) { + try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); + var scanner = new Scanner(exceptions)) { while (scanner.hasNextLine()) { - final String line = Presenter + final var line = Presenter .withoutComments(scanner.nextLine()); if (!line.isBlank()) { this.metricExceptions.add(line); @@ -390,19 +392,19 @@ public final class Presenter { throw new AssertionError("Loading of metric_exceptions.txt failed.", e); } - - // TODO load locales - this.locales = new HashMap<>(); - + + this.locales = this.loadLocales(); + this.userLocale = DEFAULT_LOCALE; + // set default settings temporarily if (Files.exists(CONFIG_FILE)) { this.loadSettings(CONFIG_FILE); } - + // print out unit counts System.out.println(this.loadStatMsg()); } - + /** * Applies a search rule to an entry in a name-unit map. * @@ -410,23 +412,23 @@ public final class Presenter { * @return stream of entries, ready for flat-mapping * @since 2022-07-06 */ - private final Stream> applySearchRule( + private Stream> applySearchRule( Map.Entry e) { - final Unit u = e.getValue(); + final var u = e.getValue(); if (u instanceof LinearUnit) { - final String name = e.getKey(); + final var name = e.getKey(); final Map.Entry linearEntry = Map.entry(name, (LinearUnit) u); return this.searchRule.apply(linearEntry).entrySet().stream().map( entry -> Map.entry(entry.getKey(), (Unit) entry.getValue())); - } else - return Stream.of(e); + } + return Stream.of(e); } - + /** * Converts from the view's input expression to its output expression. * Displays an error message if any of the required fields are invalid. - * + * * @throws UnsupportedOperationException if the view does not support * expression-based conversion (does * not implement @@ -434,49 +436,46 @@ public final class Presenter { * @since 2021-12-15 */ public void convertExpressions() { - if (this.view instanceof ExpressionConversionView) { - final ExpressionConversionView xcview = (ExpressionConversionView) this.view; - - final String fromExpression = xcview.getFromExpression(); - final String toExpression = xcview.getToExpression(); - - // expressions must not be empty - if (fromExpression.isEmpty()) { - this.view.showErrorMessage("Parse Error", - "Please enter a unit expression in the From: box."); - return; - } - if (toExpression.isEmpty()) { - this.view.showErrorMessage("Parse Error", - "Please enter a unit expression in the To: box."); - return; - } - - final Optional uc; - if (this.database.containsUnitSetName(toExpression)) { - uc = this.convertExpressionToNamedMultiUnit(fromExpression, - toExpression); - } else if (toExpression.contains(";")) { - final String[] toExpressions = toExpression.split(";"); - uc = this.convertExpressionToMultiUnit(fromExpression, - toExpressions); - } else { - uc = this.convertExpressionToExpression(fromExpression, - toExpression); - } - - uc.ifPresent(xcview::showExpressionConversionOutput); - } else + if (!(this.view instanceof ExpressionConversionView)) throw new UnsupportedOperationException( "This function can only be called when the view is an ExpressionConversionView"); + final var xcview = (ExpressionConversionView) this.view; + + final var fromExpression = xcview.getFromExpression(); + final var toExpression = xcview.getToExpression(); + + // expressions must not be empty + if (fromExpression.isEmpty()) { + this.view.showErrorMessage("Parse Error", + "Please enter a unit expression in the From: box."); + return; + } + if (toExpression.isEmpty()) { + this.view.showErrorMessage("Parse Error", + "Please enter a unit expression in the To: box."); + return; + } + + final Optional uc; + if (this.database.containsUnitSetName(toExpression)) { + uc = this.convertExpressionToNamedMultiUnit(fromExpression, + toExpression); + } else if (toExpression.contains(";")) { + final var toExpressions = toExpression.split(";"); + uc = this.convertExpressionToMultiUnit(fromExpression, toExpressions); + } else { + uc = this.convertExpressionToExpression(fromExpression, toExpression); + } + + uc.ifPresent(xcview::showExpressionConversionOutput); } - + /** * Converts a unit expression to another expression. - * + * * If an error happened, it is shown to the view and Optional.empty() is * returned. - * + * * @since 2024-08-15 */ private Optional convertExpressionToExpression( @@ -498,34 +497,33 @@ public final class Presenter { "Could not recognize text in To entry: " + e.getMessage()); return Optional.empty(); } - + // convert and show output - if (from.getUnit().canConvertTo(to)) { - final UncertainDouble uncertainValue; - - // uncertainty is meaningless for non-linear units, so we will have - // to erase uncertainty information for them - if (to instanceof LinearUnit) { - final var toLinear = (LinearUnit) to; - uncertainValue = from.convertTo(toLinear).getValue(); - } else { - final double value = from.asUnitValue().convertTo(to).getValue(); - uncertainValue = UncertainDouble.of(value, 0); - } - - final UnitConversionRecord uc = UnitConversionRecord.valueOf( - fromExpression, toExpression, "", - this.numberDisplayRule.apply(uncertainValue)); - return Optional.of(uc); - } else { + if (!from.getUnit().canConvertTo(to)) { this.view.showErrorMessage("Conversion Error", "Cannot convert between \"" + fromExpression + "\" and \"" + toExpression + "\"."); return Optional.empty(); } - + final UncertainDouble uncertainValue; + + // uncertainty is meaningless for non-linear units, so we will have + // to erase uncertainty information for them + if (to instanceof LinearUnit) { + final var toLinear = (LinearUnit) to; + uncertainValue = from.convertTo(toLinear).getValue(); + } else { + final var value = from.asUnitValue().convertTo(to).getValue(); + uncertainValue = UncertainDouble.of(value, 0); + } + + final var uc = UnitConversionRecord.valueOf( + fromExpression, toExpression, "", + this.numberDisplayRule.apply(uncertainValue)); + return Optional.of(uc); + } - + /** * Convert an expression to a MultiUnit. If an error happened, it is shown to * the view and Optional.empty() is returned. @@ -542,27 +540,26 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - + final List toUnits = new ArrayList<>(toExpressions.length); - for (int i = 0; i < toExpressions.length; i++) { + for (final String toExpression : toExpressions) { try { - final Unit toI = this.database - .getUnitFromExpression(toExpressions[i].trim()); - if (toI instanceof LinearUnit) { - toUnits.add((LinearUnit) toI); - } else { + final var toI = this.database + .getUnitFromExpression(toExpression.trim()); + if (!(toI instanceof LinearUnit)) { this.view.showErrorMessage("Unit Type Error", "Units separated by ';' must be linear; " + toI + " is not."); return Optional.empty(); } + toUnits.add((LinearUnit) toI); } catch (final IllegalArgumentException | NoSuchElementException e) { this.view.showErrorMessage("Parse Error", "Could not recognize text in To entry: " + e.getMessage()); return Optional.empty(); } } - + final List toValues; try { toValues = from.convertToMultiple(toUnits); @@ -571,12 +568,12 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - - final String toExpression = this.linearUnitValueSumToString(toValues); + + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Convert an expression to a MultiUnit with a name from the database. If an * error happened, it is shown to the view and Optional.empty() is returned. @@ -593,8 +590,8 @@ public final class Presenter { "Could not recognize text in From entry: " + e.getMessage()); return Optional.empty(); } - - final List toUnits = this.database.getUnitSet(toName); + + final var toUnits = this.database.getUnitSet(toName); final List toValues; try { toValues = from.convertToMultiple(toUnits); @@ -603,16 +600,16 @@ public final class Presenter { "Invalid units separated by ';': " + e.getMessage()); return Optional.empty(); } - - final String toExpression = this.linearUnitValueSumToString(toValues); + + final var toExpression = this.linearUnitValueSumToString(toValues); return Optional.of( UnitConversionRecord.valueOf(fromExpression, toExpression, "", "")); } - + /** * Converts from the view's input unit to its output unit. Displays an error * message if any of the required fields are invalid. - * + * * @throws UnsupportedOperationException if the view does not support * unit-based conversion (does not * implement @@ -620,64 +617,61 @@ public final class Presenter { * @since 2021-12-15 */ public void convertUnits() { - if (this.view instanceof UnitConversionView) { - final UnitConversionView ucview = (UnitConversionView) this.view; - - final Optional fromUnitOptional = ucview.getFromSelection(); - final Optional toUnitOptional = ucview.getToSelection(); - final String inputValueString = ucview.getInputValue(); - - // extract values from optionals - final String fromUnitString, toUnitString; - if (fromUnitOptional.isPresent()) { - fromUnitString = fromUnitOptional.orElseThrow(); - } else { - this.view.showErrorMessage("Unit Selection Error", - "Please specify a From unit"); - return; - } - if (toUnitOptional.isPresent()) { - toUnitString = toUnitOptional.orElseThrow(); - } else { - this.view.showErrorMessage("Unit Selection Error", - "Please specify a To unit"); - return; - } - - // convert strings to data, checking if anything is invalid - final Unit fromUnit; - final UncertainDouble uncertainValue; - - if (this.database.containsUnitName(fromUnitString)) { - fromUnit = this.database.getUnit(fromUnitString); - } else - throw this.viewError("Nonexistent From unit: %s", fromUnitString); - try { - uncertainValue = UncertainDouble - .fromRoundedString(inputValueString); - } catch (final NumberFormatException e) { - this.view.showErrorMessage("Value Error", - "Invalid value " + inputValueString); - return; - } - if (this.database.containsUnitName(toUnitString)) { - final Unit toUnit = this.database.getUnit(toUnitString); - ucview.showUnitConversionOutput( - this.convertUnitToUnit(fromUnitString, toUnitString, - inputValueString, fromUnit, toUnit, uncertainValue)); - } else if (this.database.containsUnitSetName(toUnitString)) { - final List toMulti = this.database - .getUnitSet(toUnitString); - ucview.showUnitConversionOutput( - this.convertUnitToMulti(fromUnitString, inputValueString, - fromUnit, toMulti, uncertainValue)); - } else - throw this.viewError("Nonexistent To unit: %s", toUnitString); - } else + if (!(this.view instanceof UnitConversionView)) throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); + final var ucview = (UnitConversionView) this.view; + + final var fromUnitOptional = ucview.getFromSelection(); + final var toUnitOptional = ucview.getToSelection(); + final var inputValueString = ucview.getInputValue(); + + // extract values from optionals + final String fromUnitString, toUnitString; + if (fromUnitOptional.isPresent()) { + fromUnitString = fromUnitOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Selection Error", + "Please specify a From unit"); + return; + } + if (toUnitOptional.isPresent()) { + toUnitString = toUnitOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Selection Error", + "Please specify a To unit"); + return; + } + + // convert strings to data, checking if anything is invalid + final Unit fromUnit; + final UncertainDouble uncertainValue; + + if (this.database.containsUnitName(fromUnitString)) { + fromUnit = this.database.getUnit(fromUnitString); + } else + throw this.viewError("Nonexistent From unit: %s", fromUnitString); + try { + uncertainValue = UncertainDouble.fromRoundedString(inputValueString); + } catch (final NumberFormatException e) { + this.view.showErrorMessage("Value Error", + "Invalid value " + inputValueString); + return; + } + if (this.database.containsUnitName(toUnitString)) { + final var toUnit = this.database.getUnit(toUnitString); + ucview.showUnitConversionOutput( + this.convertUnitToUnit(fromUnitString, toUnitString, + inputValueString, fromUnit, toUnit, uncertainValue)); + } else if (this.database.containsUnitSetName(toUnitString)) { + final var toMulti = this.database + .getUnitSet(toUnitString); + ucview.showUnitConversionOutput(this.convertUnitToMulti(fromUnitString, + inputValueString, fromUnit, toMulti, uncertainValue)); + } else + throw this.viewError("Nonexistent To unit: %s", toUnitString); } - + private UnitConversionRecord convertUnitToMulti(String fromUnitString, String inputValueString, Unit fromUnit, List toMulti, UncertainDouble uncertainValue) { @@ -686,7 +680,7 @@ public final class Presenter { throw this.viewError("Could not convert between %s and %s", fromUnit, toUnit); } - + final LinearUnitValue initValue; if (fromUnit instanceof LinearUnit) { final var fromLinear = (LinearUnit) fromUnit; @@ -695,46 +689,46 @@ public final class Presenter { initValue = UnitValue.of(fromUnit, uncertainValue.value()) .convertToBase(NameSymbol.EMPTY); } - - final List converted = initValue + + final var converted = initValue .convertToMultiple(toMulti); - final String toExpression = this.linearUnitValueSumToString(converted); + final var toExpression = this.linearUnitValueSumToString(converted); return UnitConversionRecord.valueOf(fromUnitString, toExpression, inputValueString, ""); - + } - + private UnitConversionRecord convertUnitToUnit(String fromUnitString, String toUnitString, String inputValueString, Unit fromUnit, Unit toUnit, UncertainDouble uncertainValue) { if (!fromUnit.canConvertTo(toUnit)) throw this.viewError("Could not convert between %s and %s", fromUnit, toUnit); - + // convert - we will need to erase uncertainty for non-linear units, so // we need to treat linear and non-linear units differently final String outputValueString; if (fromUnit instanceof LinearUnit && toUnit instanceof LinearUnit) { - final LinearUnit fromLinear = (LinearUnit) fromUnit; - final LinearUnit toLinear = (LinearUnit) toUnit; - final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear, + final var fromLinear = (LinearUnit) fromUnit; + final var toLinear = (LinearUnit) toUnit; + final var initialValue = LinearUnitValue.of(fromLinear, uncertainValue); - final LinearUnitValue converted = initialValue.convertTo(toLinear); - + final var converted = initialValue.convertTo(toLinear); + outputValueString = this.numberDisplayRule.apply(converted.getValue()); } else { - final UnitValue initialValue = UnitValue.of(fromUnit, + final var initialValue = UnitValue.of(fromUnit, uncertainValue.value()); - final UnitValue converted = initialValue.convertTo(toUnit); - + final var converted = initialValue.convertTo(toUnit); + outputValueString = this.numberDisplayRule .apply(UncertainDouble.of(converted.getValue(), 0)); } - + return UnitConversionRecord.valueOf(fromUnitString, toUnitString, inputValueString, outputValueString); } - + /** * @return true iff duplicate units are shown in unit lists * @since 2022-03-30 @@ -742,26 +736,26 @@ public final class Presenter { public boolean duplicatesShown() { return this.showDuplicates; } - + /** * @return text in About file * @since 2022-02-19 */ - public final String getAboutText() { + public String getAboutText() { return Presenter.getLinesFromResource("/about.txt").stream() .map(Presenter::withoutComments).collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); } - + /** * @return set of all locales available to select * @since 2025-02-21 */ - public final Set getAvailableLocales() { + public Set getAvailableLocales() { return this.locales.keySet(); } - + /** * Gets a name for this dimension using the database * @@ -769,28 +763,28 @@ public final class Presenter { * @return name of dimension * @since 2022-04-16 */ - final String getDimensionName(ObjectProduct dimension) { + String getDimensionName(ObjectProduct dimension) { // find this dimension in the database and get its name // if it isn't there, use the dimension's toString instead return this.database.dimensionMap().values().stream() .filter(d -> d.equals(dimension)).findAny().map(Nameable::getName) .orElse(dimension.toString(Nameable::getName)); } - + /** - * Gets the correct text for a provided ID. - * If this text is available in the user's locale, that text is provided. - * Otherwise, text is taken from the system default locale {@link #DEFAULT_LOCALE}. - * + * Gets the correct text for a provided ID. If this text is available in the + * user's locale, that text is provided. Otherwise, text is taken from the + * system default locale {@link #DEFAULT_LOCALE}. + * * @param textID ID of text to get (used in locale files) * @return text to be displayed */ public String getLocalizedText(String textID) { - final Map userLocale = this.locales.get(this.userLocale); - final Map defaultLocale = this.locales.get(DEFAULT_LOCALE); + final var userLocale = this.locales.get(this.userLocale); + final var defaultLocale = this.locales.get(DEFAULT_LOCALE); return userLocale.getOrDefault(textID, defaultLocale.get(textID)); } - + /** * @return the rule that is used by this presenter to convert numbers into * strings @@ -799,7 +793,7 @@ public final class Presenter { public Function getNumberDisplayRule() { return this.numberDisplayRule; } - + /** * @return the rule that is used by this presenter to convert strings into * numbers @@ -809,7 +803,7 @@ public final class Presenter { private Function getNumberParsingRule() { return this.numberParsingRule; } - + /** * @return the rule that determines whether a set of prefixes is valid * @since 2022-04-19 @@ -817,7 +811,7 @@ public final class Presenter { public Predicate> getPrefixRepetitionRule() { return this.prefixRepetitionRule; } - + /** * @return the rule that determines which units are prefixed * @since 2022-07-08 @@ -825,15 +819,7 @@ public final class Presenter { public Function, Map> getSearchRule() { return this.searchRule; } - - /** - * @return user's selected locale - * @since 2025-02-21 - */ - public String getUserLocale() { - return userLocale; - } - + /** * @return a search rule that shows all single prefixes * @since 2022-07-08 @@ -842,7 +828,15 @@ public final class Presenter { return PrefixSearchRule.getCoherentOnlyRule( new HashSet<>(this.database.prefixMap(true).values())); } - + + /** + * @return user's selected locale + * @since 2025-02-21 + */ + public String getUserLocale() { + return userLocale; + } + /** * @return the view associated with this presenter * @since 2022-04-19 @@ -850,7 +844,7 @@ public final class Presenter { public View getView() { return this.view; } - + /** * Accepts a list of errors. If that list is non-empty, prints an error * message and alerts the user. @@ -859,7 +853,7 @@ public final class Presenter { */ private void handleLoadErrors(List errors) { if (!errors.isEmpty()) { - final String errorMessage = String.format( + final var errorMessage = String.format( "%d error(s) happened while loading file:\n%s\n", errors.size(), errors.stream().map(Throwable::getMessage) .collect(Collectors.joining("\n"))); @@ -868,13 +862,13 @@ public final class Presenter { errorMessage); } } - + /** * @return whether or not the provided unit is semi-metric (i.e. an * exception) * @since 2022-04-16 */ - final boolean isSemiMetric(Unit u) { + boolean isSemiMetric(Unit u) { // determine if u is an exception final var primaryName = u.getPrimaryName(); final var symbol = u.getSymbol(); @@ -884,7 +878,7 @@ public final class Presenter { && this.metricExceptions.contains(symbol.orElseThrow()) || sharesAnyElements(this.metricExceptions, u.getOtherNames()); } - + /** * Convert a list of LinearUnitValues that you would get from a unit-set * conversion to a string. All but the last have their numbers rendered as @@ -893,18 +887,17 @@ public final class Presenter { * * @since 2024-08-16 */ - private final String linearUnitValueSumToString( - List values) { - final String integerPart = values.subList(0, values.size() - 1).stream() + private String linearUnitValueSumToString(List values) { + final var integerPart = values.subList(0, values.size() - 1).stream() .map(Presenter::linearUnitValueIntToString) .collect(Collectors.joining(" + ")); - final LinearUnitValue last = values.get(values.size() - 1); + final var last = values.get(values.size() - 1); return integerPart + " + " + this.numberDisplayRule.apply(last.getValue()) + " " + last.getUnit(); } - + private void loadExceptionFile(Path exceptionFile) { - try (Stream lines = Files.lines(exceptionFile)) { + try (var lines = Files.lines(exceptionFile)) { lines.map(Presenter::withoutComments) .forEach(this.metricExceptions::add); } catch (final IOException e) { @@ -913,7 +906,54 @@ public final class Presenter { + exceptionFile + "\": " + e.getLocalizedMessage()); } } + + /** + * Loads all available locales, including custom ones, into a map. + * @return map containing locales + */ + private Map> loadLocales() { + final Map> locales = new HashMap<>(); + for (final String localeName : LOCAL_LOCALES) { + final Map locale = new HashMap<>(); + String filename = "/locales/" + localeName + ".txt"; + getLinesFromResource(filename).forEach(line -> addLocaleLine(locale, line)); + locales.put(localeName, locale); + } + + if (Files.exists(USER_LOCALES_DIR)) { + try { + Files.list(USER_LOCALES_DIR).forEach( + localeFile -> { + try { + addLocaleFile(locales, localeFile); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + return locales; + } + private void addLocaleFile(Map> locales, Path file) throws IOException { + final Map locale = new HashMap<>(); + String fileName = file.getName(file.getNameCount()-1).toString(); + String localeName = fileName.substring(0, fileName.length()-4); + Files.lines(file).forEach(line -> addLocaleLine(locale, line)); + locales.put(localeName, locale); + } + + private void addLocaleLine(Map locale, String line) { + String[] parts = line.split("=", 2); + if (parts.length < 2) { + return; + } + + locale.put(parts[0], parts[1]); + } + /** * Loads settings from the user's settings file and applies them to the * presenter. @@ -924,8 +964,8 @@ public final class Presenter { void loadSettings(Path settingsFile) { for (final Map.Entry setting : this .settingsFromFile(settingsFile)) { - final String value = setting.getValue(); - + final var value = setting.getValue(); + switch (setting.getKey()) { // set manually to avoid the unnecessary saving of the non-manual // methods @@ -949,26 +989,29 @@ public final class Presenter { this.database.setPrefixRepetitionRule(this.prefixRepetitionRule); break; case "one_way": - this.oneWayConversionEnabled = Boolean.valueOf(value); + this.oneWayConversionEnabled = Boolean.parseBoolean(value); break; case "include_duplicates": - this.showDuplicates = Boolean.valueOf(value); + this.showDuplicates = Boolean.parseBoolean(value); break; case "search_prefix_rule": this.setSearchRuleFromString(value); break; + case "locale": + this.setUserLocale(value); + break; default: System.err.printf("Warning: unrecognized setting \"%s\".%n", setting.getKey()); break; } } - + if (this.view.getPresenter() != null) { this.updateView(); } } - + /** * @return a message showing how much stuff has been loaded * @since 2024-08-22 @@ -985,37 +1028,37 @@ public final class Presenter { this.database.unitSetMap().size(), this.database.dimensionMap().size()); } - + /** * @return true iff the One-Way Conversion feature is available (views that * show units as a list will have metric units removed from the From * unit list and imperial/USC units removed from the To unit list) - * + * * @since 2022-03-30 */ public boolean oneWayConversionEnabled() { return this.oneWayConversionEnabled; } - + /** * Completes creation of the presenter. This part of the initialization * depends on the view's functions, so it cannot be run if the components * they depend on are not created yet. - * + * * @since 2022-02-26 */ public void postViewInitialize() { // unit conversion specific stuff if (this.view instanceof UnitConversionView) { - final UnitConversionView ucview = (UnitConversionView) this.view; + final var ucview = (UnitConversionView) this.view; ucview.setDimensionNames(this.database.dimensionMap().keySet()); } - + this.updateView(); } - + void prefixSelected() { - final Optional selectedPrefixName = this.view + final var selectedPrefixName = this.view .getViewedPrefixName(); final Optional selectedPrefix = selectedPrefixName .map(name -> this.database.containsPrefixName(name) @@ -1025,16 +1068,16 @@ public final class Presenter { .ifPresent(prefix -> this.view.showPrefix(prefix.getNameSymbol(), String.valueOf(prefix.getMultiplier()))); } - + /** * Saves the presenter's current settings to the config file, creating it if * it doesn't exist. - * + * * @return false iff the presenter could not write to the file * @since 2022-04-19 */ public boolean saveSettings() { - final Path configDir = CONFIG_FILE.getParent(); + final var configDir = CONFIG_FILE.getParent(); if (!Files.exists(configDir)) { try { Files.createDirectories(configDir); @@ -1042,19 +1085,19 @@ public final class Presenter { return false; } } - + return this.writeSettings(CONFIG_FILE); } - + private void setDisplayRuleFromString(String ruleString) { - final String[] tokens = ruleString.split(" "); + final var tokens = ruleString.split(" "); switch (tokens[0]) { case "FIXED_DECIMALS": - final int decimals = Integer.parseInt(tokens[1]); + final var decimals = Integer.parseInt(tokens[1]); this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals); break; case "FIXED_PRECISION": - final int sigDigs = Integer.parseInt(tokens[1]); + final var sigDigs = Integer.parseInt(tokens[1]); this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs); break; case "UNCERTAINTY_BASED": @@ -1066,7 +1109,7 @@ public final class Presenter { break; } } - + /** * @param numberDisplayRule the new rule that will be used by this presenter * to convert numbers into strings @@ -1076,7 +1119,7 @@ public final class Presenter { Function numberDisplayRule) { this.numberDisplayRule = numberDisplayRule; } - + /** * @param numberParsingRule the new rule that will be used by this presenter * to convert strings into numbers @@ -1087,7 +1130,7 @@ public final class Presenter { Function numberParsingRule) { this.numberParsingRule = numberParsingRule; } - + /** * @param oneWayConversionEnabled whether not one-way conversion should be * enabled @@ -1098,7 +1141,7 @@ public final class Presenter { this.oneWayConversionEnabled = oneWayConversionEnabled; this.updateView(); } - + /** * @param prefixRepetitionRule the rule that determines whether a set of * prefixes is valid @@ -1109,7 +1152,7 @@ public final class Presenter { this.prefixRepetitionRule = prefixRepetitionRule; this.database.setPrefixRepetitionRule(prefixRepetitionRule); } - + /** * @param searchRule A rule that accepts a prefixless name-unit pair and * returns a map mapping names to prefixed versions of that @@ -1121,7 +1164,7 @@ public final class Presenter { Function, Map> searchRule) { this.searchRule = searchRule; } - + private void setSearchRuleFromString(String ruleString) { switch (ruleString) { case "NO_PREFIXES": @@ -1139,7 +1182,7 @@ public final class Presenter { ruleString); } } - + /** * @param showDuplicateUnits whether or not duplicate units should be shown * @since 2022-03-30 @@ -1148,18 +1191,9 @@ public final class Presenter { this.showDuplicates = showDuplicateUnits; this.updateView(); } - - /** - * Sets the user's locale, updating the view. - * @param userLocale locale to use - */ - public void setUserLocale(String userLocale) { - this.userLocale = userLocale; - this.view.updateText(); - } - + private List> settingsFromFile(Path settingsFile) { - try (Stream lines = Files.lines(settingsFile)) { + try (var lines = Files.lines(settingsFile)) { return lines.map(Presenter::withoutComments) .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine) .toList(); @@ -1169,7 +1203,17 @@ public final class Presenter { return null; } } - + + /** + * Sets the user's locale, updating the view. + * + * @param userLocale locale to use + */ + public void setUserLocale(String userLocale) { + this.userLocale = userLocale; + this.view.updateText(); + } + /** * Shows a unit in the unit viewer * @@ -1178,53 +1222,53 @@ public final class Presenter { */ private void showUnit(Unit u) { final var nameSymbol = u.getNameSymbol(); - final boolean isBase = u instanceof BaseUnit + final var isBase = u instanceof BaseUnit || u instanceof LinearUnit && ((LinearUnit) u).isBase(); final var definition = isBase ? "(Base unit)" : u.toDefinitionString(); final var dimensionString = this.getDimensionName(u.getDimension()); final var unitType = UnitType.getType(u, this::isSemiMetric); this.view.showUnit(nameSymbol, definition, dimensionString, unitType); } - + /** * Runs whenever a unit name is selected in the unit viewer. Gets the * description of a unit and displays it. - * + * * @since 2022-04-10 */ void unitNameSelected() { // get selected unit, if it's there and valid - final Optional selectedUnitName = this.view.getViewedUnitName(); + final var selectedUnitName = this.view.getViewedUnitName(); final Optional selectedUnit = selectedUnitName .map(unitName -> this.database.containsUnitName(unitName) ? this.database.getUnit(unitName) : null); selectedUnit.ifPresent(this::showUnit); } - + /** * Updates the view's From and To units, if it has some - * + * * @since 2021-12-15 */ public void updateView() { if (this.view instanceof UnitConversionView) { - final UnitConversionView ucview = (UnitConversionView) this.view; + final var ucview = (UnitConversionView) this.view; final var selectedDimensionName = ucview.getSelectedDimensionName(); - + // load units & prefixes into viewers this.view.setViewableUnitNames( this.database.unitMapPrefixless(this.showDuplicates).keySet()); this.view.setViewablePrefixNames( this.database.prefixMap(this.showDuplicates).keySet()); - + // get From and To units var fromUnits = this.database.unitMapPrefixless(this.showDuplicates) .entrySet().stream(); var toUnits = this.database.unitMapPrefixless(this.showDuplicates) .entrySet().stream(); var unitSets = this.database.unitSetMap().entrySet().stream(); - + // filter by dimension, if one is selected if (selectedDimensionName.isPresent()) { final var viewDimension = this.database @@ -1236,7 +1280,7 @@ public final class Presenter { unitSets = unitSets.filter(us -> viewDimension .equals(us.getValue().get(0).getDimension())); } - + // filter by unit type, if desired if (this.oneWayConversionEnabled) { fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(), @@ -1247,7 +1291,7 @@ public final class Presenter { unitSets = unitSets .filter(us -> this.metricExceptions.contains(us.getKey())); } - + // set unit names ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule) .map(Map.Entry::getKey).collect(Collectors.toSet())); @@ -1259,7 +1303,7 @@ public final class Presenter { ucview.setToUnitNames(toNames); } } - + /** * @param message message to add * @param args string formatting arguments for message @@ -1271,15 +1315,15 @@ public final class Presenter { return new AssertionError("View Programming Error (from " + this.view + "): " + String.format(message, args)); } - + /** * Saves the presenter's settings to the user settings file. - * + * * @param settingsFile file settings should be saved to * @since 2021-12-15 */ boolean writeSettings(Path settingsFile) { - try (BufferedWriter writer = Files.newBufferedWriter(settingsFile)) { + try (var writer = Files.newBufferedWriter(settingsFile)) { writer.write(String.format("number_display_rule=%s\n", displayRuleToString(this.numberDisplayRule))); writer.write( @@ -1290,6 +1334,7 @@ public final class Presenter { String.format("include_duplicates=%s\n", this.showDuplicates)); writer.write(String.format("search_prefix_rule=%s\n", searchRuleToString(this.searchRule))); + writer.write(String.format("locale=%s\n", this.userLocale)); return true; } catch (final IOException e) { e.printStackTrace(); diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 493fc10..cb0a4d2 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -218,7 +218,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // initialize important components this.presenter = new Presenter(this); - this.frame = new JFrame("7Units " + ProgramInfo.VERSION); + this.frame = new JFrame(this.presenter.getLocalizedText("tv.title") + .replace("[v]", ProgramInfo.VERSION.toString())); this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // master components (those that contain everything else within them) diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt new file mode 100644 index 0000000..9d453c9 --- /dev/null +++ b/src/main/resources/locales/en.txt @@ -0,0 +1 @@ +tv.title=7Units [v] \ No newline at end of file diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt new file mode 100644 index 0000000..1d08fda --- /dev/null +++ b/src/main/resources/locales/fr.txt @@ -0,0 +1 @@ +tv.title=7Unités [v] \ No newline at end of file -- cgit v1.2.3 From a62fd9e6b4519ffcbd0503c45b159207ce438243 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 17:48:55 -0500 Subject: Localize all user-facing strings --- src/main/java/sevenUnitsGUI/TabbedView.java | 114 ++++++++++++++++++++-------- src/main/resources/locales/en.txt | 30 +++++++- 2 files changed, 110 insertions(+), 34 deletions(-) (limited to 'src/main/resources') diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 1da119e..ca9f23c 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -24,13 +24,16 @@ import java.awt.event.ItemEvent; import java.awt.event.KeyEvent; import java.util.AbstractSet; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import javax.swing.BorderFactory; @@ -200,6 +203,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { private StandardRoundingType roundingType; private int precision; + private final Map> localizedTextSetters; + /** * Creates the view and makes it visible to the user * @@ -226,9 +231,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.masterPane = new JTabbedPane(); this.frame.add(this.masterPane); + this.localizedTextSetters = new HashMap<>(); + // ============ UNIT CONVERSION TAB ============ final JPanel convertUnitPanel = new JPanel(); this.masterPane.addTab("Convert Units", convertUnitPanel); + this.localizedTextSetters.put("tv.convert_units.title", + txt -> this.masterPane.setTitleAt(0, txt)); this.masterPane.setMnemonicAt(0, KeyEvent.VK_U); convertUnitPanel.setLayout(new BorderLayout()); @@ -264,7 +273,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { outputPanel.setLayout(new BorderLayout()); outputPanel.setBorder(new EmptyBorder(3, 6, 6, 6)); - final JLabel valuePrompt = new JLabel("Value to convert: "); + final JLabel valuePrompt = new JLabel(); + this.localizedTextSetters.put("tv.convert_units.value_prompt", + valuePrompt::setText); outputPanel.add(valuePrompt, BorderLayout.LINE_START); this.valueInput = new JTextField(); @@ -272,6 +283,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // conversion button this.convertUnitButton = new JButton("Convert"); + this.localizedTextSetters.put("tv.convert_units.convert_btn", + this.convertUnitButton::setText); outputPanel.add(this.convertUnitButton, BorderLayout.LINE_END); this.convertUnitButton .addActionListener(e -> this.presenter.convertUnits()); @@ -287,20 +300,26 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final JPanel convertExpressionPanel = new JPanel(); this.masterPane.addTab("Convert Unit Expressions", convertExpressionPanel); + this.localizedTextSetters.put("tv.convert_expressions.title", + txt -> this.masterPane.setTitleAt(1, txt)); this.masterPane.setMnemonicAt(1, KeyEvent.VK_E); convertExpressionPanel.setLayout(new GridLayout(4, 1)); // from and to expressions this.fromEntry = new JTextField(); convertExpressionPanel.add(this.fromEntry); - this.fromEntry.setBorder(BorderFactory.createTitledBorder("From")); + this.localizedTextSetters.put("tv.convert_expressions.from", + txt -> this.fromEntry.setBorder(BorderFactory.createTitledBorder(txt))); this.toEntry = new JTextField(); convertExpressionPanel.add(this.toEntry); - this.toEntry.setBorder(BorderFactory.createTitledBorder("To")); + this.localizedTextSetters.put("tv.convert_expressions.to", + txt -> this.toEntry.setBorder(BorderFactory.createTitledBorder(txt))); // button to convert - this.convertExpressionButton = new JButton("Convert"); + this.convertExpressionButton = new JButton(); + this.localizedTextSetters.put("tv.convert_expressions.convert_btn", + this.convertExpressionButton::setText); convertExpressionPanel.add(this.convertExpressionButton); this.convertExpressionButton @@ -310,13 +329,15 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // output of conversion this.expressionOutput = new JTextArea(2, 32); convertExpressionPanel.add(this.expressionOutput); - this.expressionOutput - .setBorder(BorderFactory.createTitledBorder("Output")); + this.localizedTextSetters.put("tv.convert_expressions.output", + txt -> this.expressionOutput.setBorder(BorderFactory.createTitledBorder(txt))); this.expressionOutput.setEditable(false); // =========== UNIT VIEWER =========== final JPanel unitLookupPanel = new JPanel(); this.masterPane.addTab("Unit Viewer", unitLookupPanel); + this.localizedTextSetters.put("tv.unit_viewer.title", + txt -> this.masterPane.setTitleAt(2, txt)); this.masterPane.setMnemonicAt(2, KeyEvent.VK_V); unitLookupPanel.setLayout(new GridLayout()); @@ -334,6 +355,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // ============ PREFIX VIEWER ============= final JPanel prefixLookupPanel = new JPanel(); this.masterPane.addTab("Prefix Viewer", prefixLookupPanel); + this.localizedTextSetters.put("tv.prefix_viewer.title", + txt -> this.masterPane.setTitleAt(3, txt)); this.masterPane.setMnemonicAt(3, KeyEvent.VK_P); prefixLookupPanel.setLayout(new GridLayout(1, 2)); @@ -389,7 +412,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { { final JPanel roundingPanel = new JPanel(); settingsPanel.add(roundingPanel); - roundingPanel.setBorder(new TitledBorder("Rounding Settings")); + this.localizedTextSetters.put("tv.settings.rounding.title", + txt -> roundingPanel.setBorder(new TitledBorder(txt))); roundingPanel.setLayout(new GridBagLayout()); // rounding rule selection @@ -399,13 +423,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { "Presenter loaded non-standard rounding rule")); this.precision = this.getPresenterPrecision().orElse(6); - final JLabel roundingRuleLabel = new JLabel("Rounding Rule:"); + final JLabel roundingRuleLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.rounding.rule", + roundingRuleLabel::setText); roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); // sigDigSlider needs to be first so that the rounding-type buttons can // show and hide it - final JLabel sliderLabel = new JLabel("Precision:"); + final JLabel sliderLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.rounding.precision", + sliderLabel::setText); sliderLabel.setVisible( this.roundingType != StandardRoundingType.UNCERTAINTY); roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4) @@ -431,8 +459,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { }); // significant digit rounding - final JRadioButton fixedPrecision = new JRadioButton( - "Fixed Precision"); + final JRadioButton fixedPrecision = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.fixed_sigfig", + fixedPrecision::setText); if (this.roundingType == StandardRoundingType.SIGNIFICANT_DIGITS) { fixedPrecision.setSelected(true); } @@ -447,8 +476,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { .setAnchor(GridBagConstraints.LINE_START).build()); // decimal place rounding - final JRadioButton fixedDecimals = new JRadioButton( - "Fixed Decimal Places"); + final JRadioButton fixedDecimals = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.fixed_places", + fixedDecimals::setText); if (this.roundingType == StandardRoundingType.DECIMAL_PLACES) { fixedDecimals.setSelected(true); } @@ -463,8 +493,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { .setAnchor(GridBagConstraints.LINE_START).build()); // scientific rounding - final JRadioButton relativePrecision = new JRadioButton( - "Uncertainty-Based Rounding"); + final JRadioButton relativePrecision = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.rounding.uncertainty", + relativePrecision::setText); if (this.roundingType == StandardRoundingType.UNCERTAINTY) { relativePrecision.setSelected(true); } @@ -483,8 +514,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { { final JPanel prefixRepetitionPanel = new JPanel(); settingsPanel.add(prefixRepetitionPanel); - prefixRepetitionPanel - .setBorder(new TitledBorder("Prefix Repetition Settings")); + this.localizedTextSetters.put("tv.settings.repetition.title", + txt -> prefixRepetitionPanel.setBorder(new TitledBorder(txt))); prefixRepetitionPanel.setLayout(new GridBagLayout()); final var prefixRule = this.getPresenterPrefixRule() @@ -494,7 +525,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // prefix rules final ButtonGroup prefixRuleButtons = new ButtonGroup(); - final JRadioButton noRepetition = new JRadioButton("No Repetition"); + final JRadioButton noRepetition = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.no", + noRepetition::setText); if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { noRepetition.setSelected(true); } @@ -507,7 +540,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton noRestriction = new JRadioButton("No Restriction"); + final JRadioButton noRestriction = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.any", + noRestriction::setText); if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { noRestriction.setSelected(true); } @@ -520,8 +555,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton customRepetition = new JRadioButton( - "Complex Repetition"); + final JRadioButton customRepetition = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.repetition.complex", + customRepetition::setText); if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { customRepetition.setSelected(true); } @@ -539,7 +575,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { { final JPanel searchingPanel = new JPanel(); settingsPanel.add(searchingPanel); - searchingPanel.setBorder(new TitledBorder("Search Settings")); + this.localizedTextSetters.put("tv.settings.search.title", + txt -> searchingPanel.setBorder(new TitledBorder(txt))); searchingPanel.setLayout(new GridBagLayout()); // searching rules @@ -547,8 +584,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { final var searchRule = this.presenter.getSearchRule(); - final JRadioButton noPrefixes = new JRadioButton( - "Never Include Prefixed Units"); + final JRadioButton noPrefixes = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.no_prefixes", + noPrefixes::setText); noPrefixes.addActionListener(e -> { this.presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES); this.presenter.updateView(); @@ -558,8 +596,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton commonPrefixes = new JRadioButton( - "Include Common Prefixes"); + final JRadioButton commonPrefixes = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.common_prefixes", + commonPrefixes::setText); commonPrefixes.addActionListener(e -> { this.presenter.setSearchRule(PrefixSearchRule.COMMON_PREFIXES); this.presenter.updateView(); @@ -569,8 +608,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { searchingPanel.add(commonPrefixes, new GridBagBuilder(0, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JRadioButton alwaysInclude = new JRadioButton( - "Include All Single Prefixes"); + final JRadioButton alwaysInclude = new JRadioButton(); + this.localizedTextSetters.put("tv.settings.search.all_prefixes", + alwaysInclude::setText); alwaysInclude.addActionListener(e -> { this.presenter .setSearchRule(this.presenter.getUniversalSearchRule()); @@ -599,7 +639,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { settingsPanel.add(miscPanel); miscPanel.setLayout(new GridBagLayout()); - final JCheckBox oneWay = new JCheckBox("Convert One Way Only"); + final JCheckBox oneWay = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.oneway", oneWay::setText); oneWay.setSelected(this.presenter.oneWayConversionEnabled()); oneWay.addItemListener(e -> { this.presenter.setOneWayConversionEnabled( @@ -609,8 +650,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(oneWay, new GridBagBuilder(0, 0, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JCheckBox showAllVariations = new JCheckBox( - "Show Duplicate Units & Prefixes"); + final JCheckBox showAllVariations = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.show_duplicate", + showAllVariations::setText); showAllVariations.setSelected(this.presenter.duplicatesShown()); showAllVariations.addItemListener(e -> { this.presenter @@ -620,7 +662,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); - final JLabel localeLabel = new JLabel("Locale:"); + final JLabel localeLabel = new JLabel(); + this.localizedTextSetters.put("tv.settings.locale", + localeLabel::setText); miscPanel.add(localeLabel, new GridBagBuilder(0, 2, 1, 1) .setAnchor(GridBagConstraints.LINE_START).build()); @@ -634,7 +678,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(localeSelector, new GridBagBuilder(1, 2, 1, 1) .setAnchor(GridBagConstraints.LINE_END).build()); - final JButton unitFileButton = new JButton("Manage Unit Data Files"); + final JButton unitFileButton = new JButton(); + this.localizedTextSetters.put("tv.settings.unitfiles.button", + unitFileButton::setText); unitFileButton.setEnabled(false); miscPanel.add(unitFileButton, new GridBagBuilder(0, 3, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); @@ -849,5 +895,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { public void updateText() { this.frame.setTitle(this.presenter.getLocalizedText("tv.title") .replace("[v]", ProgramInfo.VERSION.toString())); + this.localizedTextSetters.forEach((id, action) -> + action.accept(this.presenter.getLocalizedText(id))); } } diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt index 9d453c9..19ed781 100644 --- a/src/main/resources/locales/en.txt +++ b/src/main/resources/locales/en.txt @@ -1 +1,29 @@ -tv.title=7Units [v] \ No newline at end of file +tv.title=7Units [v] +tv.convert_units.title=Convert Units +tv.convert_units.value_prompt=Value to convert: +tv.convert_units.convert_btn=Convert +tv.convert_expressions.title=Convert Unit Expressions +tv.convert_expressions.from=From +tv.convert_expressions.to=To +tv.convert_expressions.convert_btn=Convert +tv.convert_expressions.output=Output +tv.unit_viewer.title=Unit Viewer +tv.prefix_viewer.title=Prefix Viewer +tv.settings.rounding.title=Rounding Settings +tv.settings.rounding.rule=Rounding Rule: +tv.settings.rounding.precision=Precision: +tv.settings.rounding.fixed_sigfig=Fixed Precision +tv.settings.rounding.fixed_places=Fixed Decimal Places +tv.settings.rounding.uncertainty=Uncertainty-based Rounding +tv.settings.repetition.title=Prefix Repetition Settings +tv.settings.repetition.no=No Repetition +tv.settings.repetition.any=No Restriction +tv.settings.repetition.complex=Complex Repetition +tv.settings.search.title=Search Settings +tv.settings.search.no_prefixes=Never Include Prefixed Units +tv.settings.search.common_prefixes=Include Common Prefixes +tv.settings.search.all_prefixes=Include All Single Prefixes +tv.settings.oneway=Convert One Way Only +tv.settings.show_duplicate=Show Duplicate Units & Prefixes +tv.settings.locale=🌐 Locale: +tv.settings.unitfiles.button=Manage Unit Data Files -- cgit v1.2.3 From 1007169658004c78c408f8bd1f4efbbeb6448323 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 18:14:53 -0500 Subject: Complete French locale translation --- src/main/resources/locales/fr.txt | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'src/main/resources') diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt index 1d08fda..d25e2b0 100644 --- a/src/main/resources/locales/fr.txt +++ b/src/main/resources/locales/fr.txt @@ -1 +1,29 @@ -tv.title=7Unités [v] \ No newline at end of file +tv.title=7Unités [v] +tv.convert_units.title=Convertir Unités +tv.convert_units.value_prompt=Valueur à convertir: +tv.convert_units.convert_btn=Convertir +tv.convert_expressions.title=Convertir Expressions +tv.convert_expressions.from=De +tv.convert_expressions.to=À +tv.convert_expressions.convert_btn=Convertir +tv.convert_expressions.output=Résultat +tv.unit_viewer.title=Unités +tv.prefix_viewer.title=Préfixes +tv.settings.rounding.title=Préférences d’Arrondissement +tv.settings.rounding.rule=Règle d’Arrondissement +tv.settings.rounding.precision=Précision: +tv.settings.rounding.fixed_sigfig=Precision fixe +tv.settings.rounding.fixed_places=Chiffres fixe +tv.settings.rounding.uncertainty=Arrondissement d’Incertitude +tv.settings.repetition.title=Préférences de répetition de préfixes +tv.settings.repetition.no=Non répetition +tv.settings.repetition.any=Tout répetition +tv.settings.repetition.complex=Répetition Complexe +tv.settings.search.title=Préférences de Recherche +tv.settings.search.no_prefixes=Jamais inclure unités préfixés +tv.settings.search.common_prefixes=Inclure préfixes fréquents +tv.settings.search.all_prefixes=Inclure tous préfixes seuls +tv.settings.oneway=Convertir Seulement en un Direction +tv.settings.show_duplicate=Montrer unités et préfixes doubles +tv.settings.locale=🌐 Locale: +tv.settings.unitfiles.button=Gérer donées d’unités -- cgit v1.2.3 From 4436b29053a0b757562ecc1d0a78e22902e6e5ae Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 19:20:30 -0500 Subject: Allow default datafile to be disabled If this option is deselected, the default unit, prefix, dimension and metric exception data will not be loaded, and only custom data and the few units that are not provided by files will be available. The main rationale for this change is so that the data can be localized by custom unit files. --- src/main/java/sevenUnitsGUI/Presenter.java | 93 +++++++++++++++++++++----- src/main/java/sevenUnitsGUI/TabbedView.java | 18 ++++- src/main/resources/locales/en.txt | 1 + src/main/resources/locales/fr.txt | 1 + src/test/java/sevenUnitsGUI/PresenterTest.java | 18 +++++ 5 files changed, 110 insertions(+), 21 deletions(-) (limited to 'src/main/resources') diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 6467f03..ba600e3 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -347,6 +347,19 @@ public final class Presenter { * unit view in views that show units as a list to choose from. */ private boolean showDuplicates = false; + + /** + * The default unit, prefix, dimension and exception data will only be loaded + * if this variable is true. + */ + private boolean useDefaultDatafiles = true; + + /** Custom unit datafiles that will be loaded by {@link #reloadData} */ + private final Set customUnitFiles = new HashSet<>(); + /** Custom dimension datafiles that will be loaded by {@link #reloadData} */ + private final Set customDimensionFiles = new HashSet<>(); + /** Custom exception datafiles that will be loaded by {@link #reloadData} */ + private final Set customExceptionFiles = new HashSet<>(); /** * Creates a Presenter @@ -357,8 +370,45 @@ public final class Presenter { public Presenter(View view) { this.view = view; this.database = new UnitDatabase(); - addDefaults(this.database); + this.metricExceptions = new HashSet<>(); + + this.locales = this.loadLocales(); + this.userLocale = DEFAULT_LOCALE; + + // set default settings temporarily + if (Files.exists(CONFIG_FILE)) { + this.loadSettings(CONFIG_FILE); + } + this.reloadData(); + + // print out unit counts + System.out.println(this.loadStatMsg()); + } + + /** + * Clears then reloads all unit, prefix, dimension and exception data. + */ + public void reloadData() { + this.database.clear(); + this.metricExceptions.clear(); + addDefaults(this.database); + + if (this.useDefaultDatafiles) { + this.loadDefaultData(); + } + + this.customUnitFiles.forEach( + path -> this.handleLoadErrors(this.database.loadUnitsFile(path))); + this.customDimensionFiles.forEach( + path -> this.handleLoadErrors(this.database.loadDimensionFile(path))); + this.customExceptionFiles.forEach(this::loadExceptionFile); + } + + /** + * Load units, prefixes and dimensions from the default files. + */ + private void loadDefaultData() { // load units and prefixes try (final var units = inputStream(DEFAULT_UNITS_FILEPATH)) { this.handleLoadErrors(this.database.loadUnitsFromStream(units)); @@ -377,7 +427,6 @@ public final class Presenter { // load metric exceptions try { - this.metricExceptions = new HashSet<>(); try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH); var scanner = new Scanner(exceptions)) { while (scanner.hasNextLine()) { @@ -392,17 +441,6 @@ public final class Presenter { throw new AssertionError("Loading of metric_exceptions.txt failed.", e); } - - this.locales = this.loadLocales(); - this.userLocale = DEFAULT_LOCALE; - - // set default settings temporarily - if (Files.exists(CONFIG_FILE)) { - this.loadSettings(CONFIG_FILE); - } - - // print out unit counts - System.out.println(this.loadStatMsg()); } /** @@ -962,6 +1000,10 @@ public final class Presenter { * @since 2021-12-15 */ void loadSettings(Path settingsFile) { + this.customDimensionFiles.clear(); + this.customExceptionFiles.clear(); + this.customUnitFiles.clear(); + for (final Map.Entry setting : this .settingsFromFile(settingsFile)) { final var value = setting.getValue(); @@ -970,15 +1012,13 @@ public final class Presenter { // set manually to avoid the unnecessary saving of the non-manual // methods case "custom_dimension_file": - this.handleLoadErrors( - this.database.loadDimensionFile(pathFromConfig(value))); + this.customDimensionFiles.add(pathFromConfig(value)); break; case "custom_exception_file": - this.loadExceptionFile(pathFromConfig(value)); + this.customExceptionFiles.add(pathFromConfig(value)); break; case "custom_unit_file": - this.handleLoadErrors( - this.database.loadUnitsFile(pathFromConfig(value))); + this.customUnitFiles.add(pathFromConfig(value)); break; case "number_display_rule": this.setDisplayRuleFromString(value); @@ -1204,6 +1244,16 @@ public final class Presenter { return null; } } + + /** + * Sets whether or not the default datafiles will be loaded. + * This method automatically updates the view's units. + */ + public void setUseDefaultDatafiles(boolean useDefaultDatafiles) { + this.useDefaultDatafiles = useDefaultDatafiles; + this.reloadData(); + this.updateView(); + } /** * Sets the user's locale, updating the view. @@ -1304,6 +1354,13 @@ public final class Presenter { ucview.setToUnitNames(toNames); } } + + /** + * @return true iff the default datafiles are being used + */ + public boolean usingDefaultDatafiles() { + return this.useDefaultDatafiles; + } /** * @param message message to add diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index ca9f23c..40ed0a7 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -662,10 +662,22 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); + final JCheckBox useDefaultFiles = new JCheckBox(); + this.localizedTextSetters.put("tv.settings.use_default_files", + useDefaultFiles::setText); + useDefaultFiles.setSelected(this.presenter.usingDefaultDatafiles()); + useDefaultFiles.addItemListener(e -> { + this.presenter + .setUseDefaultDatafiles(e.getStateChange() == ItemEvent.SELECTED); + this.presenter.saveSettings(); + }); + miscPanel.add(useDefaultFiles, new GridBagBuilder(0, 2, 2, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + final JLabel localeLabel = new JLabel(); this.localizedTextSetters.put("tv.settings.locale", localeLabel::setText); - miscPanel.add(localeLabel, new GridBagBuilder(0, 2, 1, 1) + miscPanel.add(localeLabel, new GridBagBuilder(0, 3, 1, 1) .setAnchor(GridBagConstraints.LINE_START).build()); this.presenter.getAvailableLocales().stream().sorted() @@ -675,14 +687,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.presenter.setUserLocale((String) e.getItem()); this.presenter.saveSettings(); }); - miscPanel.add(localeSelector, new GridBagBuilder(1, 2, 1, 1) + miscPanel.add(localeSelector, new GridBagBuilder(1, 3, 1, 1) .setAnchor(GridBagConstraints.LINE_END).build()); final JButton unitFileButton = new JButton(); this.localizedTextSetters.put("tv.settings.unitfiles.button", unitFileButton::setText); unitFileButton.setEnabled(false); - miscPanel.add(unitFileButton, new GridBagBuilder(0, 3, 2, 1) + miscPanel.add(unitFileButton, new GridBagBuilder(0, 4, 2, 1) .setAnchor(GridBagConstraints.LINE_START).build()); } diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt index 19ed781..5173cf3 100644 --- a/src/main/resources/locales/en.txt +++ b/src/main/resources/locales/en.txt @@ -25,5 +25,6 @@ tv.settings.search.common_prefixes=Include Common Prefixes tv.settings.search.all_prefixes=Include All Single Prefixes tv.settings.oneway=Convert One Way Only tv.settings.show_duplicate=Show Duplicate Units & Prefixes +tv.settings.use_default_files=Use Default Datafiles tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Manage Unit Data Files diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt index d25e2b0..e8b7138 100644 --- a/src/main/resources/locales/fr.txt +++ b/src/main/resources/locales/fr.txt @@ -25,5 +25,6 @@ tv.settings.search.common_prefixes=Inclure préfixes fréquents tv.settings.search.all_prefixes=Inclure tous préfixes seuls tv.settings.oneway=Convertir Seulement en un Direction tv.settings.show_duplicate=Montrer unités et préfixes doubles +tv.settings.use_default_files=Utilise donées par défaut tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Gérer donées d’unités diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 9e25a08..8b16365 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -17,6 +17,7 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -154,6 +155,23 @@ public final class PresenterTest { expectedOutput.getValue().toString(false, RoundingMode.HALF_EVEN)); assertEquals(List.of(expectedUC), viewBot.unitConversionList()); } + + /** + * Ensures that the default unitfile can be disabled. + * + * @since v1.0.0 + * @since 2025-02-23 + */ + @Test + void testDisableDefault() { + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); + assumeTrue(presenter.database.containsUnitName("joule"), + "Attempted to test disabling default on unit not in default file."); + presenter.setUseDefaultDatafiles(false); + assertFalse(presenter.database.containsUnitName("joule"), + "Presenter disabled default datafiles, but still contains the joule."); + } /** * Tests that duplicate units are successfully removed, if that is asked for -- cgit v1.2.3 From 9c358d708ba4988648d7b19ccb842f076ec4c354 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 23 Feb 2025 20:23:47 -0500 Subject: Allow internationalization of about.txt This works with custom locales (by placing the text in [config_dir]/about/[name].txt), but if such a file does not exist, it will default to the default locale (en)'s about text. --- src/main/java/sevenUnitsGUI/Presenter.java | 43 +++++++++++++++++++++-------- src/main/java/sevenUnitsGUI/TabbedView.java | 13 +++++---- src/main/resources/about.txt | 25 ----------------- src/main/resources/about/en.txt | 25 +++++++++++++++++ src/main/resources/about/fr.txt | 24 ++++++++++++++++ src/main/resources/locales/en.txt | 1 + src/main/resources/locales/fr.txt | 1 + 7 files changed, 90 insertions(+), 42 deletions(-) delete mode 100644 src/main/resources/about.txt create mode 100644 src/main/resources/about/en.txt create mode 100644 src/main/resources/about/fr.txt (limited to 'src/main/resources') diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index ba600e3..3a039a7 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -780,7 +780,27 @@ public final class Presenter { * @since 2022-02-19 */ public String getAboutText() { - return Presenter.getLinesFromResource("/about.txt").stream() + final Path customFilepath = Presenter.pathFromConfig( + "about/" + this.userLocale + ".txt"); + if (Files.exists(customFilepath)) { + try { + return formatAboutText(Files.lines(customFilepath)); + } catch (IOException e) { + final String filename = String.format("/about/%s.txt", this.userLocale); + return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } + } else if (LOCAL_LOCALES.contains(this.userLocale)) { + final String filename = String.format("/about/%s.txt", this.userLocale); + return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } else { + final String filename = String.format("/about/%s.txt", DEFAULT_LOCALE); + return formatAboutText(Presenter.getLinesFromResource(filename).stream()); + } + + } + + private String formatAboutText(Stream rawLines) { + return rawLines .map(Presenter::withoutComments).collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()) .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72)); @@ -1057,16 +1077,17 @@ public final class Presenter { * @since 2024-08-22 */ private String loadStatMsg() { - return String.format( - "Successfully loaded %d unique units with %d names (%d base units), %d unique prefixes with %d names, %d unit sets, and %d named dimensions.", - this.database.unitMapPrefixless(false).size(), - this.database.unitMapPrefixless(true).size(), - this.database.unitMapPrefixless(false).values().stream() - .filter(IS_FULL_BASE).count(), - this.database.prefixMap(false).size(), - this.database.prefixMap(true).size(), - this.database.unitSetMap().size(), - this.database.dimensionMap().size()); + return this.getLocalizedText("load_stat_msg") + .replace("[u]", Integer.toString( + this.database.unitMapPrefixless(false).size())) + .replace("[un]", Integer.toString( + this.database.unitMapPrefixless(true).size())) + .replace("[b]", Long.toString(this.database.unitMapPrefixless(false) + .values().stream().filter(IS_FULL_BASE).count())) + .replace("[p]", Integer.toString(this.database.prefixMap(false).size())) + .replace("[pn]", Integer.toString(this.database.prefixMap(true).size())) + .replace("[s]", Integer.toString(this.database.unitSetMap().size())) + .replace("[d]", Integer.toString(this.database.dimensionMap().size())); } /** diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 40ed0a7..9850aac 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -198,7 +198,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** The text box for prefix data in the prefix viewer */ private final JTextArea prefixTextBox; - // SETTINGS STUFF + // INFO & SETTINGS STUFF + final JTextArea infoTextArea; private final JComboBox localeSelector; private StandardRoundingType roundingType; private int precision; @@ -377,11 +378,10 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.masterPane.addTab("\uD83D\uDEC8", // info (i) character new JScrollPane(infoPanel)); - final JTextArea infoTextArea = new JTextArea(); - infoTextArea.setEditable(false); - infoTextArea.setOpaque(false); - infoPanel.add(infoTextArea); - infoTextArea.setText(this.presenter.getAboutText()); + this.infoTextArea = new JTextArea(); + this.infoTextArea.setEditable(false); + this.infoTextArea.setOpaque(false); + infoPanel.add(this.infoTextArea); // ============ SETTINGS PANEL ============ this.localeSelector = new JComboBox<>(); @@ -907,6 +907,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { public void updateText() { this.frame.setTitle(this.presenter.getLocalizedText("tv.title") .replace("[v]", ProgramInfo.VERSION.toString())); + this.infoTextArea.setText(this.presenter.getAboutText()); this.localizedTextSetters.forEach((id, action) -> action.accept(this.presenter.getLocalizedText(id))); } diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt deleted file mode 100644 index 4c33f8c..0000000 --- a/src/main/resources/about.txt +++ /dev/null @@ -1,25 +0,0 @@ -About 7Units Version [VERSION] - -7Units is a unit converter program with many features, -inspired by GNU Units (https://www.gnu.org/software/units/). -You can use it to simply convert units, but you can also -use it like a calculator, computing and converting expressions -like "10 m/s + (25^2 - 5^2) mi/hr". - -This software was written by Adrien Hopkins -. - -Unit/Prefix/Dimension Statistics: - -[LOADSTATS] - -Copyright Notice: - -Unit Converter Copyright (C) 2018-2024 Adrien Hopkins -This program comes with ABSOLUTELY NO WARRANTY; -for details read the LICENSE file, section 15 - -This is free software, and you are welcome to redistribute -it under certain conditions; for details go to - -or read the LICENSE file. diff --git a/src/main/resources/about/en.txt b/src/main/resources/about/en.txt new file mode 100644 index 0000000..068f922 --- /dev/null +++ b/src/main/resources/about/en.txt @@ -0,0 +1,25 @@ +About 7Units Version [VERSION] + +7Units is a unit converter program with many features, +inspired by GNU Units (https://www.gnu.org/software/units/). +You can use it to simply convert units, but you can also +use it like a calculator, computing and converting expressions +like "10 m/s + (25^2 - 5^2) mi/hr". + +This software was written by Adrien Hopkins +. + +Unit/Prefix/Dimension Statistics: + +[LOADSTATS] + +Copyright Notice: + +Unit Converter Copyright (C) 2018-2025 Adrien Hopkins +This program comes with ABSOLUTELY NO WARRANTY; +for details read the LICENSE file, section 15 + +This is free software, and you are welcome to redistribute +it under certain conditions; for details go to + +or read the LICENSE file. diff --git a/src/main/resources/about/fr.txt b/src/main/resources/about/fr.txt new file mode 100644 index 0000000..d8d82aa --- /dev/null +++ b/src/main/resources/about/fr.txt @@ -0,0 +1,24 @@ +À propos de 7Unités version [VERSION] + +7Unités est une programme pour convertir les unités avec plusieurs fonctions, +inspiré par GNU Units (https://www.gnu.org/software/units/). +Vous pouvez l’utiliser pour convertir des unités, mais vous pouvez aussi +l’utiliser comme calculatrice, computer et convertir des expressions +comme "10 m/s + (25^2 - 5^2) mi/hr". + +Ce logiciel est par Adrien Hopkins . + +Statistiques d’unités, préfixes et dimensions: + +[LOADSTATS] + +Copyright Notice: + +Unit Converter Copyright (C) 2018-2025 Adrien Hopkins +This program comes with ABSOLUTELY NO WARRANTY; +for details read the LICENSE file, section 15 + +This is free software, and you are welcome to redistribute +it under certain conditions; for details go to + +or read the LICENSE file. diff --git a/src/main/resources/locales/en.txt b/src/main/resources/locales/en.txt index 5173cf3..666e363 100644 --- a/src/main/resources/locales/en.txt +++ b/src/main/resources/locales/en.txt @@ -28,3 +28,4 @@ tv.settings.show_duplicate=Show Duplicate Units & Prefixes tv.settings.use_default_files=Use Default Datafiles tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Manage Unit Data Files +load_stat_msg=Successfully loaded [u] unique units with [un] names ([b] base units), [p] unique prefixes with [pn] names, [s] unit sets, and [d] named dimensions. diff --git a/src/main/resources/locales/fr.txt b/src/main/resources/locales/fr.txt index e8b7138..3fef030 100644 --- a/src/main/resources/locales/fr.txt +++ b/src/main/resources/locales/fr.txt @@ -28,3 +28,4 @@ tv.settings.show_duplicate=Montrer unités et préfixes doubles tv.settings.use_default_files=Utilise donées par défaut tv.settings.locale=🌐 Locale: tv.settings.unitfiles.button=Gérer donées d’unités +load_stat_msg=Chargé [u] unités uniques avec [un] noms ([b] unités bases), [p] préfixes uniques avec [pn] noms, [s] collections d’unités, et [d] dimensions nomées. -- cgit v1.2.3 From 06bd0e63f1b7bc83009ec3f4d7b048dea015529f Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 9 May 2025 21:20:00 -0500 Subject: Add tests for Presenter --- src/main/resources/unitsfile.txt | 4 +- src/test/java/sevenUnitsGUI/PresenterTest.java | 119 ++++++++++++------------- 2 files changed, 61 insertions(+), 62 deletions(-) (limited to 'src/main/resources') diff --git a/src/main/resources/unitsfile.txt b/src/main/resources/unitsfile.txt index dc33abd..17fe98a 100644 --- a/src/main/resources/unitsfile.txt +++ b/src/main/resources/unitsfile.txt @@ -194,8 +194,8 @@ ch chain furlong 10 chain mile 1760 yard mi mile -ftin foot; inch -ydftin yard; foot; inch +ftin ft; in +ydftin yd; ft; in # Imperial area units acre chain * furlong diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 8b16365..1d9b45b 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -21,10 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.math.RoundingMode; import java.nio.file.Path; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -33,12 +31,12 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; -import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; @@ -54,7 +52,7 @@ import sevenUnits.utils.UncertainDouble; * Note: this test outputs a lot to the standard output, because creating a * {@link UnitDatabase} and converting with a {@link ViewBot} both trigger * println statements. - * + * * @author Adrien Hopkins * * @since v0.4.0 @@ -78,7 +76,7 @@ public final class PresenterTest { * @since v0.4.0 * @since 2022-04-16 */ - private static final Stream> getRoundingRules() { + private static Stream> getRoundingRules() { final var SCIENTIFIC_ROUNDING = StandardDisplayRules.uncertaintyBased(); final var INTEGER_ROUNDING = StandardDisplayRules.fixedDecimals(0); final var SIG_FIG_ROUNDING = StandardDisplayRules.fixedPrecision(4); @@ -86,79 +84,80 @@ public final class PresenterTest { return Stream.of(SCIENTIFIC_ROUNDING, INTEGER_ROUNDING, SIG_FIG_ROUNDING); } - private static final Stream, Map>> getSearchRules() { + private static Stream, Map>> getSearchRules() { return SEARCH_RULES; } - private static final Set names(Set units) { + private static Set names(Set units) { return units.stream().map(Nameable::getName).collect(Collectors.toSet()); } + private static Stream testConvertExpressions() { + return Stream.of( + Arguments.of("10000.0 m", "km", "10000.0 m = 10.00000 km"), + Arguments.of("1.0 m", "ft; in", "1.0 m = 3 ft + 3 in"), + Arguments.of("1.0 m", "ftin", "1.0 m = 3 ft + 3 in")); + } + + private static Stream testConvertUnits() { + return Stream.of( + Arguments.of("m", "km", 10000.0, "10000.0 m = 10.00000 km"), + Arguments.of("m", "ftin", 1.0, "1.0 m = 3 ft + 3 in")); + } + /** * Test method for {@link Presenter#convertExpressions} - * + * * @since v0.4.0 * @since 2022-02-12 */ - @Test - void testConvertExpressions() { + @ParameterizedTest + @MethodSource + void testConvertExpressions(String from, String to, String expectedOutput) { // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); - viewBot.setFromExpression("10000.0 m"); - viewBot.setToExpression("km"); + viewBot.setFromExpression(from); + viewBot.setToExpression(to); // convert expression presenter.convertExpressions(); // test result - final List outputs = viewBot - .expressionConversionList(); - assertEquals("10000.0 m = 10.00000 km", - outputs.get(outputs.size() - 1).toString()); + final var outputs = viewBot.expressionConversionList(); + assertEquals(expectedOutput, outputs.get(outputs.size() - 1).toString()); } /** - * Tests that unit-conversion Views can correctly convert units - * + * Test method for {@link Presenter#convertUnits} + * * @since v0.4.0 * @since 2022-02-12 */ - @Test - void testConvertUnits() { + @ParameterizedTest + @MethodSource + void testConvertUnits(String from, String to, double value, + String expectedOutput) { // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); - viewBot.setFromUnitNames(names(testUnits)); - viewBot.setToUnitNames(names(testUnits)); - viewBot.setFromSelection("metre"); - viewBot.setToSelection("kilometre"); - viewBot.setInputValue("10000.0"); + viewBot.setFromSelection(from); + viewBot.setToSelection(to); + viewBot.setInputValue(Double.toString(value)); - // convert units + // convert expression presenter.convertUnits(); - /* - * use result from system as expected - I'm not testing unit conversion - * here (that's for the backend tests), I'm just testing that it correctly - * calls the unit conversion system - */ - final LinearUnitValue expectedInput = LinearUnitValue.of(Metric.METRE, - UncertainDouble.fromRoundedString("10000.0")); - final LinearUnitValue expectedOutput = expectedInput - .convertTo(Metric.KILOMETRE); - final UnitConversionRecord expectedUC = UnitConversionRecord.valueOf( - expectedInput.getUnit().getName(), - expectedOutput.getUnit().getName(), "10000.0", - expectedOutput.getValue().toString(false, RoundingMode.HALF_EVEN)); - assertEquals(List.of(expectedUC), viewBot.unitConversionList()); + // test result + final var outputs = viewBot.unitConversionList(); + assertEquals(expectedOutput, outputs.get(outputs.size() - 1).toString()); } - + /** * Ensures that the default unitfile can be disabled. - * + * * @since v1.0.0 * @since 2025-02-23 */ @@ -175,7 +174,7 @@ public final class PresenterTest { /** * Tests that duplicate units are successfully removed, if that is asked for - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -208,7 +207,7 @@ public final class PresenterTest { /** * Tests that one-way conversion correctly filters From and To units - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -242,7 +241,7 @@ public final class PresenterTest { /** * Tests the prefix-viewing functionality. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -272,7 +271,7 @@ public final class PresenterTest { /** * Tests that rounding rules are used correctly. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -291,9 +290,9 @@ public final class PresenterTest { presenter.convertUnits(); // test the result of the rounding - final String expectedOutputString = roundingRule + final var expectedOutputString = roundingRule .apply(UncertainDouble.fromRoundedString("12.3456789")); - final String actualOutputString = viewBot.unitConversionList().get(0) + final var actualOutputString = viewBot.unitConversionList().get(0) .outputValueString(); assertEquals(expectedOutputString, actualOutputString); } @@ -327,7 +326,7 @@ public final class PresenterTest { .apply(Map.entry("inch", BritishImperial.Length.INCH)).keySet()); expectedOutput.addAll( searchRule.apply(Map.entry("metre", Metric.METRE)).keySet()); - final Set actualOutput = viewBot.getFromUnitNames(); + final var actualOutput = viewBot.getFromUnitNames(); // test output assertEquals(expectedOutput, actualOutput); @@ -335,7 +334,7 @@ public final class PresenterTest { /** * Tests that settings can be saved to and loaded from a file. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -369,7 +368,7 @@ public final class PresenterTest { /** * Ensures the Presenter generates the correct data upon a unit-viewing. - * + * * @since v0.4.0 * @since 2022-04-16 */ @@ -403,15 +402,15 @@ public final class PresenterTest { /** * Test for {@link Presenter#updateView()} - * + * * @since v0.4.0 * @since 2022-02-12 */ @Test void testUpdateView() { // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); + final var viewBot = new ViewBot(); + final var presenter = new Presenter(viewBot); presenter.setOneWayConversionEnabled(false); presenter.setSearchRule(PrefixSearchRule.NO_PREFIXES); @@ -433,8 +432,8 @@ public final class PresenterTest { // filter to length units only, then get the filtered sets of units presenter.updateView(); - final Set fromUnits = viewBot.getFromUnitNames(); - final Set toUnits = viewBot.getToUnitNames(); + final var fromUnits = viewBot.getFromUnitNames(); + final var toUnits = viewBot.getToUnitNames(); // test that fromUnits/toUnits is [METRE, KILOMETRE] assertEquals(Set.of("metre", "kilometre"), fromUnits); -- cgit v1.2.3 From ef34d9b0a1346ec6a6243dc4df7c59612faf6768 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Tue, 10 Jun 2025 12:37:50 -0500 Subject: Correct sample expressions in about text The expressions were valid, but used 'hr' instead of 'h' for the hour, which is not the abbreviation used by 7Units. --- src/main/resources/about/en.txt | 2 +- src/main/resources/about/fr.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/resources') diff --git a/src/main/resources/about/en.txt b/src/main/resources/about/en.txt index 068f922..37986e5 100644 --- a/src/main/resources/about/en.txt +++ b/src/main/resources/about/en.txt @@ -4,7 +4,7 @@ About 7Units Version [VERSION] inspired by GNU Units (https://www.gnu.org/software/units/). You can use it to simply convert units, but you can also use it like a calculator, computing and converting expressions -like "10 m/s + (25^2 - 5^2) mi/hr". +like "10 m/s + (25^2 - 5^2) mi/h". This software was written by Adrien Hopkins . diff --git a/src/main/resources/about/fr.txt b/src/main/resources/about/fr.txt index d8d82aa..fc7ed83 100644 --- a/src/main/resources/about/fr.txt +++ b/src/main/resources/about/fr.txt @@ -4,7 +4,7 @@ inspiré par GNU Units (https://www.gnu.org/software/units/). Vous pouvez l’utiliser pour convertir des unités, mais vous pouvez aussi l’utiliser comme calculatrice, computer et convertir des expressions -comme "10 m/s + (25^2 - 5^2) mi/hr". +comme "10 m/s + (25^2 - 5^2) mi/h". Ce logiciel est par Adrien Hopkins . -- cgit v1.2.3