summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/sevenUnits/unit/LinearUnitValue.java103
-rw-r--r--src/main/java/sevenUnitsGUI/Presenter.java606
-rw-r--r--src/main/java/sevenUnitsGUI/TabbedView.java207
-rw-r--r--src/main/java/sevenUnitsGUI/UnitConversionRecord.java24
4 files changed, 527 insertions, 413 deletions
diff --git a/src/main/java/sevenUnits/unit/LinearUnitValue.java b/src/main/java/sevenUnits/unit/LinearUnitValue.java
index 3a9428b..fad3eb0 100644
--- a/src/main/java/sevenUnits/unit/LinearUnitValue.java
+++ b/src/main/java/sevenUnits/unit/LinearUnitValue.java
@@ -17,10 +17,13 @@
package sevenUnits.unit;
import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import sevenUnits.utils.DecimalComparison;
+import sevenUnits.utils.ObjectProduct;
import sevenUnits.utils.UncertainDouble;
/**
@@ -34,7 +37,7 @@ import sevenUnits.utils.UncertainDouble;
*/
public final class LinearUnitValue {
public static final LinearUnitValue ONE = getExact(Metric.ONE, 1);
-
+
/**
* Gets an exact {@code LinearUnitValue}
*
@@ -49,7 +52,7 @@ public final class LinearUnitValue {
Objects.requireNonNull(unit, "unit must not be null"),
UncertainDouble.of(value, 0));
}
-
+
/**
* Gets an uncertain {@code LinearUnitValue}
*
@@ -65,11 +68,11 @@ public final class LinearUnitValue {
Objects.requireNonNull(unit, "unit must not be null"),
Objects.requireNonNull(value, "value may not be null"));
}
-
+
private final LinearUnit unit;
-
+
private final UncertainDouble value;
-
+
/**
* @param unit unit to express as
* @param value value to express
@@ -79,7 +82,7 @@ public final class LinearUnitValue {
this.unit = unit;
this.value = value;
}
-
+
/**
* @return this value as a {@code UnitValue}. All uncertainty information is
* removed from the returned value.
@@ -88,7 +91,7 @@ public final class LinearUnitValue {
public final UnitValue asUnitValue() {
return UnitValue.of(this.unit, this.value.value());
}
-
+
/**
* @param other a {@code LinearUnit}
* @return true iff this value can be represented with {@code other}.
@@ -97,7 +100,7 @@ public final class LinearUnitValue {
public final boolean canConvertTo(final LinearUnit other) {
return this.unit.canConvertTo(other);
}
-
+
/**
* Returns a LinearUnitValue that represents the same value expressed in a
* different unit
@@ -109,7 +112,43 @@ public final class LinearUnitValue {
public final LinearUnitValue convertTo(final LinearUnit other) {
return LinearUnitValue.of(other, this.unit.convertTo(other, this.value));
}
-
+
+ /**
+ * Convert a LinearUnitValue to a sum of multiple units, where all but the
+ * last have exact integer values.
+ *
+ * @param others units to convert to
+ * @throws IllegalArgumentException if no units are provided or units
+ * provided have incompatible bases
+ * @since 2024-08-15
+ */
+ public final List<LinearUnitValue> convertToMultiple(
+ final List<LinearUnit> others) {
+ if (others.size() < 1)
+ throw new IllegalArgumentException("Must have at least one unit");
+ final ObjectProduct<BaseUnit> unitBase = others.get(0).getBase();
+ for (final LinearUnit unit : others) {
+ if (!unitBase.equals(unit.getBase()))
+ throw new IllegalArgumentException(
+ "All units must have the same base.");
+ }
+
+ LinearUnitValue remaining = this;
+ final List<LinearUnitValue> values = new ArrayList<>(others.size());
+ for (final LinearUnit unit : others.subList(0, others.size() - 1)) {
+ final LinearUnitValue remainingInUnit = remaining.convertTo(unit);
+ final LinearUnitValue value = getExact(unit,
+ Math.floor(remainingInUnit.getValueExact()));
+ values.add(value);
+ remaining = remaining.minus(value);
+ }
+
+ final LinearUnitValue lastValue = remaining
+ .convertTo(others.get(others.size() - 1));
+ values.add(lastValue);
+ return values;
+ }
+
/**
* Divides this value by a scalar
*
@@ -120,7 +159,7 @@ public final class LinearUnitValue {
public LinearUnitValue dividedBy(final double divisor) {
return LinearUnitValue.of(this.unit, this.value.dividedByExact(divisor));
}
-
+
/**
* Divides this value by another value
*
@@ -132,7 +171,7 @@ public final class LinearUnitValue {
return LinearUnitValue.of(this.unit.dividedBy(divisor.unit),
this.value.dividedBy(divisor.value));
}
-
+
/**
* Returns true if this and obj represent the same value, regardless of
* whether or not they are expressed in the same unit. So (1000 m).equals(1
@@ -150,7 +189,7 @@ public final class LinearUnitValue {
&& this.unit.convertToBase(this.value)
.equals(other.unit.convertToBase(other.value));
}
-
+
/**
* Returns true if this and obj represent the same value, regardless of
* whether or not they are expressed in the same unit. So (1000 m).equals(1
@@ -171,7 +210,7 @@ public final class LinearUnitValue {
&& DecimalComparison.equals(this.unit.convertToBase(this.value),
other.unit.convertToBase(other.value));
}
-
+
/**
* @param other another {@code LinearUnitValue}
* @return true iff this and other are within each other's uncertainty range
@@ -185,10 +224,10 @@ public final class LinearUnitValue {
final LinearUnit base = LinearUnit.valueOf(this.unit.getBase(), 1);
final LinearUnitValue thisBase = this.convertTo(base);
final LinearUnitValue otherBase = other.convertTo(base);
-
+
return thisBase.value.equivalent(otherBase.value);
}
-
+
/**
* @return the unit
* @since 2020-09-29
@@ -196,7 +235,7 @@ public final class LinearUnitValue {
public final LinearUnit getUnit() {
return this.unit;
}
-
+
/**
* @return the value
* @since 2020-09-29
@@ -204,7 +243,7 @@ public final class LinearUnitValue {
public final UncertainDouble getValue() {
return this.value;
}
-
+
/**
* @return the exact value
* @since 2020-09-07
@@ -212,13 +251,13 @@ public final class LinearUnitValue {
public final double getValueExact() {
return this.value.value();
}
-
+
@Override
public int hashCode() {
return Objects.hash(this.unit.getBase(),
this.unit.convertToBase(this.getValue()));
}
-
+
/**
* Returns the difference of this value and another, expressed in this
* value's unit
@@ -231,17 +270,17 @@ public final class LinearUnitValue {
*/
public LinearUnitValue minus(final LinearUnitValue subtrahend) {
Objects.requireNonNull(subtrahend, "subtrahend may not be null");
-
+
if (!this.canConvertTo(subtrahend.unit))
throw new IllegalArgumentException(String.format(
"Incompatible units for subtraction \"%s\" and \"%s\".",
this.unit, subtrahend.unit));
-
+
final LinearUnitValue otherConverted = subtrahend.convertTo(this.unit);
return LinearUnitValue.of(this.unit,
this.value.minus(otherConverted.value));
}
-
+
/**
* Returns the sum of this value and another, expressed in this value's unit
*
@@ -253,17 +292,17 @@ public final class LinearUnitValue {
*/
public LinearUnitValue plus(final LinearUnitValue addend) {
Objects.requireNonNull(addend, "addend may not be null");
-
+
if (!this.canConvertTo(addend.unit))
throw new IllegalArgumentException(String.format(
"Incompatible units for addition \"%s\" and \"%s\".", this.unit,
addend.unit));
-
+
final LinearUnitValue otherConverted = addend.convertTo(this.unit);
return LinearUnitValue.of(this.unit,
this.value.plus(otherConverted.value));
}
-
+
/**
* Multiplies this value by a scalar
*
@@ -274,7 +313,7 @@ public final class LinearUnitValue {
public LinearUnitValue times(final double multiplier) {
return LinearUnitValue.of(this.unit, this.value.timesExact(multiplier));
}
-
+
/**
* Multiplies this value by another value
*
@@ -286,7 +325,7 @@ public final class LinearUnitValue {
return LinearUnitValue.of(this.unit.times(multiplier.unit),
this.value.times(multiplier.value));
}
-
+
/**
* Raises a value to an exponent
*
@@ -298,12 +337,12 @@ public final class LinearUnitValue {
return LinearUnitValue.of(this.unit.toExponent(exponent),
this.value.toExponentExact(exponent));
}
-
+
@Override
public String toString() {
return this.toString(!this.value.isExact(), RoundingMode.HALF_EVEN);
}
-
+
/**
* Returns a string representing the object. <br>
* If the attached unit has a name or symbol, the string looks like "12 km".
@@ -321,9 +360,9 @@ public final class LinearUnitValue {
final Optional<String> primaryName = this.unit.getPrimaryName();
final Optional<String> symbol = this.unit.getSymbol();
final String chosenName = symbol.orElse(primaryName.orElse(null));
-
+
final UncertainDouble baseValue = this.unit.convertToBase(this.value);
-
+
// get rounded strings
// if showUncertainty is true, add brackets around the string
final String valueString = (showUncertainty ? "(" : "")
@@ -332,7 +371,7 @@ public final class LinearUnitValue {
final String baseValueString = (showUncertainty ? "(" : "")
+ baseValue.toString(showUncertainty, roundingMode)
+ (showUncertainty ? ")" : "");
-
+
// create string
if (chosenName == null)
return String.format("%s unnamed unit (= %s %s)", valueString,
diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java
index eba8438..8f99649 100644
--- a/src/main/java/sevenUnitsGUI/Presenter.java
+++ b/src/main/java/sevenUnitsGUI/Presenter.java
@@ -72,30 +72,7 @@ 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";
-
- private static final Path userConfigDir() {
- if (System.getProperty("os.name").startsWith("Windows")) {
- final String 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);
- }
- }
- }
-
- /** Gets a Path from a pathname in the config file. */
- private static Path pathFromConfig(String pathname) {
- return CONFIG_FILE.getParent().resolve(pathname);
- }
-
+
/**
* Adds default units and dimensions to a database.
*
@@ -117,14 +94,28 @@ 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<UncertainDouble, String> numberDisplayRule) {
+ if (numberDisplayRule instanceof FixedDecimals)
+ return String.format("FIXED_DECIMALS %d",
+ ((FixedDecimals) numberDisplayRule).decimalPlaces());
+ else if (numberDisplayRule instanceof FixedPrecision)
+ return String.format("FIXED_PRECISION %d",
+ ((FixedPrecision) numberDisplayRule).significantFigures());
+ else if (numberDisplayRule instanceof UncertaintyBased)
+ return "UNCERTAINTY_BASED";
+ else
+ return numberDisplayRule.toString();
+ }
+
/**
* @return text in About file
* @since 2022-02-19
@@ -134,7 +125,7 @@ public final class Presenter {
.map(Presenter::withoutComments).collect(Collectors.joining("\n"))
.replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString());
}
-
+
/**
* Gets the text of a resource file as a set of strings (each one is one line
* of the text).
@@ -145,7 +136,7 @@ public final class Presenter {
*/
private static final List<String> getLinesFromResource(String filename) {
final List<String> lines = new ArrayList<>();
-
+
try (InputStream stream = inputStream(filename);
Scanner scanner = new Scanner(stream)) {
while (scanner.hasNextLine()) {
@@ -155,10 +146,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.
*
@@ -169,7 +160,38 @@ public final class Presenter {
private static final InputStream inputStream(String filepath) {
return Presenter.class.getResourceAsStream(filepath);
}
-
+
+ private static Map.Entry<String, String> parseSettingLine(String line) {
+ final int 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);
+
+ 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);
+ }
+
+ private static String searchRuleToString(
+ Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
+ if (PrefixSearchRule.NO_PREFIXES.equals(searchRule))
+ return "NO_PREFIXES";
+ else 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();
+ }
+
+ // ====== SETTINGS ======
+
/**
* @return true iff a and b have any elements in common
* @since 2022-04-19
@@ -181,7 +203,23 @@ public final class Presenter {
}
return false;
}
-
+
+ private static final Path userConfigDir() {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ final String 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);
+ }
+ }
+
/**
* @return {@code line} with any comments removed.
* @since 2021-03-13
@@ -190,25 +228,23 @@ public final class Presenter {
final int index = line.indexOf('#');
return index == -1 ? line : line.substring(0, index);
}
-
- // ====== SETTINGS ======
-
+
/**
* 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. <b>Not implemented yet.</b>
*/
private Function<String, UncertainDouble> 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
@@ -216,41 +252,41 @@ public final class Presenter {
*/
private Function<UncertainDouble, String> 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<List<UnitPrefix>> 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.Entry<String, LinearUnit>, Map<String, LinearUnit>> 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<String> metricExceptions;
-
+
/**
* 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
*
@@ -261,14 +297,14 @@ 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)) {
this.database.loadUnitsFromStream(units);
} catch (final IOException e) {
throw new AssertionError("Loading of unitsfile.txt failed.", e);
}
-
+
// load dimensions
try (final InputStream dimensions = inputStream(
DEFAULT_DIMENSIONS_FILEPATH)) {
@@ -276,7 +312,7 @@ public final class Presenter {
} catch (final IOException e) {
throw new AssertionError("Loading of dimensionfile.txt failed.", e);
}
-
+
// load metric exceptions
try {
this.metricExceptions = new HashSet<>();
@@ -294,16 +330,16 @@ public final class Presenter {
throw new AssertionError("Loading of metric_exceptions.txt failed.",
e);
}
-
+
// set default settings temporarily
if (Files.exists(CONFIG_FILE)) {
this.loadSettings(CONFIG_FILE);
}
-
+
// a Predicate that returns true iff the argument is a full base unit
final Predicate<Unit> isFullBase = unit -> unit instanceof LinearUnit
&& ((LinearUnit) unit).isBase();
-
+
// print out unit counts
System.out.printf(
"Successfully loaded %d units with %d unit names (%d base units).%n",
@@ -312,7 +348,7 @@ public final class Presenter {
this.database.unitMapPrefixless(false).values().stream()
.filter(isFullBase).count());
}
-
+
/**
* Applies a search rule to an entry in a name-unit map.
*
@@ -332,7 +368,7 @@ public final class Presenter {
} else
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.
@@ -346,10 +382,10 @@ public final class Presenter {
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",
@@ -361,54 +397,131 @@ public final class Presenter {
"Please enter a unit expression in the To: box.");
return;
}
-
- // evaluate expressions
- final LinearUnitValue from;
- final Unit to;
- try {
- from = this.database.evaluateUnitExpression(fromExpression);
- } catch (final IllegalArgumentException | NoSuchElementException e) {
- this.view.showErrorMessage("Parse Error",
- "Could not recognize text in From entry: " + e.getMessage());
- return;
+
+ final Optional<UnitConversionRecord> uc;
+ if (toExpression.contains(";")) {
+ final String[] toExpressions = toExpression.split(";");
+ uc = this.convertExpressionToMultiUnit(fromExpression,
+ toExpressions);
+ } else {
+ uc = this.convertExpressionToExpression(fromExpression,
+ toExpression);
+ }
+
+ uc.ifPresent(xcview::showExpressionConversionOutput);
+ } else
+ throw new UnsupportedOperationException(
+ "This function can only be called when the view is an ExpressionConversionView");
+ }
+
+ /**
+ * 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<UnitConversionRecord> convertExpressionToExpression(
+ String fromExpression, String toExpression) {
+ // evaluate expressions
+ final LinearUnitValue from;
+ final Unit to;
+ try {
+ from = this.database.evaluateUnitExpression(fromExpression);
+ } catch (final IllegalArgumentException | NoSuchElementException e) {
+ this.view.showErrorMessage("Parse Error",
+ "Could not recognize text in From entry: " + e.getMessage());
+ return Optional.empty();
+ }
+ try {
+ to = this.database.getUnitFromExpression(toExpression);
+ } catch (final IllegalArgumentException | NoSuchElementException e) {
+ this.view.showErrorMessage("Parse Error",
+ "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 {
+ this.view.showErrorMessage("Conversion Error",
+ "Cannot convert between \"" + fromExpression + "\" and \""
+ + toExpression + "\".");
+ return Optional.empty();
+ }
+
+ }
+
+ /**
+ * Convert an expression to a MultiUnit. If an error happened, it is shown to
+ * the view and Optional.empty() is returned.
+ *
+ * @since 2024-08-15
+ */
+ private Optional<UnitConversionRecord> convertExpressionToMultiUnit(
+ String fromExpression, String[] toExpressions) {
+ final LinearUnitValue from;
+ try {
+ from = this.database.evaluateUnitExpression(fromExpression);
+ } catch (final IllegalArgumentException | NoSuchElementException e) {
+ this.view.showErrorMessage("Parse Error",
+ "Could not recognize text in From entry: " + e.getMessage());
+ return Optional.empty();
+ }
+
+ final List<LinearUnit> toUnits = new ArrayList<>(toExpressions.length);
+ for (int i = 0; i < toExpressions.length; i++) {
try {
- to = this.database.getUnitFromExpression(toExpression);
+ final Unit toI = this.database
+ .getUnitFromExpression(toExpressions[i].trim());
+ if (toI instanceof LinearUnit) {
+ toUnits.add((LinearUnit) toI);
+ } else {
+ this.view.showErrorMessage("Unit Type Error",
+ "Units separated by ';' must be linear; " + toI
+ + " is not.");
+ return Optional.empty();
+ }
} catch (final IllegalArgumentException | NoSuchElementException e) {
this.view.showErrorMessage("Parse Error",
"Could not recognize text in To entry: " + e.getMessage());
- return;
- }
-
- // 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));
- xcview.showExpressionConversionOutput(uc);
- } else {
- this.view.showErrorMessage("Conversion Error",
- "Cannot convert between \"" + fromExpression + "\" and \""
- + toExpression + "\".");
+ return Optional.empty();
}
-
- } else
- throw new UnsupportedOperationException(
- "This function can only be called when the view is an ExpressionConversionView");
+ }
+
+ final List<LinearUnitValue> toValues;
+ try {
+ toValues = from.convertToMultiple(toUnits);
+ } catch (final IllegalArgumentException e) {
+ this.view.showErrorMessage("Unit Error",
+ "Invalid units separated by ';': " + e.getMessage());
+ return Optional.empty();
+ }
+
+ final String toExpression = toValues.stream().map(
+ v -> this.numberDisplayRule.apply(v.getValue()) + " " + v.getUnit())
+ .collect(Collectors.joining(" + "));
+ 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.
@@ -422,11 +535,11 @@ public final class Presenter {
public void convertUnits() {
if (this.view instanceof UnitConversionView) {
final UnitConversionView ucview = (UnitConversionView) this.view;
-
+
final Optional<String> fromUnitOptional = ucview.getFromSelection();
final Optional<String> toUnitOptional = ucview.getToSelection();
final String inputValueString = ucview.getInputValue();
-
+
// extract values from optionals
final String fromUnitString, toUnitString;
if (fromUnitOptional.isPresent()) {
@@ -443,11 +556,11 @@ public final class Presenter {
"Please specify a To unit");
return;
}
-
+
// convert strings to data, checking if anything is invalid
final Unit fromUnit, toUnit;
final UncertainDouble uncertainValue;
-
+
if (this.database.containsUnitName(fromUnitString)) {
fromUnit = this.database.getUnit(fromUnitString);
} else
@@ -464,11 +577,11 @@ public final class Presenter {
"Invalid value " + inputValueString);
return;
}
-
+
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;
@@ -478,18 +591,18 @@ public final class Presenter {
final LinearUnitValue initialValue = LinearUnitValue.of(fromLinear,
uncertainValue);
final LinearUnitValue converted = initialValue.convertTo(toLinear);
-
+
outputValueString = this.numberDisplayRule
.apply(converted.getValue());
} else {
final UnitValue initialValue = UnitValue.of(fromUnit,
uncertainValue.value());
final UnitValue converted = initialValue.convertTo(toUnit);
-
+
outputValueString = this.numberDisplayRule
.apply(UncertainDouble.of(converted.getValue(), 0));
}
-
+
ucview.showUnitConversionOutput(
UnitConversionRecord.valueOf(fromUnitString, toUnitString,
inputValueString, outputValueString));
@@ -497,7 +610,7 @@ public final class Presenter {
throw new UnsupportedOperationException(
"This function can only be called when the view is a UnitConversionView.");
}
-
+
/**
* @return true iff duplicate units are shown in unit lists
* @since 2022-03-30
@@ -505,7 +618,7 @@ public final class Presenter {
public boolean duplicatesShown() {
return this.showDuplicates;
}
-
+
/**
* Gets a name for this dimension using the database
*
@@ -520,7 +633,7 @@ public final class Presenter {
.filter(d -> d.equals(dimension)).findAny().map(Nameable::getName)
.orElse(dimension.toString(Nameable::getName));
}
-
+
/**
* @return the rule that is used by this presenter to convert numbers into
* strings
@@ -529,7 +642,7 @@ public final class Presenter {
public Function<UncertainDouble, String> getNumberDisplayRule() {
return this.numberDisplayRule;
}
-
+
/**
* @return the rule that is used by this presenter to convert strings into
* numbers
@@ -539,7 +652,7 @@ public final class Presenter {
private Function<String, UncertainDouble> getNumberParsingRule() {
return this.numberParsingRule;
}
-
+
/**
* @return the rule that determines whether a set of prefixes is valid
* @since 2022-04-19
@@ -547,7 +660,7 @@ public final class Presenter {
public Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
return this.prefixRepetitionRule;
}
-
+
/**
* @return the rule that determines which units are prefixed
* @since 2022-07-08
@@ -555,7 +668,7 @@ public final class Presenter {
public Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> getSearchRule() {
return this.searchRule;
}
-
+
/**
* @return a search rule that shows all single prefixes
* @since 2022-07-08
@@ -564,7 +677,7 @@ public final class Presenter {
return PrefixSearchRule.getCoherentOnlyRule(
new HashSet<>(this.database.prefixMap(true).values()));
}
-
+
/**
* @return the view associated with this presenter
* @since 2022-04-19
@@ -572,7 +685,7 @@ public final class Presenter {
public View getView() {
return this.view;
}
-
+
/**
* @return whether or not the provided unit is semi-metric (i.e. an
* exception)
@@ -588,7 +701,18 @@ public final class Presenter {
&& this.metricExceptions.contains(symbol.orElseThrow())
|| sharesAnyElements(this.metricExceptions, u.getOtherNames());
}
-
+
+ private void loadExceptionFile(Path exceptionFile) {
+ try (Stream<String> lines = Files.lines(exceptionFile)) {
+ lines.map(Presenter::withoutComments)
+ .forEach(this.metricExceptions::add);
+ } catch (final IOException e) {
+ this.view.showErrorMessage("File Load Error",
+ "Error loading configured metric exception file \""
+ + exceptionFile + "\": " + e.getLocalizedMessage());
+ }
+ }
+
/**
* Loads settings from the user's settings file and applies them to the
* presenter.
@@ -597,9 +721,10 @@ public final class Presenter {
* @since 2021-12-15
*/
void loadSettings(Path settingsFile) {
- for (Map.Entry<String, String> setting : settingsFromFile(settingsFile)) {
+ for (final Map.Entry<String, String> setting : this
+ .settingsFromFile(settingsFile)) {
final String value = setting.getValue();
-
+
switch (setting.getKey()) {
// set manually to avoid the unnecessary saving of the non-manual
// methods
@@ -635,86 +760,12 @@ public final class Presenter {
break;
}
}
-
+
if (this.view.getPresenter() != null) {
this.updateView();
}
}
-
- private List<Map.Entry<String, String>> settingsFromFile(Path settingsFile) {
- try (Stream<String> lines = Files.lines(settingsFile)) {
- return lines.map(Presenter::withoutComments)
- .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine)
- .toList();
- } catch (final IOException e) {
- this.view.showErrorMessage("Settings Loading Error",
- "Error loading settings file. Using default settings.");
- return null;
- }
- }
-
- private static Map.Entry<String, String> parseSettingLine(String line) {
- final int 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);
-
- return Map.entry(param, value);
- }
-
- private void setSearchRuleFromString(String ruleString) {
- switch (ruleString) {
- case "NO_PREFIXES":
- this.searchRule = PrefixSearchRule.NO_PREFIXES;
- break;
- case "COMMON_PREFIXES":
- this.searchRule = PrefixSearchRule.COMMON_PREFIXES;
- break;
- case "ALL_METRIC_PREFIXES":
- this.searchRule = PrefixSearchRule.ALL_METRIC_PREFIXES;
- break;
- default:
- System.err.printf(
- "Warning: unrecognized value for search_prefix_rule: %s\n",
- ruleString);
- }
- }
-
- private void setDisplayRuleFromString(String ruleString) {
- String[] tokens = ruleString.split(" ");
- switch (tokens[0]) {
- case "FIXED_DECIMALS":
- final int decimals = Integer.parseInt(tokens[1]);
- this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals);
- break;
- case "FIXED_PRECISION":
- final int sigDigs = Integer.parseInt(tokens[1]);
- this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs);
- break;
- case "UNCERTAINTY_BASED":
- this.numberDisplayRule = StandardDisplayRules.uncertaintyBased();
- break;
- default:
- this.numberDisplayRule = StandardDisplayRules
- .getStandardRule(ruleString);
- break;
- }
- }
-
- private void loadExceptionFile(Path exceptionFile) {
- try (Stream<String> lines = Files.lines(exceptionFile)) {
- lines.map(Presenter::withoutComments)
- .forEach(this.metricExceptions::add);
- } catch (IOException e) {
- this.view.showErrorMessage("File Load Error",
- "Error loading configured metric exception file \""
- + exceptionFile + "\": " + e.getLocalizedMessage());
- }
- }
-
+
/**
* @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
@@ -725,7 +776,7 @@ public final class Presenter {
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
@@ -739,10 +790,10 @@ public final class Presenter {
final UnitConversionView ucview = (UnitConversionView) this.view;
ucview.setDimensionNames(this.database.dimensionMap().keySet());
}
-
+
this.updateView();
}
-
+
void prefixSelected() {
final Optional<String> selectedPrefixName = this.view
.getViewedPrefixName();
@@ -754,7 +805,7 @@ 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.
@@ -767,68 +818,35 @@ public final class Presenter {
if (!Files.exists(configDir)) {
try {
Files.createDirectories(configDir);
- } catch (IOException e) {
+ } catch (final IOException e) {
return false;
}
}
-
+
return this.writeSettings(CONFIG_FILE);
}
-
- /**
- * 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)) {
- writer.write(String.format("number_display_rule=%s\n",
- displayRuleToString(this.numberDisplayRule)));
- writer.write(
- String.format("prefix_rule=%s\n", this.prefixRepetitionRule));
- writer.write(
- String.format("one_way=%s\n", this.oneWayConversionEnabled));
- writer.write(
- String.format("include_duplicates=%s\n", this.showDuplicates));
- writer.write(String.format("search_prefix_rule=%s\n",
- searchRuleToString(this.searchRule)));
- return true;
- } catch (final IOException e) {
- e.printStackTrace();
- this.view.showErrorMessage("I/O Error",
- "Error occurred while saving settings: "
- + e.getLocalizedMessage());
- return false;
+
+ private void setDisplayRuleFromString(String ruleString) {
+ final String[] tokens = ruleString.split(" ");
+ switch (tokens[0]) {
+ case "FIXED_DECIMALS":
+ final int decimals = Integer.parseInt(tokens[1]);
+ this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals);
+ break;
+ case "FIXED_PRECISION":
+ final int sigDigs = Integer.parseInt(tokens[1]);
+ this.numberDisplayRule = StandardDisplayRules.fixedPrecision(sigDigs);
+ break;
+ case "UNCERTAINTY_BASED":
+ this.numberDisplayRule = StandardDisplayRules.uncertaintyBased();
+ break;
+ default:
+ this.numberDisplayRule = StandardDisplayRules
+ .getStandardRule(ruleString);
+ break;
}
}
-
- private static String searchRuleToString(
- Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
- if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) {
- return "NO_PREFIXES";
- } else 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();
- }
-
- private static String displayRuleToString(
- Function<UncertainDouble, String> numberDisplayRule) {
- if (numberDisplayRule instanceof FixedDecimals) {
- return String.format("FIXED_DECIMALS %d",
- ((FixedDecimals) numberDisplayRule).decimalPlaces());
- } else if (numberDisplayRule instanceof FixedPrecision) {
- return String.format("FIXED_PRECISION %d",
- ((FixedPrecision) numberDisplayRule).significantFigures());
- } else if (numberDisplayRule instanceof UncertaintyBased) {
- return "UNCERTAINTY_BASED";
- } else
- return numberDisplayRule.toString();
- }
-
+
/**
* @param numberDisplayRule the new rule that will be used by this presenter
* to convert numbers into strings
@@ -838,7 +856,7 @@ public final class Presenter {
Function<UncertainDouble, String> numberDisplayRule) {
this.numberDisplayRule = numberDisplayRule;
}
-
+
/**
* @param numberParsingRule the new rule that will be used by this presenter
* to convert strings into numbers
@@ -849,7 +867,7 @@ public final class Presenter {
Function<String, UncertainDouble> numberParsingRule) {
this.numberParsingRule = numberParsingRule;
}
-
+
/**
* @param oneWayConversionEnabled whether not one-way conversion should be
* enabled
@@ -860,7 +878,7 @@ public final class Presenter {
this.oneWayConversionEnabled = oneWayConversionEnabled;
this.updateView();
}
-
+
/**
* @param prefixRepetitionRule the rule that determines whether a set of
* prefixes is valid
@@ -871,7 +889,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
@@ -883,7 +901,25 @@ public final class Presenter {
Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
this.searchRule = searchRule;
}
-
+
+ private void setSearchRuleFromString(String ruleString) {
+ switch (ruleString) {
+ case "NO_PREFIXES":
+ this.searchRule = PrefixSearchRule.NO_PREFIXES;
+ break;
+ case "COMMON_PREFIXES":
+ this.searchRule = PrefixSearchRule.COMMON_PREFIXES;
+ break;
+ case "ALL_METRIC_PREFIXES":
+ this.searchRule = PrefixSearchRule.ALL_METRIC_PREFIXES;
+ break;
+ default:
+ System.err.printf(
+ "Warning: unrecognized value for search_prefix_rule: %s\n",
+ ruleString);
+ }
+ }
+
/**
* @param showDuplicateUnits whether or not duplicate units should be shown
* @since 2022-03-30
@@ -892,7 +928,19 @@ public final class Presenter {
this.showDuplicates = showDuplicateUnits;
this.updateView();
}
-
+
+ private List<Map.Entry<String, String>> settingsFromFile(Path settingsFile) {
+ try (Stream<String> lines = Files.lines(settingsFile)) {
+ return lines.map(Presenter::withoutComments)
+ .filter(line -> !line.isBlank()).map(Presenter::parseSettingLine)
+ .toList();
+ } catch (final IOException e) {
+ this.view.showErrorMessage("Settings Loading Error",
+ "Error loading settings file. Using default settings.");
+ return null;
+ }
+ }
+
/**
* Shows a unit in the unit viewer
*
@@ -908,7 +956,7 @@ public final class Presenter {
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.
@@ -924,7 +972,7 @@ public final class Presenter {
: null);
selectedUnit.ifPresent(this::showUnit);
}
-
+
/**
* Updates the view's From and To units, if it has some
*
@@ -934,19 +982,19 @@ public final class Presenter {
if (this.view instanceof UnitConversionView) {
final UnitConversionView 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();
-
+
// filter by dimension, if one is selected
if (selectedDimensionName.isPresent()) {
final var viewDimension = this.database
@@ -956,7 +1004,7 @@ public final class Presenter {
toUnits = toUnits.filter(
u -> viewDimension.equals(u.getValue().getDimension()));
}
-
+
// filter by unit type, if desired
if (this.oneWayConversionEnabled) {
fromUnits = fromUnits.filter(u -> UnitType.getType(u.getValue(),
@@ -964,7 +1012,7 @@ public final class Presenter {
toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(),
this::isSemiMetric) != UnitType.NON_METRIC);
}
-
+
// set unit names
ucview.setFromUnitNames(fromUnits.flatMap(this::applySearchRule)
.map(Map.Entry::getKey).collect(Collectors.toSet()));
@@ -972,7 +1020,7 @@ public final class Presenter {
.map(Map.Entry::getKey).collect(Collectors.toSet()));
}
}
-
+
/**
* @param message message to add
* @param args string formatting arguments for message
@@ -984,4 +1032,32 @@ 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)) {
+ writer.write(String.format("number_display_rule=%s\n",
+ displayRuleToString(this.numberDisplayRule)));
+ writer.write(
+ String.format("prefix_rule=%s\n", this.prefixRepetitionRule));
+ writer.write(
+ String.format("one_way=%s\n", this.oneWayConversionEnabled));
+ writer.write(
+ String.format("include_duplicates=%s\n", this.showDuplicates));
+ writer.write(String.format("search_prefix_rule=%s\n",
+ searchRuleToString(this.searchRule)));
+ return true;
+ } catch (final IOException e) {
+ e.printStackTrace();
+ this.view.showErrorMessage("I/O Error",
+ "Error occurred while saving settings: "
+ + e.getLocalizedMessage());
+ return false;
+ }
+ }
}
diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java
index 997acc3..a71de44 100644
--- a/src/main/java/sevenUnitsGUI/TabbedView.java
+++ b/src/main/java/sevenUnitsGUI/TabbedView.java
@@ -78,7 +78,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*/
private static final class JComboBoxItemSet<E> extends AbstractSet<E> {
private final JComboBox<E> comboBox;
-
+
/**
* @param comboBox combo box to get items from
* @since 2022-02-19
@@ -86,17 +86,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
public JComboBoxItemSet(JComboBox<E> comboBox) {
this.comboBox = comboBox;
}
-
+
@Override
public Iterator<E> iterator() {
return new Iterator<>() {
private int index = 0;
-
+
@Override
public boolean hasNext() {
return this.index < JComboBoxItemSet.this.size();
}
-
+
@Override
public E next() {
if (this.hasNext())
@@ -107,14 +107,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
}
};
}
-
+
@Override
public int size() {
return this.comboBox.getItemCount();
}
-
+
}
-
+
/**
* The standard types of rounding, corresponding to the options on the
* TabbedView's settings panel.
@@ -139,7 +139,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*/
UNCERTAINTY;
}
-
+
/**
* Creates a TabbedView.
*
@@ -153,14 +153,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
@SuppressWarnings("unused")
final View view = new TabbedView();
}
-
+
/** The Presenter that handles this View */
final Presenter presenter;
/** The frame that this view lives on */
final JFrame frame;
/** The tabbed pane that contains all of the components */
final JTabbedPane masterPane;
-
+
// DIMENSION-BASED CONVERTER
/** The combo box that selects dimensions */
final JComboBox<String> dimensionSelector;
@@ -174,7 +174,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
final JButton convertUnitButton;
/** The output area in the dimension-based converter */
final JTextArea unitOutput;
-
+
// EXPRESSION-BASED CONVERTER
/** The "From" entry in the conversion panel */
final JTextField fromEntry;
@@ -184,7 +184,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
final JButton convertExpressionButton;
/** The output area in the conversion panel */
final JTextArea expressionOutput;
-
+
// UNIT AND PREFIX VIEWERS
/** The searchable list of unit names in the unit viewer */
private final SearchBoxList<String> unitNameList;
@@ -194,11 +194,11 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
private final JTextArea unitTextBox;
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
-
+
// SETTINGS STUFF
private StandardRoundingType roundingType;
private int precision;
-
+
/**
* Creates the view and makes it visible to the user
*
@@ -215,161 +215,161 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
System.err.println("Failed to enable system look-and-feel.");
e.printStackTrace();
}
-
+
// initialize important components
this.presenter = new Presenter(this);
this.frame = new JFrame("7Units " + ProgramInfo.VERSION);
this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
-
+
// master components (those that contain everything else within them)
this.masterPane = new JTabbedPane();
this.frame.add(this.masterPane);
-
+
// ============ UNIT CONVERSION TAB ============
final JPanel convertUnitPanel = new JPanel();
this.masterPane.addTab("Convert Units", convertUnitPanel);
this.masterPane.setMnemonicAt(0, KeyEvent.VK_U);
convertUnitPanel.setLayout(new BorderLayout());
-
+
{ // panel for input part
final JPanel inputPanel = new JPanel();
convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
inputPanel.setLayout(new GridLayout(1, 3));
inputPanel.setBorder(new EmptyBorder(6, 6, 3, 6));
-
+
this.fromSearch = new SearchBoxList<>();
inputPanel.add(this.fromSearch);
-
+
final JPanel inBetweenPanel = new JPanel();
inputPanel.add(inBetweenPanel);
inBetweenPanel.setLayout(new BorderLayout());
-
+
this.dimensionSelector = new JComboBox<>();
inBetweenPanel.add(this.dimensionSelector, BorderLayout.PAGE_START);
this.dimensionSelector
.addItemListener(e -> this.presenter.updateView());
-
+
final JLabel arrowLabel = new JLabel("-->");
inBetweenPanel.add(arrowLabel, BorderLayout.CENTER);
arrowLabel.setHorizontalAlignment(SwingConstants.CENTER);
-
+
this.toSearch = new SearchBoxList<>();
inputPanel.add(this.toSearch);
}
-
+
{ // panel for submit and output, and also value entry
final JPanel outputPanel = new JPanel();
convertUnitPanel.add(outputPanel, BorderLayout.PAGE_END);
outputPanel.setLayout(new BorderLayout());
outputPanel.setBorder(new EmptyBorder(3, 6, 6, 6));
-
+
final JLabel valuePrompt = new JLabel("Value to convert: ");
outputPanel.add(valuePrompt, BorderLayout.LINE_START);
-
+
this.valueInput = new JTextField();
outputPanel.add(this.valueInput, BorderLayout.CENTER);
-
+
// conversion button
this.convertUnitButton = new JButton("Convert");
outputPanel.add(this.convertUnitButton, BorderLayout.LINE_END);
this.convertUnitButton
.addActionListener(e -> this.presenter.convertUnits());
this.convertUnitButton.setMnemonic(KeyEvent.VK_ENTER);
-
+
// conversion output
this.unitOutput = new JTextArea(2, 32);
outputPanel.add(this.unitOutput, BorderLayout.PAGE_END);
this.unitOutput.setEditable(false);
}
-
+
// ============ EXPRESSION CONVERSION TAB ============
final JPanel convertExpressionPanel = new JPanel();
this.masterPane.addTab("Convert Unit Expressions",
convertExpressionPanel);
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.toEntry = new JTextField();
convertExpressionPanel.add(this.toEntry);
this.toEntry.setBorder(BorderFactory.createTitledBorder("To"));
-
+
// button to convert
this.convertExpressionButton = new JButton("Convert");
convertExpressionPanel.add(this.convertExpressionButton);
-
+
this.convertExpressionButton
.addActionListener(e -> this.presenter.convertExpressions());
this.convertExpressionButton.setMnemonic(KeyEvent.VK_ENTER);
-
+
// output of conversion
this.expressionOutput = new JTextArea(2, 32);
convertExpressionPanel.add(this.expressionOutput);
this.expressionOutput
.setBorder(BorderFactory.createTitledBorder("Output"));
this.expressionOutput.setEditable(false);
-
+
// =========== UNIT VIEWER ===========
final JPanel unitLookupPanel = new JPanel();
this.masterPane.addTab("Unit Viewer", unitLookupPanel);
this.masterPane.setMnemonicAt(2, KeyEvent.VK_V);
unitLookupPanel.setLayout(new GridLayout());
-
+
this.unitNameList = new SearchBoxList<>();
unitLookupPanel.add(this.unitNameList);
this.unitNameList.getSearchList()
.addListSelectionListener(e -> this.presenter.unitNameSelected());
-
+
// the text box for unit's toString
this.unitTextBox = new JTextArea();
unitLookupPanel.add(this.unitTextBox);
this.unitTextBox.setEditable(false);
this.unitTextBox.setLineWrap(true);
-
+
// ============ PREFIX VIEWER =============
final JPanel prefixLookupPanel = new JPanel();
this.masterPane.addTab("Prefix Viewer", prefixLookupPanel);
this.masterPane.setMnemonicAt(3, KeyEvent.VK_P);
prefixLookupPanel.setLayout(new GridLayout(1, 2));
-
+
this.prefixNameList = new SearchBoxList<>();
prefixLookupPanel.add(this.prefixNameList);
this.prefixNameList.getSearchList()
.addListSelectionListener(e -> this.presenter.prefixSelected());
-
+
// the text box for prefix's toString
this.prefixTextBox = new JTextArea();
prefixLookupPanel.add(this.prefixTextBox);
this.prefixTextBox.setEditable(false);
this.prefixTextBox.setLineWrap(true);
-
+
// ============ INFO PANEL ============
-
+
final JPanel infoPanel = new JPanel();
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(Presenter.getAboutText());
-
+
// ============ SETTINGS PANEL ============
this.masterPane.addTab("\u2699",
new JScrollPane(this.createSettingsPanel()));
this.masterPane.setMnemonicAt(5, KeyEvent.VK_S);
-
+
// ============ FINALIZE CREATION OF VIEW ============
this.presenter.postViewInitialize();
this.frame.pack();
this.frame.setVisible(true);
}
-
+
/**
* Creates and returns the settings panel (in its own function to make this
* code more organized, as this function is massive!)
@@ -378,28 +378,28 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*/
private JPanel createSettingsPanel() {
final JPanel settingsPanel = new JPanel();
-
+
settingsPanel
.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
-
+
// ============ ROUNDING SETTINGS ============
{
final JPanel roundingPanel = new JPanel();
settingsPanel.add(roundingPanel);
roundingPanel.setBorder(new TitledBorder("Rounding Settings"));
roundingPanel.setLayout(new GridBagLayout());
-
+
// rounding rule selection
final ButtonGroup roundingRuleButtons = new ButtonGroup();
this.roundingType = this.getPresenterRoundingType()
.orElseThrow(() -> new AssertionError(
"Presenter loaded non-standard rounding rule"));
this.precision = this.getPresenterPrecision().orElse(6);
-
+
final JLabel roundingRuleLabel = new JLabel("Rounding Rule:");
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:");
@@ -407,26 +407,26 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.roundingType != StandardRoundingType.UNCERTAINTY);
roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JSlider sigDigSlider = new JSlider(0, 12);
roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
sigDigSlider.setMajorTickSpacing(4);
sigDigSlider.setMinorTickSpacing(1);
sigDigSlider.setSnapToTicks(true);
sigDigSlider.setPaintTicks(true);
sigDigSlider.setPaintLabels(true);
-
+
sigDigSlider.setVisible(
this.roundingType != StandardRoundingType.UNCERTAINTY);
sigDigSlider.setValue(this.precision);
-
+
sigDigSlider.addChangeListener(e -> {
this.precision = sigDigSlider.getValue();
this.updatePresenterRoundingRule();
});
-
+
// significant digit rounding
final JRadioButton fixedPrecision = new JRadioButton(
"Fixed Precision");
@@ -442,7 +442,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
roundingRuleButtons.add(fixedPrecision);
roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
// decimal place rounding
final JRadioButton fixedDecimals = new JRadioButton(
"Fixed Decimal Places");
@@ -458,7 +458,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
roundingRuleButtons.add(fixedDecimals);
roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
// scientific rounding
final JRadioButton relativePrecision = new JRadioButton(
"Uncertainty-Based Rounding");
@@ -475,7 +475,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3)
.setAnchor(GridBagConstraints.LINE_START).build());
}
-
+
// ============ PREFIX REPETITION SETTINGS ============
{
final JPanel prefixRepetitionPanel = new JPanel();
@@ -483,14 +483,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRepetitionPanel
.setBorder(new TitledBorder("Prefix Repetition Settings"));
prefixRepetitionPanel.setLayout(new GridBagLayout());
-
+
final var prefixRule = this.getPresenterPrefixRule()
.orElseThrow(() -> new AssertionError(
"Presenter loaded non-standard prefix rule"));
-
+
// prefix rules
final ButtonGroup prefixRuleButtons = new ButtonGroup();
-
+
final JRadioButton noRepetition = new JRadioButton("No Repetition");
if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) {
noRepetition.setSelected(true);
@@ -503,7 +503,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRuleButtons.add(noRepetition);
prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton noRestriction = new JRadioButton("No Restriction");
if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) {
noRestriction.setSelected(true);
@@ -516,7 +516,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRuleButtons.add(noRestriction);
prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton customRepetition = new JRadioButton(
"Complex Repetition");
if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) {
@@ -531,19 +531,19 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
}
-
+
// ============ SEARCH SETTINGS ============
{
final JPanel searchingPanel = new JPanel();
settingsPanel.add(searchingPanel);
searchingPanel.setBorder(new TitledBorder("Search Settings"));
searchingPanel.setLayout(new GridBagLayout());
-
+
// searching rules
final ButtonGroup searchRuleButtons = new ButtonGroup();
-
+
final var searchRule = this.presenter.getSearchRule();
-
+
final JRadioButton noPrefixes = new JRadioButton(
"Never Include Prefixed Units");
noPrefixes.addActionListener(e -> {
@@ -554,7 +554,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
searchRuleButtons.add(noPrefixes);
searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton commonPrefixes = new JRadioButton(
"Include Common Prefixes");
commonPrefixes.addActionListener(e -> {
@@ -565,7 +565,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
searchRuleButtons.add(commonPrefixes);
searchingPanel.add(commonPrefixes, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JRadioButton alwaysInclude = new JRadioButton(
"Include All Single Prefixes");
alwaysInclude.addActionListener(e -> {
@@ -577,7 +577,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
searchRuleButtons.add(alwaysInclude);
searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
if (PrefixSearchRule.NO_PREFIXES.equals(searchRule)) {
noPrefixes.setSelected(true);
} else if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule)) {
@@ -589,13 +589,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.presenter.saveSettings();
}
}
-
+
// ============ OTHER SETTINGS ============
{
final JPanel miscPanel = new JPanel();
settingsPanel.add(miscPanel);
miscPanel.setLayout(new GridBagLayout());
-
+
final JCheckBox oneWay = new JCheckBox("Convert One Way Only");
oneWay.setSelected(this.presenter.oneWayConversionEnabled());
oneWay.addItemListener(e -> {
@@ -605,7 +605,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
});
miscPanel.add(oneWay, new GridBagBuilder(0, 0)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JCheckBox showAllVariations = new JCheckBox(
"Show Duplicate Units & Prefixes");
showAllVariations.setSelected(this.presenter.duplicatesShown());
@@ -616,49 +616,49 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
});
miscPanel.add(showAllVariations, new GridBagBuilder(0, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
-
+
final JButton unitFileButton = new JButton("Manage Unit Data Files");
unitFileButton.setEnabled(false);
miscPanel.add(unitFileButton, new GridBagBuilder(0, 2)
.setAnchor(GridBagConstraints.LINE_START).build());
}
-
+
return settingsPanel;
}
-
+
@Override
public Set<String> getDimensionNames() {
return Collections
.unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector));
}
-
+
@Override
public String getFromExpression() {
return this.fromEntry.getText();
}
-
+
@Override
public Optional<String> getFromSelection() {
return this.fromSearch.getSelectedValue();
}
-
+
@Override
public Set<String> getFromUnitNames() {
// this should work because the only way I can mutate the item list is
// with setFromUnits which only accepts a Set
return new HashSet<>(this.fromSearch.getItems());
}
-
+
@Override
public String getInputValue() {
return this.valueInput.getText();
}
-
+
@Override
public Presenter getPresenter() {
return this.presenter;
}
-
+
/**
* @return the precision of the presenter's rounding rule, if that is
* meaningful
@@ -678,7 +678,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
else
return OptionalInt.empty();
}
-
+
/**
* @return presenter's prefix repetition rule
* @since v0.4.0
@@ -690,7 +690,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
? Optional.of((DefaultPrefixRepetitionRule) prefixRule)
: Optional.empty();
}
-
+
/**
* Determines which rounding type the presenter is currently using, if any.
*
@@ -709,41 +709,41 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
else
return Optional.empty();
}
-
+
@Override
public Optional<String> getSelectedDimensionName() {
final String selectedItem = (String) this.dimensionSelector
.getSelectedItem();
return Optional.ofNullable(selectedItem);
}
-
+
@Override
public String getToExpression() {
return this.toEntry.getText();
}
-
+
@Override
public Optional<String> getToSelection() {
return this.toSearch.getSelectedValue();
}
-
+
@Override
public Set<String> getToUnitNames() {
// this should work because the only way I can mutate the item list is
// with setToUnits which only accepts a Set
return new HashSet<>(this.toSearch.getItems());
}
-
+
@Override
public Optional<String> getViewedPrefixName() {
return this.prefixNameList.getSelectedValue();
}
-
+
@Override
public Optional<String> getViewedUnitName() {
return this.unitNameList.getSelectedValue();
}
-
+
@Override
public void setDimensionNames(Set<String> dimensionNames) {
this.dimensionSelector.removeAllItems();
@@ -751,45 +751,44 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.dimensionSelector.addItem(d);
}
}
-
+
@Override
public void setFromUnitNames(Set<String> units) {
this.fromSearch.setItems(units);
}
-
+
@Override
public void setToUnitNames(Set<String> units) {
this.toSearch.setItems(units);
}
-
+
@Override
public void setViewablePrefixNames(Set<String> prefixNames) {
this.prefixNameList.setItems(prefixNames);
}
-
+
@Override
public void setViewableUnitNames(Set<String> unitNames) {
this.unitNameList.setItems(unitNames);
}
-
+
@Override
public void showErrorMessage(String title, String message) {
JOptionPane.showMessageDialog(this.frame, message, title,
JOptionPane.ERROR_MESSAGE);
}
-
+
@Override
public void showExpressionConversionOutput(UnitConversionRecord uc) {
- this.expressionOutput.setText(String.format("%s = %s %s", uc.fromName(),
- uc.outputValueString(), uc.toName()));
+ this.expressionOutput.setText(uc.toString());
}
-
+
@Override
public void showPrefix(NameSymbol name, String multiplierString) {
this.prefixTextBox.setText(
String.format("%s%nMultiplier: %s", name, multiplierString));
}
-
+
@Override
public void showUnit(NameSymbol name, String definition,
String dimensionName, UnitType type) {
@@ -797,12 +796,12 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
String.format("%s%nDefinition: %s%nDimension: %s%nType: %s", name,
definition, dimensionName, type));
}
-
+
@Override
public void showUnitConversionOutput(UnitConversionRecord uc) {
this.unitOutput.setText(uc.toString());
}
-
+
/**
* Sets the presenter's rounding rule to the one specified by the current
* settings
diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
index 43a62e6..fa64ee9 100644
--- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
+++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
@@ -44,7 +44,7 @@ public final class UnitConversionRecord {
input.getValue().toString(false, RoundingMode.HALF_EVEN),
output.getValue().toString(false, RoundingMode.HALF_EVEN));
}
-
+
/**
* Gets a {@code UnitConversionRecord} from two unit values
*
@@ -60,7 +60,7 @@ public final class UnitConversionRecord {
output.getUnit().getName(), String.valueOf(input.getValue()),
String.valueOf(output.getValue()));
}
-
+
/**
* Gets a {@code UnitConversionRecord}
*
@@ -78,7 +78,7 @@ public final class UnitConversionRecord {
return new UnitConversionRecord(fromName, toName, inputValueString,
outputValueString);
}
-
+
/**
* The name of the unit or expression that was converted from
*/
@@ -87,7 +87,7 @@ public final class UnitConversionRecord {
* The name of the unit or expression that was converted to
*/
private final String toName;
-
+
/**
* A string representing the input value. It doesn't need to be the same as
* the input value's string representation; it could be rounded, for example.
@@ -98,7 +98,7 @@ public final class UnitConversionRecord {
* the input value's string representation; it could be rounded, for example.
*/
private final String outputValueString;
-
+
/**
* @param fromName name of unit or expression that was converted
* from
@@ -114,7 +114,7 @@ public final class UnitConversionRecord {
this.inputValueString = inputValueString;
this.outputValueString = outputValueString;
}
-
+
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -144,7 +144,7 @@ public final class UnitConversionRecord {
return false;
return true;
}
-
+
/**
* @return name of unit or expression that was converted from
* @since v0.4.0
@@ -153,7 +153,7 @@ public final class UnitConversionRecord {
public String fromName() {
return this.fromName;
}
-
+
@Override
public int hashCode() {
final int prime = 31;
@@ -168,7 +168,7 @@ public final class UnitConversionRecord {
+ (this.toName == null ? 0 : this.toName.hashCode());
return result;
}
-
+
/**
* @return string representing input value
* @since v0.4.0
@@ -177,7 +177,7 @@ public final class UnitConversionRecord {
public String inputValueString() {
return this.inputValueString;
}
-
+
/**
* @return string representing output value
* @since v0.4.0
@@ -186,7 +186,7 @@ public final class UnitConversionRecord {
public String outputValueString() {
return this.outputValueString;
}
-
+
/**
* @return name of unit or expression that was converted to
* @since v0.4.0
@@ -195,7 +195,7 @@ public final class UnitConversionRecord {
public String toName() {
return this.toName;
}
-
+
@Override
public String toString() {
final String inputString = this.inputValueString.isBlank() ? this.fromName