summaryrefslogtreecommitdiff
path: root/src/main/java/sevenUnitsGUI
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2025-06-15 19:42:01 -0500
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2025-06-15 19:42:01 -0500
commit2fdbc084fd1d78f0b7633db34460b1195de264f3 (patch)
tree4c908950d9b049394f8160b8159b498aec586ecc /src/main/java/sevenUnitsGUI
parented53492243ecad8d975401a97f5b634328ad2c71 (diff)
parentbccb5b5e3452421c81c1fb58f83391ba6584807c (diff)
Merge release 1.0.0 into stable branchHEADstable
See the tag 'v1.0.0' or the changelog for more information about this release.
Diffstat (limited to 'src/main/java/sevenUnitsGUI')
-rw-r--r--src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java27
-rw-r--r--src/main/java/sevenUnitsGUI/DelegateListModel.java33
-rw-r--r--src/main/java/sevenUnitsGUI/ExpressionConversionView.java14
-rw-r--r--src/main/java/sevenUnitsGUI/FilterComparator.java25
-rw-r--r--src/main/java/sevenUnitsGUI/GridBagBuilder.java34
-rw-r--r--src/main/java/sevenUnitsGUI/Main.java6
-rw-r--r--src/main/java/sevenUnitsGUI/PrefixSearchRule.java33
-rw-r--r--src/main/java/sevenUnitsGUI/Presenter.java1209
-rw-r--r--src/main/java/sevenUnitsGUI/SearchBoxList.java54
-rw-r--r--src/main/java/sevenUnitsGUI/StandardDisplayRules.java48
-rw-r--r--src/main/java/sevenUnitsGUI/TabbedView.java257
-rw-r--r--src/main/java/sevenUnitsGUI/UnitConversionRecord.java45
-rw-r--r--src/main/java/sevenUnitsGUI/UnitConversionView.java33
-rw-r--r--src/main/java/sevenUnitsGUI/View.java31
-rw-r--r--src/main/java/sevenUnitsGUI/ViewBot.java70
-rw-r--r--src/main/java/sevenUnitsGUI/package-info.java5
16 files changed, 1289 insertions, 635 deletions
diff --git a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java
index 1fb2709..0e38c67 100644
--- a/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java
+++ b/src/main/java/sevenUnitsGUI/DefaultPrefixRepetitionRule.java
@@ -1,5 +1,18 @@
/**
- * @since 2020-08-26
+ * Copyright (C) 2020, 2022, 2024, 2025 Adrien Hopkins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package sevenUnitsGUI;
@@ -13,14 +26,17 @@ import sevenUnits.unit.UnitPrefix;
* A rule that specifies whether prefix repetition is allowed
*
* @since 2020-08-26
+ * @since v0.3.0
*/
public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> {
+ /** Prefix repetition is never allowed; only one prefix may be used. */
NO_REPETITION {
@Override
public boolean test(List<UnitPrefix> prefixes) {
return prefixes.size() <= 1;
}
},
+ /** Prefix repetition is always allowed, without restrictions. */
NO_RESTRICTION {
@Override
public boolean test(List<UnitPrefix> prefixes) {
@@ -40,7 +56,7 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> {
final boolean magnifying;
if (prefixes.isEmpty())
return true;
- else if (prefixes.get(0).getMultiplier() > 1) {
+ if (prefixes.get(0).getMultiplier() > 1) {
magnifying = true;
} else {
magnifying = false;
@@ -52,15 +68,14 @@ public enum DefaultPrefixRepetitionRule implements Predicate<List<UnitPrefix>> {
if (!Metric.DECIMAL_PREFIXES.contains(prefixes.get(0)))
return NO_REPETITION.test(prefixes);
- int part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto,
+ var part = 0; // 0=yotta/yoctos, 1=kilo-zetta/milli-zepto,
// 2=deka,hecto,deci,centi
for (final UnitPrefix prefix : prefixes) {
// check that the current prefix is metric and appropriately
// magnifying/reducing
- if (!Metric.DECIMAL_PREFIXES.contains(prefix))
- return false;
- if (magnifying != prefix.getMultiplier() > 1)
+ if (!Metric.DECIMAL_PREFIXES.contains(prefix)
+ || (magnifying != prefix.getMultiplier() > 1))
return false;
// check if the current prefix is correct
diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java
index 798383b..da4f978 100644
--- a/src/main/java/sevenUnitsGUI/DelegateListModel.java
+++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * Copyright (C) 2018, 2022, 2024 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -31,7 +31,7 @@ import javax.swing.AbstractListModel;
* the delegated list's methods because the delegate methods handle updating the
* list.
* </p>
- *
+ *
* @author Adrien Hopkins
* @since 2019-01-14
* @since v0.1.0
@@ -46,7 +46,7 @@ final class DelegateListModel<E> extends AbstractListModel<E>
/**
* The list that this model is a delegate to.
- *
+ *
* @since 2019-01-14
* @since v0.1.0
*/
@@ -54,8 +54,9 @@ final class DelegateListModel<E> extends AbstractListModel<E>
/**
* Creates an empty {@code DelegateListModel}.
- *
+ *
* @since 2019-04-13
+ * @since v0.2.0
*/
public DelegateListModel() {
this(new ArrayList<>());
@@ -63,7 +64,7 @@ final class DelegateListModel<E> extends AbstractListModel<E>
/**
* Creates the {@code DelegateListModel}.
- *
+ *
* @param delegate list to delegate
* @since 2019-01-14
* @since v0.1.0
@@ -74,8 +75,8 @@ final class DelegateListModel<E> extends AbstractListModel<E>
@Override
public boolean add(final E element) {
- final int index = this.delegate.size();
- final boolean success = this.delegate.add(element);
+ final var index = this.delegate.size();
+ final var success = this.delegate.add(element);
this.fireIntervalAdded(this, index, index);
return success;
}
@@ -88,7 +89,7 @@ final class DelegateListModel<E> extends AbstractListModel<E>
@Override
public boolean addAll(final Collection<? extends E> c) {
- boolean changed = false;
+ var changed = false;
for (final E e : c) {
if (this.add(e)) {
changed = true;
@@ -108,7 +109,7 @@ final class DelegateListModel<E> extends AbstractListModel<E>
@Override
public void clear() {
- final int oldSize = this.delegate.size();
+ final var oldSize = this.delegate.size();
this.delegate.clear();
if (oldSize >= 1) {
this.fireIntervalRemoved(this, 0, oldSize - 1);
@@ -176,7 +177,7 @@ final class DelegateListModel<E> extends AbstractListModel<E>
@Override
public E remove(final int index) {
- final E returnValue = this.delegate.get(index);
+ final var returnValue = this.delegate.get(index);
this.delegate.remove(index);
this.fireIntervalRemoved(this, index, index);
return returnValue;
@@ -184,15 +185,15 @@ final class DelegateListModel<E> extends AbstractListModel<E>
@Override
public boolean remove(final Object o) {
- final int index = this.delegate.indexOf(o);
- final boolean returnValue = this.delegate.remove(o);
+ final var index = this.delegate.indexOf(o);
+ final var returnValue = this.delegate.remove(o);
this.fireIntervalRemoved(this, index, index);
return returnValue;
}
@Override
public boolean removeAll(final Collection<?> c) {
- boolean changed = false;
+ var changed = false;
for (final Object e : c) {
if (this.remove(e)) {
changed = true;
@@ -203,15 +204,15 @@ final class DelegateListModel<E> extends AbstractListModel<E>
@Override
public boolean retainAll(final Collection<?> c) {
- final int oldSize = this.size();
- final boolean returnValue = this.delegate.retainAll(c);
+ final var oldSize = this.size();
+ final var returnValue = this.delegate.retainAll(c);
this.fireIntervalRemoved(this, this.size(), oldSize - 1);
return returnValue;
}
@Override
public E set(final int index, final E element) {
- final E returnValue = this.delegate.get(index);
+ final var returnValue = this.delegate.get(index);
this.delegate.set(index, element);
this.fireContentsChanged(this, index, index);
return returnValue;
diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java
index 882c995..ce69365 100644
--- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java
+++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021 Adrien Hopkins
+ * Copyright (C) 2021, 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -18,32 +18,32 @@ package sevenUnitsGUI;
/**
* A View that can convert unit expressions
- *
+ *
* @author Adrien Hopkins
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
public interface ExpressionConversionView extends View {
/**
* @return unit expression to convert <em>from</em>
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
String getFromExpression();
/**
* @return unit expression to convert <em>to</em>
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
String getToExpression();
/**
* Shows the output of an expression conversion to the user.
- *
+ *
* @param uc unit conversion to show
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
void showExpressionConversionOutput(UnitConversionRecord uc);
}
diff --git a/src/main/java/sevenUnitsGUI/FilterComparator.java b/src/main/java/sevenUnitsGUI/FilterComparator.java
index 484a98f..ff942fb 100644
--- a/src/main/java/sevenUnitsGUI/FilterComparator.java
+++ b/src/main/java/sevenUnitsGUI/FilterComparator.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * Copyright (C) 2018, 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -21,9 +21,9 @@ import java.util.Objects;
/**
* A comparator that compares strings using a filter.
- *
+ *
* @param <T> type of element being compared
- *
+ *
* @author Adrien Hopkins
* @since 2019-01-15
* @since v0.1.0
@@ -31,21 +31,21 @@ import java.util.Objects;
final class FilterComparator<T> implements Comparator<T> {
/**
* The filter that the comparator is filtered by.
- *
+ *
* @since 2019-01-15
* @since v0.1.0
*/
private final String filter;
/**
* The comparator to use if the arguments are otherwise equal.
- *
+ *
* @since 2019-01-15
* @since v0.1.0
*/
private final Comparator<T> comparator;
/**
* Whether or not the comparison is case-sensitive.
- *
+ *
* @since 2019-04-14
* @since v0.2.0
*/
@@ -53,7 +53,7 @@ final class FilterComparator<T> implements Comparator<T> {
/**
* Creates the {@code FilterComparator}.
- *
+ *
* @param filter
* @since 2019-01-15
* @since v0.1.0
@@ -64,7 +64,7 @@ final class FilterComparator<T> implements Comparator<T> {
/**
* Creates the {@code FilterComparator}.
- *
+ *
* @param filter string to filter by
* @param comparator comparator to fall back to if all else fails, null is
* compareTo.
@@ -79,7 +79,7 @@ final class FilterComparator<T> implements Comparator<T> {
/**
* Creates the {@code FilterComparator}.
- *
+ *
* @param filter string to filter by
* @param comparator comparator to fall back to if all else fails, null is
* compareTo.
@@ -118,19 +118,18 @@ final class FilterComparator<T> implements Comparator<T> {
// elements that start with the filter always go first
if (str0.startsWith(this.filter) && !str1.startsWith(this.filter))
return -1;
- else if (!str0.startsWith(this.filter) && str1.startsWith(this.filter))
+ if (!str0.startsWith(this.filter) && str1.startsWith(this.filter))
return 1;
// elements that contain the filter but don't start with them go next
if (str0.contains(this.filter) && !str1.contains(this.filter))
return -1;
- else if (!str0.contains(this.filter) && !str1.contains(this.filter))
+ if (!str0.contains(this.filter) && !str1.contains(this.filter))
return 1;
// other elements go last
if (this.comparator == null)
return str0.compareTo(str1);
- else
- return this.comparator.compare(arg0, arg1);
+ return this.comparator.compare(arg0, arg1);
}
}
diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java
index fdbaee7..a9fede3 100644
--- a/src/main/java/sevenUnitsGUI/GridBagBuilder.java
+++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018 Adrien Hopkins
+ * Copyright (C) 2018, 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -21,7 +21,7 @@ import java.awt.Insets;
/**
* A builder for Java's {@link java.awt.GridBagConstraints} class.
- *
+ *
* @author Adrien Hopkins
* @since 2018-11-30
* @since v0.1.0
@@ -40,7 +40,7 @@ final class GridBagBuilder {
* <p>
* The default value is <code>RELATIVE</code>. <code>gridx</code> should be a
* non-negative value.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#gridy
@@ -58,7 +58,7 @@ final class GridBagBuilder {
* <p>
* The default value is <code>RELATIVE</code>. <code>gridy</code> should be a
* non-negative value.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#gridx
@@ -76,7 +76,7 @@ final class GridBagBuilder {
* from <code>gridx</code> to the next to the last one in its row.
* <p>
* <code>gridwidth</code> should be non-negative and the default value is 1.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#gridheight
@@ -96,7 +96,7 @@ final class GridBagBuilder {
* <p>
* <code>gridheight</code> should be a non-negative value and the default
* value is 1.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#gridwidth
@@ -119,7 +119,7 @@ final class GridBagBuilder {
* <p>
* The default value of this field is <code>0</code>. <code>weightx</code>
* should be a non-negative value.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#weighty
@@ -142,7 +142,7 @@ final class GridBagBuilder {
* <p>
* The default value of this field is <code>0</code>. <code>weighty</code>
* should be a non-negative value.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#weightx
@@ -173,7 +173,7 @@ final class GridBagBuilder {
* <code>BELOW_BASELINE</code>, <code>BELOW_BASELINE_LEADING</code>, and
* <code>BELOW_BASELINE_TRAILING</code>. The default value is
* <code>CENTER</code>.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.ComponentOrientation
@@ -199,7 +199,7 @@ final class GridBagBuilder {
* </ul>
* <p>
* The default value is <code>NONE</code>.
- *
+ *
* @serial
* @see #clone()
*/
@@ -212,7 +212,7 @@ final class GridBagBuilder {
* amount of space between the component and the edges of its display area.
* <p>
* The default value is <code>new Insets(0, 0, 0, 0)</code>.
- *
+ *
* @serial
* @see #clone()
*/
@@ -226,7 +226,7 @@ final class GridBagBuilder {
* is at least its minimum width plus <code>ipadx</code> pixels.
* <p>
* The default value is <code>0</code>.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#ipady
@@ -241,7 +241,7 @@ final class GridBagBuilder {
* least its minimum height plus <code>ipady</code> pixels.
* <p>
* The default value is 0.
- *
+ *
* @serial
* @see #clone()
* @see java.awt.GridBagConstraints#ipadx
@@ -292,7 +292,6 @@ final class GridBagBuilder {
final int gridheight, final double weightx, final double weighty,
final int anchor, final int fill, final Insets insets, final int ipadx,
final int ipady) {
- super();
this.gridx = gridx;
this.gridy = gridy;
this.gridwidth = gridwidth;
@@ -418,6 +417,7 @@ final class GridBagBuilder {
/**
* @param anchor anchor to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
@@ -428,6 +428,7 @@ final class GridBagBuilder {
/**
* @param fill fill to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
@@ -438,6 +439,7 @@ final class GridBagBuilder {
/**
* @param insets insets to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
@@ -448,6 +450,7 @@ final class GridBagBuilder {
/**
* @param ipadx ipadx to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
@@ -458,6 +461,7 @@ final class GridBagBuilder {
/**
* @param ipady ipady to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
@@ -468,6 +472,7 @@ final class GridBagBuilder {
/**
* @param weightx weightx to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
@@ -478,6 +483,7 @@ final class GridBagBuilder {
/**
* @param weighty weighty to set
+ * @return this, so that you can chain methods
* @since 2018-11-30
* @since v0.1.0
*/
diff --git a/src/main/java/sevenUnitsGUI/Main.java b/src/main/java/sevenUnitsGUI/Main.java
index ff61b3b..3ff2fd9 100644
--- a/src/main/java/sevenUnitsGUI/Main.java
+++ b/src/main/java/sevenUnitsGUI/Main.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Adrien Hopkins
+ * Copyright (C) 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -19,8 +19,8 @@ package sevenUnitsGUI;
/**
* The main code for the 7Units GUI
*
- * @since v0.4.0
* @since 2022-04-19
+ * @since v0.4.0
*/
public final class Main {
@@ -28,8 +28,8 @@ public final class Main {
* The main method that starts 7Units
*
* @param args commandline arguments
- * @since v0.4.0
* @since 2022-04-19
+ * @since v0.4.0
*/
public static void main(String[] args) {
View.createTabbedView();
diff --git a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java
index 69f09e6..73d12bc 100644
--- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java
+++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Adrien Hopkins
+ * Copyright (C) 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -34,8 +34,8 @@ import sevenUnits.unit.UnitPrefix;
* A search rule that applies a certain set of prefixes to a unit. It always
* includes the original unit in the output map.
*
- * @since v0.4.0
* @since 2022-07-06
+ * @since v0.4.0
*/
public final class PrefixSearchRule implements
Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> {
@@ -70,10 +70,10 @@ public final class PrefixSearchRule implements
*
* @param prefixes prefixes to apply
* @return prefix rule
- * @since v0.4.0
* @since 2022-07-06
+ * @since v0.4.0
*/
- public static final PrefixSearchRule getCoherentOnlyRule(
+ public static PrefixSearchRule getCoherentOnlyRule(
Set<UnitPrefix> prefixes) {
return new PrefixSearchRule(prefixes,
u -> u.isCoherent() && !u.getName().equals("kilogram"));
@@ -84,30 +84,25 @@ public final class PrefixSearchRule implements
*
* @param prefixes prefixes to apply
* @return prefix rule
- * @since v0.4.0
* @since 2022-07-06
+ * @since v0.4.0
*/
- public static final PrefixSearchRule getUniversalRule(
- Set<UnitPrefix> prefixes) {
+ public static PrefixSearchRule getUniversalRule(Set<UnitPrefix> prefixes) {
return new PrefixSearchRule(prefixes, u -> true);
}
- /**
- * The set of prefixes that will be applied to the unit.
- */
+ /** The set of prefixes that will be applied to the unit. */
private final Set<UnitPrefix> prefixes;
- /**
- * Determines which units are given prefixes.
- */
+ /** Determines which units are given prefixes. */
private final Predicate<LinearUnit> prefixableUnitRule;
/**
* @param prefixes prefixes to add to units
* @param prefixableUnitRule function that determines which units get
* prefixes
- * @since v0.4.0
* @since 2022-07-06
+ * @since v0.4.0
*/
public PrefixSearchRule(Set<UnitPrefix> prefixes,
Predicate<LinearUnit> prefixableUnitRule) {
@@ -118,8 +113,8 @@ public final class PrefixSearchRule implements
@Override
public Map<String, LinearUnit> apply(Entry<String, LinearUnit> t) {
final Map<String, LinearUnit> outputUnits = new HashMap<>();
- final String originalName = t.getKey();
- final LinearUnit originalUnit = t.getValue();
+ final var originalName = t.getKey();
+ final var originalUnit = t.getValue();
outputUnits.put(originalName, originalUnit);
if (this.prefixableUnitRule.test(originalUnit)) {
for (final UnitPrefix prefix : this.prefixes) {
@@ -136,15 +131,15 @@ public final class PrefixSearchRule implements
return true;
if (!(obj instanceof PrefixSearchRule))
return false;
- final PrefixSearchRule other = (PrefixSearchRule) obj;
+ final var other = (PrefixSearchRule) obj;
return Objects.equals(this.prefixableUnitRule, other.prefixableUnitRule)
&& Objects.equals(this.prefixes, other.prefixes);
}
/**
* @return rule that determines which units get prefixes
- * @since v0.4.0
* @since 2022-07-09
+ * @since v0.4.0
*/
public Predicate<LinearUnit> getPrefixableUnitRule() {
return this.prefixableUnitRule;
@@ -152,8 +147,8 @@ public final class PrefixSearchRule implements
/**
* @return the prefixes that are applied by this rule
- * @since v0.4.0
* @since 2022-07-06
+ * @since v0.4.0
*/
public Set<UnitPrefix> getPrefixes() {
return this.prefixes;
diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java
index eba8438..d258e1f 100644
--- a/src/main/java/sevenUnitsGUI/Presenter.java
+++ b/src/main/java/sevenUnitsGUI/Presenter.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021-2022 Adrien Hopkins
+ * Copyright (C) 2021-2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -16,12 +16,12 @@
*/
package sevenUnitsGUI;
-import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -40,12 +40,14 @@ import sevenUnits.unit.BaseUnit;
import sevenUnits.unit.BritishImperial;
import sevenUnits.unit.LinearUnit;
import sevenUnits.unit.LinearUnitValue;
+import sevenUnits.unit.LoadingException;
import sevenUnits.unit.Metric;
import sevenUnits.unit.Unit;
import sevenUnits.unit.UnitDatabase;
import sevenUnits.unit.UnitPrefix;
import sevenUnits.unit.UnitType;
import sevenUnits.unit.UnitValue;
+import sevenUnits.utils.NameSymbol;
import sevenUnits.utils.Nameable;
import sevenUnits.utils.ObjectProduct;
import sevenUnits.utils.UncertainDouble;
@@ -55,9 +57,10 @@ import sevenUnitsGUI.StandardDisplayRules.UncertaintyBased;
/**
* An object that handles interactions between the view and the backend code
- *
+ *
* @author Adrien Hopkins
* @since 2021-12-15
+ * @since v0.4.0
*/
public final class Presenter {
/**
@@ -72,33 +75,25 @@ 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<Unit> IS_FULL_BASE = unit -> unit instanceof LinearUnit
+ && ((LinearUnit) unit).isBase();
+ /**
+ * The default locale, used in two situations:
+ * <ul>
+ * <li>If no text is available in your locale, uses text from this locale.
+ * <li>Users are initialized with this locale.
+ * </ul>
+ */
+ static final String DEFAULT_LOCALE = "en";
- 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);
- }
+ private static final List<String> 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
@@ -125,14 +120,32 @@ public final class Presenter {
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());
+ if (numberDisplayRule instanceof FixedPrecision)
+ return String.format("FIXED_PRECISION %d",
+ ((FixedPrecision) numberDisplayRule).significantFigures());
+ if (numberDisplayRule instanceof UncertaintyBased)
+ return "UNCERTAINTY_BASED";
+ return numberDisplayRule.toString();
+ }
+
/**
- * @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
+ * @since v1.0.0
*/
- 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 int findLineSplit(String toWrap, int maxLineLength) {
+ for (var i = maxLineLength - 1; i >= 0; i--) {
+ if (Character.isWhitespace(toWrap.charAt(i)))
+ return i;
+ }
+ return -1;
}
/**
@@ -142,12 +155,13 @@ public final class Presenter {
* @param filename filename to get resource from
* @return contents of file
* @since 2021-03-27
+ * @since v0.3.0
*/
- private static final List<String> getLinesFromResource(String filename) {
+ private static List<String> getLinesFromResource(String filename) {
final List<String> 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,16 +179,59 @@ public final class Presenter {
* @param filepath file to use as resource
* @return obtained Path
* @since 2021-03-27
+ * @since v0.3.0
*/
- 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
+ * @since v1.0.0
+ */
+ private static String linearUnitValueIntToString(LinearUnitValue uv) {
+ return Long.toString(Math.round(uv.getValueExact())) + " " + uv.getUnit();
+ }
+
+ private static Map.Entry<String, String> parseSettingLine(String line) {
+ final var equalsIndex = line.indexOf('=');
+ if (equalsIndex == -1)
+ throw new IllegalStateException(
+ "Settings file is malformed at line: " + line);
+
+ 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.Entry<String, LinearUnit>, Map<String, LinearUnit>> searchRule) {
+ if (PrefixSearchRule.NO_PREFIXES.equals(searchRule))
+ return "NO_PREFIXES";
+ if (PrefixSearchRule.COMMON_PREFIXES.equals(searchRule))
+ return "COMMON_PREFIXES";
+ if (PrefixSearchRule.ALL_METRIC_PREFIXES.equals(searchRule))
+ return "ALL_METRIC_PREFIXES";
+ return searchRule.toString();
+ }
+
+ /**
* @return true iff a and b have any elements in common
* @since 2022-04-19
+ * @since v0.4.0
*/
- 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;
@@ -182,20 +239,55 @@ public final class Presenter {
return false;
}
+ private static Path userConfigDir() {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ final var envFolder = System.getenv("LOCALAPPDATA");
+ if (envFolder == null || "".equals(envFolder))
+ return Path.of(System.getenv("USERPROFILE"), "AppData", "Local");
+ return Path.of(envFolder);
+ }
+ final var envFolder = System.getenv("XDG_CONFIG_HOME");
+ if (envFolder == null || "".equals(envFolder))
+ return Path.of(System.getenv("HOME"), ".config");
+ return Path.of(envFolder);
+ }
+
/**
* @return {@code line} with any comments removed.
* @since 2021-03-13
+ * @since v0.3.0
*/
- 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);
}
- // ====== SETTINGS ======
-
/**
- * The view that this presenter communicates with
+ * Wraps a string, ensuring no line is longer than {@code maxLineLength}.
+ *
+ * @since 2024-08-22
+ * @since v1.0.0
*/
+ private static String wrapString(String toWrap, int maxLineLength) {
+ final var wrapped = new StringBuilder(toWrap.length());
+ var remaining = toWrap;
+ while (remaining.length() > maxLineLength) {
+ final var 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 */
private final View view;
/**
@@ -238,6 +330,12 @@ public final class Presenter {
*/
private final Set<String> metricExceptions;
+ /** maps locale names (e.g. 'en') to key-text maps */
+ final Map<String, Map<String, String>> 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
@@ -252,65 +350,61 @@ public final class Presenter {
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<Path> customUnitFiles = new HashSet<>();
+ /** Custom dimension datafiles that will be loaded by {@link #reloadData} */
+ private final Set<Path> customDimensionFiles = new HashSet<>();
+ /** Custom exception datafiles that will be loaded by {@link #reloadData} */
+ private final Set<Path> customExceptionFiles = new HashSet<>();
+
+ /**
* Creates a Presenter
- *
+ *
* @param view the view that this presenter communicates with
* @since 2021-12-15
+ * @since v0.4.0
*/
public Presenter(View view) {
this.view = view;
this.database = new UnitDatabase();
- addDefaults(this.database);
+ this.metricExceptions = new HashSet<>();
- // 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)) {
- 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)) {
- while (scanner.hasNextLine()) {
- final String line = Presenter
- .withoutComments(scanner.nextLine());
- if (!line.isBlank()) {
- this.metricExceptions.add(line);
- }
- }
- }
- } catch (final IOException e) {
- 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);
}
- // a Predicate that returns true iff the argument is a full base unit
- final Predicate<Unit> isFullBase = unit -> unit instanceof LinearUnit
- && ((LinearUnit) unit).isBase();
+ this.reloadData();
// print out unit counts
- System.out.printf(
- "Successfully loaded %d units with %d unit names (%d base units).%n",
- this.database.unitMapPrefixless(false).size(),
- this.database.unitMapPrefixless(true).size(),
- this.database.unitMapPrefixless(false).values().stream()
- .filter(isFullBase).count());
+ System.out.println(this.loadStatMsg());
+ }
+
+ private void addLocaleFile(Map<String, Map<String, String>> locales,
+ Path file) throws IOException {
+ final Map<String, String> locale = new HashMap<>();
+ final var fileName = file.getName(file.getNameCount() - 1).toString();
+ final var localeName = fileName.substring(0, fileName.length() - 4);
+ try (var lines = Files.lines(file)) {
+ lines.forEach(line -> this.addLocaleLine(locale, line));
+ }
+ locales.put(localeName, locale);
+ }
+
+ private void addLocaleLine(Map<String, String> locale, String line) {
+ final var parts = line.split("=", 2);
+ if (parts.length < 2)
+ return;
+
+ locale.put(parts[0], parts[1]);
}
/**
@@ -319,201 +413,387 @@ public final class Presenter {
* @param e entry
* @return stream of entries, ready for flat-mapping
* @since 2022-07-06
+ * @since v0.4.0
*/
- private final Stream<Map.Entry<String, Unit>> applySearchRule(
+ private Stream<Map.Entry<String, Unit>> applySearchRule(
Map.Entry<String, Unit> 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<String, LinearUnit> 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
* {@link ExpressionConversionView})
* @since 2021-12-15
+ * @since v0.4.0
*/
public void convertExpressions() {
- if (this.view instanceof ExpressionConversionView) {
- final ExpressionConversionView xcview = (ExpressionConversionView) this.view;
+ 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 String fromExpression = xcview.getFromExpression();
- final String toExpression = xcview.getToExpression();
+ 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;
- }
+ // 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;
+ }
- // 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 (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
+ * @since v1.0.0
+ */
+ 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)) {
+ 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.
+ *
+ * @since 2024-08-15
+ * @since v1.0.0
+ */
+ 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 (final String toExpression : toExpressions) {
try {
- to = this.database.getUnitFromExpression(toExpression);
+ 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;
+ return Optional.empty();
}
+ }
+
+ 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();
+ }
- // convert and show output
- if (from.getUnit().canConvertTo(to)) {
- final UncertainDouble uncertainValue;
+ final var toExpression = this.linearUnitValueSumToString(toValues);
+ return Optional.of(
+ UnitConversionRecord.valueOf(fromExpression, toExpression, "", ""));
+ }
- // 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);
- }
+ /**
+ * 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.
+ *
+ * @since 2024-08-15
+ * @since v1.0.0
+ */
+ private Optional<UnitConversionRecord> convertExpressionToNamedMultiUnit(
+ String fromExpression, String toName) {
+ 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 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 + "\".");
- }
+ final var toUnits = this.database.getUnitSet(toName);
+ 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();
+ }
- } else
- throw new UnsupportedOperationException(
- "This function can only be called when the view is an ExpressionConversionView");
+ 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
* {@link UnitConversionView})
* @since 2021-12-15
+ * @since v0.4.0
*/
public void convertUnits() {
- if (this.view instanceof UnitConversionView) {
- final UnitConversionView ucview = (UnitConversionView) this.view;
+ 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()) {
+ this.view.showErrorMessage("Unit Selection Error",
+ "Please specify a From unit");
+ return;
+ }
+ fromUnitString = fromUnitOptional.orElseThrow();
+ if (!toUnitOptional.isPresent()) {
+ this.view.showErrorMessage("Unit Selection Error",
+ "Please specify a To unit");
+ return;
+ }
+ toUnitString = toUnitOptional.orElseThrow();
- final Optional<String> fromUnitOptional = ucview.getFromSelection();
- final Optional<String> toUnitOptional = ucview.getToSelection();
- final String inputValueString = ucview.getInputValue();
+ // convert strings to data, checking if anything is invalid
+ final Unit fromUnit;
+ final UncertainDouble uncertainValue;
- // 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, toUnit;
- final UncertainDouble uncertainValue;
-
- if (this.database.containsUnitName(fromUnitString)) {
- fromUnit = this.database.getUnit(fromUnitString);
- } else
- throw this.viewError("Nonexistent From unit: %s", fromUnitString);
- if (this.database.containsUnitName(toUnitString)) {
- toUnit = this.database.getUnit(toUnitString);
- } else
- throw this.viewError("Nonexistent To unit: %s", toUnitString);
- try {
- uncertainValue = UncertainDouble
- .fromRoundedString(inputValueString);
- } catch (final NumberFormatException e) {
- this.view.showErrorMessage("Value Error",
- "Invalid value " + inputValueString);
- return;
- }
+ if (!this.database.containsUnitName(fromUnitString))
+ throw this.viewError("Nonexistent From unit: %s", fromUnitString);
+ fromUnit = this.database.getUnit(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<LinearUnit> toMulti,
+ UncertainDouble uncertainValue) {
+ for (final LinearUnit toUnit : toMulti) {
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,
- 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);
+ final LinearUnitValue initValue;
+ if (fromUnit instanceof LinearUnit) {
+ final var fromLinear = (LinearUnit) fromUnit;
+ initValue = LinearUnitValue.of(fromLinear, uncertainValue);
+ } else {
+ initValue = UnitValue.of(fromUnit, uncertainValue.value())
+ .convertToBase(NameSymbol.EMPTY);
+ }
- outputValueString = this.numberDisplayRule
- .apply(UncertainDouble.of(converted.getValue(), 0));
- }
+ final var converted = initValue.convertToMultiple(toMulti);
+ final var toExpression = this.linearUnitValueSumToString(converted);
+ return UnitConversionRecord.valueOf(fromUnitString, toExpression,
+ inputValueString, "");
- ucview.showUnitConversionOutput(
- UnitConversionRecord.valueOf(fromUnitString, toUnitString,
- inputValueString, outputValueString));
- } else
- throw new UnsupportedOperationException(
- "This function can only be called when the view is a UnitConversionView.");
+ }
+
+ 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 var fromLinear = (LinearUnit) fromUnit;
+ final var toLinear = (LinearUnit) toUnit;
+ final var initialValue = LinearUnitValue.of(fromLinear,
+ uncertainValue);
+ final var converted = initialValue.convertTo(toLinear);
+
+ outputValueString = this.numberDisplayRule.apply(converted.getValue());
+ } else {
+ final var initialValue = UnitValue.of(fromUnit,
+ uncertainValue.value());
+ 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
+ * @since v0.4.0
*/
public boolean duplicatesShown() {
return this.showDuplicates;
}
+ private String formatAboutText(Stream<String> rawLines) {
+ return rawLines.map(Presenter::withoutComments)
+ .collect(Collectors.joining("\n"))
+ .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString())
+ .replaceAll("\\[LOADSTATS\\]", wrapString(this.loadStatMsg(), 72));
+ }
+
+ /**
+ * @return text in About file
+ * @since 2022-02-19
+ * @since v0.4.0
+ */
+ public String getAboutText() {
+ final var customFilepath = Presenter
+ .pathFromConfig("about/" + this.userLocale + ".txt");
+ if (Files.exists(customFilepath)) {
+ try (var lines = Files.lines(customFilepath)) {
+ return this.formatAboutText(lines);
+ } catch (final IOException e) {
+ final var filename = String.format("/about/%s.txt",
+ this.userLocale);
+ return this.formatAboutText(
+ Presenter.getLinesFromResource(filename).stream());
+ }
+ }
+ if (LOCAL_LOCALES.contains(this.userLocale)) {
+ final var filename = String.format("/about/%s.txt", this.userLocale);
+ return this.formatAboutText(
+ Presenter.getLinesFromResource(filename).stream());
+ }
+ final var filename = String.format("/about/%s.txt", DEFAULT_LOCALE);
+ return this
+ .formatAboutText(Presenter.getLinesFromResource(filename).stream());
+
+ }
+
+ /**
+ * @return set of all locales available to select
+ * @since 2025-02-21
+ * @since v1.0.0
+ */
+ public Set<String> getAvailableLocales() {
+ return this.locales.keySet();
+ }
+
/**
* Gets a name for this dimension using the database
*
* @param dimension dimension to name
* @return name of dimension
* @since 2022-04-16
+ * @since v0.4.0
*/
- final String getDimensionName(ObjectProduct<BaseDimension> dimension) {
+ String getDimensionName(ObjectProduct<BaseDimension> 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()
@@ -522,9 +802,24 @@ public final class Presenter {
}
/**
+ * 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 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
* @since 2022-04-10
+ * @since v0.4.0
*/
public Function<UncertainDouble, String> getNumberDisplayRule() {
return this.numberDisplayRule;
@@ -534,6 +829,7 @@ public final class Presenter {
* @return the rule that is used by this presenter to convert strings into
* numbers
* @since 2022-04-10
+ * @since v0.4.0
*/
@SuppressWarnings("unused") // not implemented yet
private Function<String, UncertainDouble> getNumberParsingRule() {
@@ -543,6 +839,7 @@ public final class Presenter {
/**
* @return the rule that determines whether a set of prefixes is valid
* @since 2022-04-19
+ * @since v0.4.0
*/
public Predicate<List<UnitPrefix>> getPrefixRepetitionRule() {
return this.prefixRepetitionRule;
@@ -551,6 +848,7 @@ public final class Presenter {
/**
* @return the rule that determines which units are prefixed
* @since 2022-07-08
+ * @since v0.4.0
*/
public Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> getSearchRule() {
return this.searchRule;
@@ -559,6 +857,7 @@ public final class Presenter {
/**
* @return a search rule that shows all single prefixes
* @since 2022-07-08
+ * @since v0.4.0
*/
public Function<Map.Entry<String, LinearUnit>, Map<String, LinearUnit>> getUniversalSearchRule() {
return PrefixSearchRule.getCoherentOnlyRule(
@@ -566,19 +865,49 @@ public final class Presenter {
}
/**
+ * @return user's selected locale
+ * @since 2025-02-21
+ * @since v1.0.0
+ */
+ public String getUserLocale() {
+ return this.userLocale;
+ }
+
+ /**
* @return the view associated with this presenter
* @since 2022-04-19
+ * @since v0.4.0
*/
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.
+ *
+ * @since 2024-08-22
+ * @since v1.0.0
+ */
+ private void handleLoadErrors(List<LoadingException> errors) {
+ if (!errors.isEmpty()) {
+ 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")));
+ System.err.print(errorMessage);
+ this.view.showErrorMessage(errors.size() + "Loading Error(s)",
+ errorMessage);
+ }
+ }
+
+ /**
* @return whether or not the provided unit is semi-metric (i.e. an
* exception)
* @since 2022-04-16
+ * @since v0.4.0
*/
- 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();
@@ -590,27 +919,127 @@ public final class Presenter {
}
/**
+ * 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
+ * integers, since they are always integers. The last one follows the usual
+ * number display rule.
+ *
+ * @since 2024-08-16
+ * @since v1.0.0
+ */
+ private String linearUnitValueSumToString(List<LinearUnitValue> values) {
+ final var integerPart = values.subList(0, values.size() - 1).stream()
+ .map(Presenter::linearUnitValueIntToString)
+ .collect(Collectors.joining(" + "));
+ final var last = values.get(values.size() - 1);
+ return integerPart + " + " + this.numberDisplayRule.apply(last.getValue())
+ + " " + last.getUnit();
+ }
+
+ /** 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));
+ } catch (final IOException e) {
+ throw new AssertionError("Loading of unitsfile.txt failed.", e);
+ }
+
+ // load dimensions
+ 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 {
+ try (var exceptions = inputStream(DEFAULT_EXCEPTIONS_FILEPATH);
+ var scanner = new Scanner(exceptions)) {
+ while (scanner.hasNextLine()) {
+ final var line = Presenter.withoutComments(scanner.nextLine());
+ if (!line.isBlank()) {
+ this.metricExceptions.add(line);
+ }
+ }
+ }
+ } catch (final IOException e) {
+ throw new AssertionError("Loading of metric_exceptions.txt failed.",
+ e);
+ }
+ }
+
+ private void loadExceptionFile(Path exceptionFile) {
+ try (var 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 all available locales, including custom ones, into a map.
+ *
+ * @return map containing locales
+ */
+ private Map<String, Map<String, String>> loadLocales() {
+ final Map<String, Map<String, String>> locales = new HashMap<>();
+ for (final String localeName : LOCAL_LOCALES) {
+ final Map<String, String> locale = new HashMap<>();
+ final var filename = "/locales/" + localeName + ".txt";
+ getLinesFromResource(filename)
+ .forEach(line -> this.addLocaleLine(locale, line));
+ locales.put(localeName, locale);
+ }
+
+ if (Files.exists(USER_LOCALES_DIR)) {
+ try (var files = Files.list(USER_LOCALES_DIR)) {
+ files.forEach(localeFile -> {
+ try {
+ this.addLocaleFile(locales, localeFile);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ });
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return locales;
+ }
+
+ /**
* Loads settings from the user's settings file and applies them to the
* presenter.
*
* @param settingsFile file settings should be loaded from
* @since 2021-12-15
+ * @since v0.4.0
*/
void loadSettings(Path settingsFile) {
- for (Map.Entry<String, String> setting : settingsFromFile(settingsFile)) {
- final String value = setting.getValue();
+ this.customDimensionFiles.clear();
+ this.customExceptionFiles.clear();
+ this.customUnitFiles.clear();
+
+ for (final Map.Entry<String, String> setting : this
+ .settingsFromFile(settingsFile)) {
+ final var value = setting.getValue();
switch (setting.getKey()) {
// set manually to avoid the unnecessary saving of the non-manual
// methods
case "custom_dimension_file":
- 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.database.loadUnitsFile(pathFromConfig(value));
+ this.customUnitFiles.add(pathFromConfig(value));
break;
case "number_display_rule":
this.setDisplayRuleFromString(value);
@@ -621,14 +1050,28 @@ 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 "use_default_datafiles":
+ this.useDefaultDatafiles = Boolean.parseBoolean(value);
+ break;
+ case "locale":
+ if (this.locales.containsKey(value)) {
+ this.userLocale = value;
+ } else {
+ System.err.printf("Warning: unrecognized locale \"%s\".%n",
+ value);
+ this.view.showErrorMessage("Unrecognized Locale",
+ "Could not find locale \"" + value
+ + "\", resetting to default.");
+ }
+ break;
default:
System.err.printf("Warning: unrecognized setting \"%s\".%n",
setting.getKey());
@@ -641,86 +1084,38 @@ public final class Presenter {
}
}
- 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 a message showing how much stuff has been loaded
+ * @since 2024-08-22
+ * @since v1.0.0
+ */
+ private String loadStatMsg() {
+ 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()));
}
/**
* @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
+ * @since v0.4.0
*/
public boolean oneWayConversionEnabled() {
return this.oneWayConversionEnabled;
@@ -730,22 +1125,23 @@ public final class Presenter {
* 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
+ * @since v0.4.0
*/
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();
+ this.view.updateText();
}
void prefixSelected() {
- final Optional<String> selectedPrefixName = this.view
- .getViewedPrefixName();
+ final var selectedPrefixName = this.view.getViewedPrefixName();
final Optional<UnitPrefix> selectedPrefix = selectedPrefixName
.map(name -> this.database.containsPrefixName(name)
? this.database.getPrefix(name)
@@ -755,19 +1151,37 @@ public final class Presenter {
String.valueOf(prefix.getMultiplier())));
}
+ /** 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);
+ }
+
/**
* 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
+ * @since v0.4.0
*/
public boolean saveSettings() {
- final Path configDir = CONFIG_FILE.getParent();
+ final var configDir = CONFIG_FILE.getParent();
if (!Files.exists(configDir)) {
try {
Files.createDirectories(configDir);
- } catch (IOException e) {
+ } catch (final IOException e) {
return false;
}
}
@@ -775,64 +1189,32 @@ public final class Presenter {
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 var tokens = ruleString.split(" ");
+ switch (tokens[0]) {
+ case "FIXED_DECIMALS":
+ final var decimals = Integer.parseInt(tokens[1]);
+ this.numberDisplayRule = StandardDisplayRules.fixedDecimals(decimals);
+ break;
+ case "FIXED_PRECISION":
+ final var 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
* @since 2022-04-10
+ * @since v0.4.0
*/
public void setNumberDisplayRule(
Function<UncertainDouble, String> numberDisplayRule) {
@@ -843,6 +1225,7 @@ public final class Presenter {
* @param numberParsingRule the new rule that will be used by this presenter
* to convert strings into numbers
* @since 2022-04-10
+ * @since v0.4.0
*/
@SuppressWarnings("unused") // not implemented yet
private void setNumberParsingRule(
@@ -854,7 +1237,8 @@ public final class Presenter {
* @param oneWayConversionEnabled whether not one-way conversion should be
* enabled
* @since 2022-03-30
- * @see {@link #isOneWayConversionEnabled}
+ * @since v0.4.0
+ * @see #oneWayConversionEnabled
*/
public void setOneWayConversionEnabled(boolean oneWayConversionEnabled) {
this.oneWayConversionEnabled = oneWayConversionEnabled;
@@ -865,6 +1249,7 @@ public final class Presenter {
* @param prefixRepetitionRule the rule that determines whether a set of
* prefixes is valid
* @since 2022-04-19
+ * @since v0.4.0
*/
public void setPrefixRepetitionRule(
Predicate<List<UnitPrefix>> prefixRepetitionRule) {
@@ -878,30 +1263,86 @@ public final class Presenter {
* unit (including the unit itself) that should be
* searchable.
* @since 2022-07-08
+ * @since v0.4.0
*/
public void setSearchRule(
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
+ * @since v0.4.0
*/
public void setShowDuplicates(boolean showDuplicateUnits) {
this.showDuplicates = showDuplicateUnits;
this.updateView();
}
+ private List<Map.Entry<String, String>> settingsFromFile(Path settingsFile) {
+ try (var 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;
+ }
+ }
+
+ /**
+ * Sets whether or not the default datafiles will be loaded. This method
+ * automatically updates the view's units.
+ *
+ * @param useDefaultDatafiles whether or not default datafiles should be
+ * loaded
+ */
+ public void setUseDefaultDatafiles(boolean useDefaultDatafiles) {
+ this.useDefaultDatafiles = useDefaultDatafiles;
+ this.reloadData();
+ 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();
+ }
+
/**
* Shows a unit in the unit viewer
*
* @param u unit to show
* @since 2022-04-16
+ * @since v0.4.0
*/
- private final void showUnit(Unit u) {
+ 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());
@@ -912,12 +1353,13 @@ public final class Presenter {
/**
* Runs whenever a unit name is selected in the unit viewer. Gets the
* description of a unit and displays it.
- *
+ *
* @since 2022-04-10
+ * @since v0.4.0
*/
void unitNameSelected() {
// get selected unit, if it's there and valid
- final Optional<String> selectedUnitName = this.view.getViewedUnitName();
+ final var selectedUnitName = this.view.getViewedUnitName();
final Optional<Unit> selectedUnit = selectedUnitName
.map(unitName -> this.database.containsUnitName(unitName)
? this.database.getUnit(unitName)
@@ -927,12 +1369,13 @@ public final class Presenter {
/**
* Updates the view's From and To units, if it has some
- *
+ *
* @since 2021-12-15
+ * @since v0.4.0
*/
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
@@ -946,6 +1389,7 @@ public final class Presenter {
.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()) {
@@ -955,6 +1399,8 @@ public final class Presenter {
u -> viewDimension.equals(u.getValue().getDimension()));
toUnits = toUnits.filter(
u -> viewDimension.equals(u.getValue().getDimension()));
+ unitSets = unitSets.filter(us -> viewDimension
+ .equals(us.getValue().get(0).getDimension()));
}
// filter by unit type, if desired
@@ -963,25 +1409,70 @@ public final class Presenter {
this::isSemiMetric) != UnitType.METRIC);
toUnits = toUnits.filter(u -> UnitType.getType(u.getValue(),
this::isSemiMetric) != UnitType.NON_METRIC);
+ // unit sets are never considered metric
+ 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()));
- ucview.setToUnitNames(toUnits.flatMap(this::applySearchRule)
- .map(Map.Entry::getKey).collect(Collectors.toSet()));
+ final var toUnitNames = toUnits.flatMap(this::applySearchRule)
+ .map(Map.Entry::getKey);
+ final var unitSetNames = unitSets.map(Map.Entry::getKey);
+ final var toNames = Stream.concat(toUnitNames, unitSetNames)
+ .collect(Collectors.toSet());
+ ucview.setToUnitNames(toNames);
}
}
+ /** @return true iff the default datafiles are being used */
+ public boolean usingDefaultDatafiles() {
+ return this.useDefaultDatafiles;
+ }
+
/**
* @param message message to add
* @param args string formatting arguments for message
* @return AssertionError stating that an error has happened in the view's
* code
* @since 2022-04-09
+ * @since v0.4.0
*/
private AssertionError viewError(String message, Object... args) {
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
+ * @since v0.4.0
+ */
+ boolean writeSettings(Path settingsFile) {
+ try (var 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)));
+ writer.write(String.format("use_default_datafiles=%s\n",
+ this.useDefaultDatafiles));
+ writer.write(String.format("locale=%s\n", this.userLocale));
+ 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/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java
index 8fba459..96f71de 100644
--- a/src/main/java/sevenUnitsGUI/SearchBoxList.java
+++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2019 Adrien Hopkins
+ * Copyright (C) 2019, 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -49,7 +49,7 @@ final class SearchBoxList<E> extends JPanel {
/**
* The text to place in an empty search box.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
@@ -57,7 +57,7 @@ final class SearchBoxList<E> extends JPanel {
/**
* The color to use for an empty foreground.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
@@ -82,8 +82,9 @@ final class SearchBoxList<E> extends JPanel {
/**
* Creates an empty SearchBoxList
- *
+ *
* @since 2022-02-19
+ * @since v0.4.0
*/
public SearchBoxList() {
this(List.of(), null, false);
@@ -91,9 +92,10 @@ final class SearchBoxList<E> extends JPanel {
/**
* Creates the {@code SearchBoxList}.
- *
+ *
* @param itemsToFilter items to put in the list
* @since 2019-04-14
+ * @since v0.2.0
*/
public SearchBoxList(final Collection<E> itemsToFilter) {
this(itemsToFilter, null, false);
@@ -101,12 +103,12 @@ final class SearchBoxList<E> extends JPanel {
/**
* Creates the {@code SearchBoxList}.
- *
+ *
* @param itemsToFilter items to put in the list
* @param defaultOrdering default ordering of items after filtration
* (null=Comparable)
* @param caseSensitive whether or not the filtration is case-sensitive
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
@@ -147,7 +149,7 @@ final class SearchBoxList<E> extends JPanel {
/**
* Adds an additional filter for searching.
- *
+ *
* @param filter filter to add.
* @since 2019-04-13
* @since v0.2.0
@@ -158,7 +160,7 @@ final class SearchBoxList<E> extends JPanel {
/**
* Resets the search filter.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
@@ -170,6 +172,7 @@ final class SearchBoxList<E> extends JPanel {
* @return items available in search list, including items that are hidden by
* the search filter
* @since 2022-03-30
+ * @since v0.4.0
*/
public Collection<E> getItems() {
return Collections.unmodifiableCollection(this.itemsToFilter);
@@ -180,7 +183,7 @@ final class SearchBoxList<E> extends JPanel {
* @since 2019-04-14
* @since v0.2.0
*/
- public final JTextField getSearchBox() {
+ public JTextField getSearchBox() {
return this.searchBox;
}
@@ -194,9 +197,8 @@ final class SearchBoxList<E> extends JPanel {
private Predicate<E> getSearchFilter(final String searchText) {
if (this.caseSensitive)
return item -> item.toString().contains(searchText);
- else
- return item -> item.toString().toLowerCase()
- .contains(searchText.toLowerCase());
+ return item -> item.toString().toLowerCase()
+ .contains(searchText.toLowerCase());
}
/**
@@ -204,7 +206,7 @@ final class SearchBoxList<E> extends JPanel {
* @since 2019-04-14
* @since v0.2.0
*/
- public final JList<E> getSearchList() {
+ public JList<E> getSearchList() {
return this.searchItems;
}
@@ -228,16 +230,16 @@ final class SearchBoxList<E> extends JPanel {
/**
* Re-applies the filters.
- *
+ *
* @since 2019-04-13
* @since v0.2.0
*/
public void reapplyFilter() {
- final String searchText = this.searchBoxEmpty ? ""
+ final var searchText = this.searchBoxEmpty ? ""
: this.searchBox.getText();
- final FilterComparator<E> comparator = new FilterComparator<>(searchText,
+ final var comparator = new FilterComparator<>(searchText,
this.defaultOrdering, this.caseSensitive);
- final Predicate<E> searchFilter = this.getSearchFilter(searchText);
+ final var searchFilter = this.getSearchFilter(searchText);
this.listModel.clear();
this.itemsToFilter.forEach(item -> {
@@ -255,7 +257,7 @@ final class SearchBoxList<E> extends JPanel {
/**
* Runs whenever the search box gains focus.
- *
+ *
* @param e focus event
* @since 2019-04-13
* @since v0.2.0
@@ -270,7 +272,7 @@ final class SearchBoxList<E> extends JPanel {
/**
* Runs whenever the search box loses focus.
- *
+ *
* @param e focus event
* @since 2019-04-13
* @since v0.2.0
@@ -288,7 +290,7 @@ final class SearchBoxList<E> extends JPanel {
* <p>
* Reapplies the search filter, and custom filters.
* </p>
- *
+ *
* @since 2019-04-14
* @since v0.2.0
*/
@@ -296,11 +298,11 @@ final class SearchBoxList<E> extends JPanel {
if (this.searchBoxFocused) {
this.searchBoxEmpty = this.searchBox.getText().equals("");
}
- final String searchText = this.searchBoxEmpty ? ""
+ final var searchText = this.searchBoxEmpty ? ""
: this.searchBox.getText();
- final FilterComparator<E> comparator = new FilterComparator<>(searchText,
+ final var comparator = new FilterComparator<>(searchText,
this.defaultOrdering, this.caseSensitive);
- final Predicate<E> searchFilter = this.getSearchFilter(searchText);
+ final var searchFilter = this.getSearchFilter(searchText);
// initialize list with items that match the filter then sort
this.listModel.clear();
@@ -323,6 +325,7 @@ final class SearchBoxList<E> extends JPanel {
*
* @param newItems new items to put in list
* @since 2021-05-22
+ * @since v0.3.0
*/
public void setItems(Collection<? extends E> newItems) {
this.itemsToFilter.clear();
@@ -332,8 +335,9 @@ final class SearchBoxList<E> extends JPanel {
/**
* Manually updates the search box's item list.
- *
+ *
* @since 2020-08-27
+ * @since v0.3.0
*/
public void updateList() {
this.searchBoxTextChanged();
diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
index d00263b..16d31ae 100644
--- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
+++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Adrien Hopkins
+ * Copyright (C) 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -28,28 +28,28 @@ import sevenUnits.utils.UncertainDouble;
* A static utility class that can be used to make display rules for the
* presenter.
*
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
public final class StandardDisplayRules {
/**
* A rule that rounds to a fixed number of decimal places.
*
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
public static final class FixedDecimals
implements Function<UncertainDouble, String> {
+ /** Regular expression used for converting this to a string. */
public static final Pattern TO_STRING_PATTERN = Pattern
.compile("Round to (\\d+) decimal places");
- /**
- * The number of places to round to.
- */
+ /** The number of places to round to. */
private final int decimalPlaces;
/**
* @param decimalPlaces
* @since 2022-04-18
+ * @since v0.4.0
*/
private FixedDecimals(int decimalPlaces) {
this.decimalPlaces = decimalPlaces;
@@ -65,6 +65,7 @@ public final class StandardDisplayRules {
/**
* @return the number of decimal places this rule rounds to
* @since 2022-04-18
+ * @since v0.4.0
*/
public int decimalPlaces() {
return this.decimalPlaces;
@@ -76,7 +77,7 @@ public final class StandardDisplayRules {
return true;
if (!(obj instanceof FixedDecimals))
return false;
- final FixedDecimals other = (FixedDecimals) obj;
+ final var other = (FixedDecimals) obj;
if (this.decimalPlaces != other.decimalPlaces)
return false;
return true;
@@ -96,22 +97,22 @@ public final class StandardDisplayRules {
/**
* A rule that rounds to a fixed number of significant digits.
*
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
public static final class FixedPrecision
implements Function<UncertainDouble, String> {
+ /** Regular expression used for converting this to a string. */
public static final Pattern TO_STRING_PATTERN = Pattern
.compile("Round to (\\d+) significant figures");
- /**
- * The number of significant figures to round to.
- */
+ /** The number of significant figures to round to. */
private final MathContext mathContext;
/**
* @param significantFigures
* @since 2022-04-18
+ * @since v0.4.0
*/
private FixedPrecision(int significantFigures) {
this.mathContext = new MathContext(significantFigures,
@@ -130,7 +131,7 @@ public final class StandardDisplayRules {
return true;
if (!(obj instanceof FixedPrecision))
return false;
- final FixedPrecision other = (FixedPrecision) obj;
+ final var other = (FixedPrecision) obj;
if (this.mathContext == null) {
if (other.mathContext != null)
return false;
@@ -148,6 +149,7 @@ public final class StandardDisplayRules {
/**
* @return the number of significant figures this rule rounds to
* @since 2022-04-18
+ * @since v0.4.0
*/
public int significantFigures() {
return this.mathContext.getPrecision();
@@ -165,8 +167,8 @@ public final class StandardDisplayRules {
* This means the output will have around as many significant figures as the
* input.
*
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
public static final class UncertaintyBased
implements Function<UncertainDouble, String> {
@@ -193,10 +195,10 @@ public final class StandardDisplayRules {
/**
* @param decimalPlaces decimal places to round to
* @return a rounding rule that rounds to fixed number of decimal places
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
- public static final FixedDecimals fixedDecimals(int decimalPlaces) {
+ public static FixedDecimals fixedDecimals(int decimalPlaces) {
return new FixedDecimals(decimalPlaces);
}
@@ -204,10 +206,10 @@ public final class StandardDisplayRules {
* @param significantFigures significant figures to round to
* @return a rounding rule that rounds to a fixed number of significant
* figures
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
- public static final FixedPrecision fixedPrecision(int significantFigures) {
+ public static FixedPrecision fixedPrecision(int significantFigures) {
return new FixedPrecision(significantFigures);
}
@@ -218,10 +220,10 @@ public final class StandardDisplayRules {
* @return display rule
* @throws IllegalArgumentException if the provided string is not that of a
* standard rule.
- * @since v0.4.0
* @since 2021-12-24
+ * @since v0.4.0
*/
- public static final Function<UncertainDouble, String> getStandardRule(
+ public static Function<UncertainDouble, String> getStandardRule(
String ruleToString) {
if (UNCERTAINTY_BASED_ROUNDING_RULE.toString().equals(ruleToString))
return UNCERTAINTY_BASED_ROUNDING_RULE;
@@ -230,13 +232,13 @@ public final class StandardDisplayRules {
final var placesMatch = FixedDecimals.TO_STRING_PATTERN
.matcher(ruleToString);
if (placesMatch.matches())
- return new FixedDecimals(Integer.valueOf(placesMatch.group(1)));
+ return new FixedDecimals(Integer.parseInt(placesMatch.group(1)));
// test if it is a fixed-sig-fig rule
final var sigFigMatch = FixedPrecision.TO_STRING_PATTERN
.matcher(ruleToString);
if (sigFigMatch.matches())
- return new FixedPrecision(Integer.valueOf(sigFigMatch.group(1)));
+ return new FixedPrecision(Integer.parseInt(sigFigMatch.group(1)));
throw new IllegalArgumentException(
"Provided string does not match any given rules.");
@@ -244,10 +246,10 @@ public final class StandardDisplayRules {
/**
* @return an UncertainDouble-based rounding rule
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
- public static final UncertaintyBased uncertaintyBased() {
+ public static UncertaintyBased uncertaintyBased() {
return UNCERTAINTY_BASED_ROUNDING_RULE;
}
diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java
index 997acc3..8be58f5 100644
--- a/src/main/java/sevenUnitsGUI/TabbedView.java
+++ b/src/main/java/sevenUnitsGUI/TabbedView.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Adrien Hopkins
+ * Copyright (C) 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -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;
@@ -64,8 +67,8 @@ import sevenUnits.utils.UncertainDouble;
/**
* A View that separates its functions into multiple tabs
*
- * @since v0.4.0
* @since 2022-02-19
+ * @since v0.4.0
*/
final class TabbedView implements ExpressionConversionView, UnitConversionView {
/**
@@ -73,8 +76,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
*
* @param <E> type of item in list
*
- * @since v0.4.0
* @since 2022-02-19
+ * @since v0.4.0
*/
private static final class JComboBoxItemSet<E> extends AbstractSet<E> {
private final JComboBox<E> comboBox;
@@ -82,6 +85,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
/**
* @param comboBox combo box to get items from
* @since 2022-02-19
+ * @since v0.4.0
*/
public JComboBoxItemSet(JComboBox<E> comboBox) {
this.comboBox = comboBox;
@@ -101,9 +105,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
public E next() {
if (this.hasNext())
return JComboBoxItemSet.this.comboBox.getItemAt(this.index++);
- else
- throw new NoSuchElementException(
- "Iterator has finished iteration");
+ throw new NoSuchElementException(
+ "Iterator has finished iteration");
}
};
}
@@ -119,10 +122,10 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
* The standard types of rounding, corresponding to the options on the
* TabbedView's settings panel.
*
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
- private static enum StandardRoundingType {
+ private enum StandardRoundingType {
/**
* Rounds to a fixed number of significant digits. Precision is used,
* representing the number of significant digits to round to.
@@ -144,8 +147,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
* Creates a TabbedView.
*
* @param args command line arguments
- * @since v0.4.0
* @since 2022-02-19
+ * @since v0.4.0
*/
public static void main(String[] args) {
// This view doesn't need to do anything, the side effects of creating it
@@ -195,15 +198,19 @@ 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<String> localeSelector;
private StandardRoundingType roundingType;
private int precision;
+ private final Map<String, Consumer<String>> localizedTextSetters;
+
/**
* Creates the view and makes it visible to the user
- *
- * @since v0.4.0
+ *
* @since 2022-02-19
+ * @since v0.4.0
*/
public TabbedView() {
// enable system look and feel
@@ -218,21 +225,25 @@ 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("7Units (Unlocalized)");
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);
+ this.localizedTextSetters = new HashMap<>();
+
// ============ UNIT CONVERSION TAB ============
- final JPanel convertUnitPanel = new JPanel();
+ final var 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());
{ // panel for input part
- final JPanel inputPanel = new JPanel();
+ final var inputPanel = new JPanel();
convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
inputPanel.setLayout(new GridLayout(1, 3));
inputPanel.setBorder(new EmptyBorder(6, 6, 3, 6));
@@ -240,7 +251,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.fromSearch = new SearchBoxList<>();
inputPanel.add(this.fromSearch);
- final JPanel inBetweenPanel = new JPanel();
+ final var inBetweenPanel = new JPanel();
inputPanel.add(inBetweenPanel);
inBetweenPanel.setLayout(new BorderLayout());
@@ -249,7 +260,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.dimensionSelector
.addItemListener(e -> this.presenter.updateView());
- final JLabel arrowLabel = new JLabel("-->");
+ final var arrowLabel = new JLabel("-->");
inBetweenPanel.add(arrowLabel, BorderLayout.CENTER);
arrowLabel.setHorizontalAlignment(SwingConstants.CENTER);
@@ -258,12 +269,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
}
{ // panel for submit and output, and also value entry
- final JPanel outputPanel = new JPanel();
+ final var 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: ");
+ final var valuePrompt = new JLabel();
+ this.localizedTextSetters.put("tv.convert_units.value_prompt",
+ valuePrompt::setText);
outputPanel.add(valuePrompt, BorderLayout.LINE_START);
this.valueInput = new JTextField();
@@ -271,6 +284,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());
@@ -283,23 +298,31 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
}
// ============ EXPRESSION CONVERSION TAB ============
- final JPanel convertExpressionPanel = new JPanel();
+ final var 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
@@ -309,13 +332,16 @@ 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();
+ final var 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());
@@ -331,8 +357,10 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.unitTextBox.setLineWrap(true);
// ============ PREFIX VIEWER =============
- final JPanel prefixLookupPanel = new JPanel();
+ final var 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));
@@ -349,23 +377,24 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
// ============ INFO PANEL ============
- final JPanel infoPanel = new JPanel();
+ final var 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());
+ this.infoTextArea = new JTextArea();
+ this.infoTextArea.setEditable(false);
+ this.infoTextArea.setOpaque(false);
+ infoPanel.add(this.infoTextArea);
// ============ SETTINGS PANEL ============
+ this.localeSelector = new JComboBox<>();
this.masterPane.addTab("\u2699",
new JScrollPane(this.createSettingsPanel()));
this.masterPane.setMnemonicAt(5, KeyEvent.VK_S);
// ============ FINALIZE CREATION OF VIEW ============
this.presenter.postViewInitialize();
+ this.updateText();
this.frame.pack();
this.frame.setVisible(true);
}
@@ -375,40 +404,46 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
* code more organized, as this function is massive!)
*
* @since 2022-02-19
+ * @since v0.4.0
*/
private JPanel createSettingsPanel() {
- final JPanel settingsPanel = new JPanel();
+ final var settingsPanel = new JPanel();
settingsPanel
.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
// ============ ROUNDING SETTINGS ============
{
- final JPanel roundingPanel = new JPanel();
+ final var 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
- final ButtonGroup roundingRuleButtons = new ButtonGroup();
+ final var 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:");
+ final var 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 var 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)
.setAnchor(GridBagConstraints.LINE_START).build());
- final JSlider sigDigSlider = new JSlider(0, 12);
+ final var sigDigSlider = new JSlider(0, 12);
roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5)
.setAnchor(GridBagConstraints.LINE_START).build());
@@ -428,8 +463,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
});
// significant digit rounding
- final JRadioButton fixedPrecision = new JRadioButton(
- "Fixed Precision");
+ final var fixedPrecision = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.rounding.fixed_sigfig",
+ fixedPrecision::setText);
if (this.roundingType == StandardRoundingType.SIGNIFICANT_DIGITS) {
fixedPrecision.setSelected(true);
}
@@ -444,8 +480,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
.setAnchor(GridBagConstraints.LINE_START).build());
// decimal place rounding
- final JRadioButton fixedDecimals = new JRadioButton(
- "Fixed Decimal Places");
+ final var fixedDecimals = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.rounding.fixed_places",
+ fixedDecimals::setText);
if (this.roundingType == StandardRoundingType.DECIMAL_PLACES) {
fixedDecimals.setSelected(true);
}
@@ -460,8 +497,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
.setAnchor(GridBagConstraints.LINE_START).build());
// scientific rounding
- final JRadioButton relativePrecision = new JRadioButton(
- "Uncertainty-Based Rounding");
+ final var relativePrecision = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.rounding.uncertainty",
+ relativePrecision::setText);
if (this.roundingType == StandardRoundingType.UNCERTAINTY) {
relativePrecision.setSelected(true);
}
@@ -478,10 +516,10 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
// ============ PREFIX REPETITION SETTINGS ============
{
- final JPanel prefixRepetitionPanel = new JPanel();
+ final var 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()
@@ -489,9 +527,11 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
"Presenter loaded non-standard prefix rule"));
// prefix rules
- final ButtonGroup prefixRuleButtons = new ButtonGroup();
+ final var prefixRuleButtons = new ButtonGroup();
- final JRadioButton noRepetition = new JRadioButton("No Repetition");
+ final var noRepetition = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.repetition.no",
+ noRepetition::setText);
if (prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) {
noRepetition.setSelected(true);
}
@@ -504,7 +544,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 var noRestriction = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.repetition.any",
+ noRestriction::setText);
if (prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) {
noRestriction.setSelected(true);
}
@@ -517,8 +559,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 var customRepetition = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.repetition.complex",
+ customRepetition::setText);
if (prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) {
customRepetition.setSelected(true);
}
@@ -534,18 +577,20 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
// ============ SEARCH SETTINGS ============
{
- final JPanel searchingPanel = new JPanel();
+ final var 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
- final ButtonGroup searchRuleButtons = new ButtonGroup();
+ final var searchRuleButtons = new ButtonGroup();
final var searchRule = this.presenter.getSearchRule();
- final JRadioButton noPrefixes = new JRadioButton(
- "Never Include Prefixed Units");
+ final var 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();
@@ -555,8 +600,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 var 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();
@@ -566,8 +612,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 var alwaysInclude = new JRadioButton();
+ this.localizedTextSetters.put("tv.settings.search.all_prefixes",
+ alwaysInclude::setText);
alwaysInclude.addActionListener(e -> {
this.presenter
.setSearchRule(this.presenter.getUniversalSearchRule());
@@ -592,34 +639,66 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
// ============ OTHER SETTINGS ============
{
- final JPanel miscPanel = new JPanel();
+ final var miscPanel = new JPanel();
settingsPanel.add(miscPanel);
miscPanel.setLayout(new GridBagLayout());
- final JCheckBox oneWay = new JCheckBox("Convert One Way Only");
+ final var oneWay = new JCheckBox();
+ this.localizedTextSetters.put("tv.settings.oneway", oneWay::setText);
oneWay.setSelected(this.presenter.oneWayConversionEnabled());
oneWay.addItemListener(e -> {
this.presenter.setOneWayConversionEnabled(
e.getStateChange() == ItemEvent.SELECTED);
this.presenter.saveSettings();
});
- miscPanel.add(oneWay, new GridBagBuilder(0, 0)
+ miscPanel.add(oneWay, new GridBagBuilder(0, 0, 2, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
- final JCheckBox showAllVariations = new JCheckBox(
- "Show Duplicate Units & Prefixes");
+ final var showAllVariations = new JCheckBox();
+ this.localizedTextSetters.put("tv.settings.show_duplicate",
+ showAllVariations::setText);
showAllVariations.setSelected(this.presenter.duplicatesShown());
showAllVariations.addItemListener(e -> {
this.presenter
.setShowDuplicates(e.getStateChange() == ItemEvent.SELECTED);
this.presenter.saveSettings();
});
- miscPanel.add(showAllVariations, new GridBagBuilder(0, 1)
+ miscPanel.add(showAllVariations, new GridBagBuilder(0, 1, 2, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final var 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 JButton unitFileButton = new JButton("Manage Unit Data Files");
+ final var localeLabel = new JLabel();
+ this.localizedTextSetters.put("tv.settings.locale",
+ localeLabel::setText);
+ miscPanel.add(localeLabel, new GridBagBuilder(0, 3, 1, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ this.presenter.getAvailableLocales().stream().sorted()
+ .forEachOrdered(this.localeSelector::addItem);
+ this.localeSelector.setSelectedItem(this.presenter.getUserLocale());
+ this.localeSelector.addItemListener(e -> {
+ this.presenter.setUserLocale((String) e.getItem());
+ this.presenter.saveSettings();
+ });
+ miscPanel.add(localeSelector, new GridBagBuilder(1, 3, 1, 1)
+ .setAnchor(GridBagConstraints.LINE_END).build());
+
+ final var unitFileButton = new JButton();
+ this.localizedTextSetters.put("tv.settings.unitfiles.button",
+ unitFileButton::setText);
unitFileButton.setEnabled(false);
- miscPanel.add(unitFileButton, new GridBagBuilder(0, 2)
+ miscPanel.add(unitFileButton, new GridBagBuilder(0, 4, 2, 1)
.setAnchor(GridBagConstraints.LINE_START).build());
}
@@ -662,8 +741,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
/**
* @return the precision of the presenter's rounding rule, if that is
* meaningful
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
private OptionalInt getPresenterPrecision() {
final var presenterRule = this.presenter.getNumberDisplayRule();
@@ -671,18 +750,17 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
return OptionalInt
.of(((StandardDisplayRules.FixedDecimals) presenterRule)
.decimalPlaces());
- else if (presenterRule instanceof StandardDisplayRules.FixedPrecision)
+ if (presenterRule instanceof StandardDisplayRules.FixedPrecision)
return OptionalInt
.of(((StandardDisplayRules.FixedPrecision) presenterRule)
.significantFigures());
- else
- return OptionalInt.empty();
+ return OptionalInt.empty();
}
/**
* @return presenter's prefix repetition rule
- * @since v0.4.0
* @since 2022-04-19
+ * @since v0.4.0
*/
private Optional<DefaultPrefixRepetitionRule> getPresenterPrefixRule() {
final var prefixRule = this.presenter.getPrefixRepetitionRule();
@@ -694,25 +772,24 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
/**
* Determines which rounding type the presenter is currently using, if any.
*
- * @since v0.4.0
* @since 2022-04-18
+ * @since v0.4.0
*/
private Optional<StandardRoundingType> getPresenterRoundingType() {
final var presenterRule = this.presenter.getNumberDisplayRule();
if (Objects.equals(presenterRule,
StandardDisplayRules.uncertaintyBased()))
return Optional.of(StandardRoundingType.UNCERTAINTY);
- else if (presenterRule instanceof StandardDisplayRules.FixedDecimals)
+ if (presenterRule instanceof StandardDisplayRules.FixedDecimals)
return Optional.of(StandardRoundingType.DECIMAL_PLACES);
- else if (presenterRule instanceof StandardDisplayRules.FixedPrecision)
+ if (presenterRule instanceof StandardDisplayRules.FixedPrecision)
return Optional.of(StandardRoundingType.SIGNIFICANT_DIGITS);
- else
- return Optional.empty();
+ return Optional.empty();
}
@Override
public Optional<String> getSelectedDimensionName() {
- final String selectedItem = (String) this.dimensionSelector
+ final var selectedItem = (String) this.dimensionSelector
.getSelectedItem();
return Optional.ofNullable(selectedItem);
}
@@ -780,8 +857,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
@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
@@ -806,9 +882,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
/**
* Sets the presenter's rounding rule to the one specified by the current
* settings
- *
- * @since v0.4.0
+ *
* @since 2022-04-18
+ * @since v0.4.0
*/
private void updatePresenterRoundingRule() {
final Function<UncertainDouble, String> roundingRule;
@@ -828,4 +904,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView {
this.presenter.setNumberDisplayRule(roundingRule);
this.presenter.saveSettings();
}
+
+ @Override
+ 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/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
index 43a62e6..3c2bb6c 100644
--- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
+++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Adrien Hopkins
+ * Copyright (C) 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -24,18 +24,18 @@ import sevenUnits.unit.UnitValue;
/**
* A record of a conversion between units or expressions
*
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public final class UnitConversionRecord {
/**
* Gets a {@code UnitConversionRecord} from two linear unit values
*
- * @param input input unit & value
- * @param output output unit & value
+ * @param input input unit &amp; value
+ * @param output output unit &amp; value
* @return unit conversion record
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public static UnitConversionRecord fromLinearValues(LinearUnitValue input,
LinearUnitValue output) {
@@ -48,11 +48,11 @@ public final class UnitConversionRecord {
/**
* Gets a {@code UnitConversionRecord} from two unit values
*
- * @param input input unit & value
- * @param output output unit & value
+ * @param input input unit &amp; value
+ * @param output output unit &amp; value
* @return unit conversion record
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public static UnitConversionRecord fromValues(UnitValue input,
UnitValue output) {
@@ -70,8 +70,8 @@ public final class UnitConversionRecord {
* @param inputValueString string representing input value
* @param outputValueString string representing output value
* @return unit conversion record
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public static UnitConversionRecord valueOf(String fromName, String toName,
String inputValueString, String outputValueString) {
@@ -79,13 +79,9 @@ public final class UnitConversionRecord {
outputValueString);
}
- /**
- * The name of the unit or expression that was converted from
- */
+ /** The name of the unit or expression that was converted from */
private final String fromName;
- /**
- * The name of the unit or expression that was converted to
- */
+ /** The name of the unit or expression that was converted to */
private final String toName;
/**
@@ -106,6 +102,7 @@ public final class UnitConversionRecord {
* @param inputValueString string representing input value
* @param outputValueString string representing output value
* @since 2022-04-09
+ * @since v0.4.0
*/
private UnitConversionRecord(String fromName, String toName,
String inputValueString, String outputValueString) {
@@ -121,7 +118,7 @@ public final class UnitConversionRecord {
return true;
if (!(obj instanceof UnitConversionRecord))
return false;
- final UnitConversionRecord other = (UnitConversionRecord) obj;
+ final var other = (UnitConversionRecord) obj;
if (this.fromName == null) {
if (other.fromName != null)
return false;
@@ -147,8 +144,8 @@ public final class UnitConversionRecord {
/**
* @return name of unit or expression that was converted from
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public String fromName() {
return this.fromName;
@@ -156,8 +153,8 @@ public final class UnitConversionRecord {
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
+ final var prime = 31;
+ var result = 1;
result = prime * result
+ (this.fromName == null ? 0 : this.fromName.hashCode());
result = prime * result + (this.inputValueString == null ? 0
@@ -171,8 +168,8 @@ public final class UnitConversionRecord {
/**
* @return string representing input value
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public String inputValueString() {
return this.inputValueString;
@@ -180,8 +177,8 @@ public final class UnitConversionRecord {
/**
* @return string representing output value
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public String outputValueString() {
return this.outputValueString;
@@ -189,8 +186,8 @@ public final class UnitConversionRecord {
/**
* @return name of unit or expression that was converted to
- * @since v0.4.0
* @since 2022-04-09
+ * @since v0.4.0
*/
public String toName() {
return this.toName;
@@ -198,9 +195,9 @@ public final class UnitConversionRecord {
@Override
public String toString() {
- final String inputString = this.inputValueString.isBlank() ? this.fromName
+ final var inputString = this.inputValueString.isBlank() ? this.fromName
: this.inputValueString + " " + this.fromName;
- final String outputString = this.outputValueString.isBlank() ? this.toName
+ final var outputString = this.outputValueString.isBlank() ? this.toName
: this.outputValueString + " " + this.toName;
return inputString + " = " + outputString;
}
diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java
index b9077f7..fa3a388 100644
--- a/src/main/java/sevenUnitsGUI/UnitConversionView.java
+++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021-2022 Adrien Hopkins
+ * Copyright (C) 2021, 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -21,59 +21,59 @@ import java.util.Set;
/**
* A View that supports single unit-based conversion
- *
+ *
* @author Adrien Hopkins
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
public interface UnitConversionView extends View {
/**
* @return dimensions available for filtering
- * @since v0.4.0
* @since 2022-01-29
+ * @since v0.4.0
*/
Set<String> getDimensionNames();
/**
* @return name of unit to convert <em>from</em>
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
Optional<String> getFromSelection();
/**
* @return list of names of units available to convert from
- * @since v0.4.0
* @since 2022-03-30
+ * @since v0.4.0
*/
Set<String> getFromUnitNames();
/**
* @return value to convert between the units (specifically, the numeric
* string provided by the user)
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
String getInputValue();
/**
* @return selected dimension
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
Optional<String> getSelectedDimensionName();
/**
* @return name of unit to convert <em>to</em>
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
Optional<String> getToSelection();
/**
* @return list of names of units available to convert to
- * @since v0.4.0
* @since 2022-03-30
+ * @since v0.4.0
*/
Set<String> getToUnitNames();
@@ -81,8 +81,8 @@ public interface UnitConversionView extends View {
* Sets the available dimensions for filtering.
*
* @param dimensionNames names of dimensions to use
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
void setDimensionNames(Set<String> dimensionNames);
@@ -92,8 +92,8 @@ public interface UnitConversionView extends View {
* that allow the user to select units from a list.
*
* @param unitNames names of units to convert from
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
void setFromUnitNames(Set<String> unitNames);
@@ -103,18 +103,17 @@ public interface UnitConversionView extends View {
* that allow the user to select units from a list.
*
* @param unitNames names of units to convert to
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
void setToUnitNames(Set<String> unitNames);
/**
* Shows the output of a unit conversion.
- *
- * @param input input unit & value (obtained from this view)
- * @param output output unit & value
- * @since v0.4.0
+ *
+ * @param uc record of unit conversion
* @since 2021-12-24
+ * @since v0.4.0
*/
void showUnitConversionOutput(UnitConversionRecord uc);
}
diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java
index 7dd0c44..0adeb3a 100644
--- a/src/main/java/sevenUnitsGUI/View.java
+++ b/src/main/java/sevenUnitsGUI/View.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021-2022 Adrien Hopkins
+ * Copyright (C) 2021, 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -24,16 +24,16 @@ import sevenUnits.utils.NameSymbol;
/**
* An object that controls user interaction with 7Units
- *
+ *
* @author Adrien Hopkins
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
public interface View {
/**
* @return a new tabbed view
- * @since v0.4.0
* @since 2022-04-19
+ * @since v0.4.0
*/
static View createTabbedView() {
return new TabbedView();
@@ -41,22 +41,22 @@ public interface View {
/**
* @return the presenter associated with this view
- * @since v0.4.0
* @since 2022-04-19
+ * @since v0.4.0
*/
Presenter getPresenter();
/**
* @return name of prefix currently being viewed
- * @since v0.4.0
* @since 2022-04-10
+ * @since v0.4.0
*/
Optional<String> getViewedPrefixName();
/**
* @return name of unit currently being viewed
- * @since v0.4.0
* @since 2022-04-10
+ * @since v0.4.0
*/
Optional<String> getViewedUnitName();
@@ -65,8 +65,8 @@ public interface View {
* viewer
*
* @param prefixNames prefix names to view
- * @since v0.4.0
* @since 2022-04-10
+ * @since v0.4.0
*/
void setViewablePrefixNames(Set<String> prefixNames);
@@ -74,8 +74,8 @@ public interface View {
* Sets the list of units that are available to be viewed in a unit viewer
*
* @param unitNames unit names to view
- * @since v0.4.0
* @since 2022-04-10
+ * @since v0.4.0
*/
void setViewableUnitNames(Set<String> unitNames);
@@ -85,8 +85,8 @@ public interface View {
* @param title title of error message; on any view that uses an error
* dialog, this should be the title of the error dialog.
* @param message error message
- * @since v0.4.0
* @since 2021-12-15
+ * @since v0.4.0
*/
void showErrorMessage(String title, String message);
@@ -95,8 +95,8 @@ public interface View {
*
* @param name name(s) and symbol of prefix
* @param multiplierString string representation of prefix multiplier
- * @since v0.4.0
* @since 2022-04-10
+ * @since v0.4.0
*/
void showPrefix(NameSymbol name, String multiplierString);
@@ -107,9 +107,16 @@ public interface View {
* @param definition unit's definition string
* @param dimensionName name of unit's dimension
* @param type type of unit (metric/semi-metric/non-metric)
- * @since v0.4.0
* @since 2022-04-10
+ * @since v0.4.0
*/
void showUnit(NameSymbol name, String definition, String dimensionName,
UnitType type);
+
+ /**
+ * Updates the view's text to reflect the presenter's locale.
+ *
+ * This method <b>must not</b> call {@link Presenter#setUserLocale(String)}.
+ */
+ void updateText();
}
diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java
index e6593fb..750e2d9 100644
--- a/src/main/java/sevenUnitsGUI/ViewBot.java
+++ b/src/main/java/sevenUnitsGUI/ViewBot.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Adrien Hopkins
+ * Copyright (C) 2022, 2024, 2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -30,10 +30,10 @@ import sevenUnits.utils.Nameable;
/**
* A class that simulates a View (supports both unit and expression conversion)
* for testing. Getters and setters work as expected.
- *
+ *
* @author Adrien Hopkins
- * @since v0.4.0
* @since 2022-01-29
+ * @since v0.4.0
*/
public final class ViewBot
implements UnitConversionView, ExpressionConversionView {
@@ -42,6 +42,7 @@ public final class ViewBot
* {@link View#showPrefix(NameSymbol, String)}, for testing.
*
* @since 2022-04-16
+ * @since v0.4.0
*/
public static final class PrefixViewingRecord implements Nameable {
private final NameSymbol nameSymbol;
@@ -51,6 +52,7 @@ public final class ViewBot
* @param nameSymbol
* @param multiplierString
* @since 2022-04-16
+ * @since v0.4.0
*/
public PrefixViewingRecord(NameSymbol nameSymbol,
String multiplierString) {
@@ -64,7 +66,7 @@ public final class ViewBot
return true;
if (!(obj instanceof PrefixViewingRecord))
return false;
- final PrefixViewingRecord other = (PrefixViewingRecord) obj;
+ final var other = (PrefixViewingRecord) obj;
return Objects.equals(this.multiplierString, other.multiplierString)
&& Objects.equals(this.nameSymbol, other.nameSymbol);
}
@@ -79,17 +81,19 @@ public final class ViewBot
return Objects.hash(this.multiplierString, this.nameSymbol);
}
+ /** @return A string representation of the prefix multiplier. */
public String multiplierString() {
return this.multiplierString;
}
+ /** @return A {@code NameSymbol} describing the prefix. */
public NameSymbol nameSymbol() {
return this.nameSymbol;
}
@Override
public String toString() {
- final StringBuilder builder = new StringBuilder();
+ final var builder = new StringBuilder();
builder.append("PrefixViewingRecord [nameSymbol=");
builder.append(this.nameSymbol);
builder.append(", multiplierString=");
@@ -104,6 +108,7 @@ public final class ViewBot
* {@link View#showUnit(NameSymbol, String, String, UnitType)}, for testing.
*
* @since 2022-04-16
+ * @since v0.4.0
*/
public static final class UnitViewingRecord implements Nameable {
private final NameSymbol nameSymbol;
@@ -112,7 +117,12 @@ public final class ViewBot
private final UnitType unitType;
/**
+ * @param nameSymbol name(s) and symbol of unit
+ * @param definition unit's definition string
+ * @param dimensionName name of unit's dimension
+ * @param unitType type of unit (metric/semi-metric/non-metric)
* @since 2022-04-16
+ * @since v0.4.0
*/
public UnitViewingRecord(NameSymbol nameSymbol, String definition,
String dimensionName, UnitType unitType) {
@@ -125,6 +135,7 @@ public final class ViewBot
/**
* @return the definition
* @since 2022-04-16
+ * @since v0.4.0
*/
public String definition() {
return this.definition;
@@ -133,6 +144,7 @@ public final class ViewBot
/**
* @return the dimensionName
* @since 2022-04-16
+ * @since v0.4.0
*/
public String dimensionName() {
return this.dimensionName;
@@ -144,7 +156,7 @@ public final class ViewBot
return true;
if (!(obj instanceof UnitViewingRecord))
return false;
- final UnitViewingRecord other = (UnitViewingRecord) obj;
+ final var other = (UnitViewingRecord) obj;
return Objects.equals(this.definition, other.definition)
&& Objects.equals(this.dimensionName, other.dimensionName)
&& Objects.equals(this.nameSymbol, other.nameSymbol)
@@ -154,6 +166,7 @@ public final class ViewBot
/**
* @return the nameSymbol
* @since 2022-04-16
+ * @since v0.4.0
*/
@Override
public NameSymbol getNameSymbol() {
@@ -166,13 +179,14 @@ public final class ViewBot
this.nameSymbol, this.unitType);
}
+ /** @return name(s) and symbol of unit */
public NameSymbol nameSymbol() {
return this.nameSymbol;
}
@Override
public String toString() {
- final StringBuilder builder = new StringBuilder();
+ final var builder = new StringBuilder();
builder.append("UnitViewingRecord [nameSymbol=");
builder.append(this.nameSymbol);
builder.append(", definition=");
@@ -188,6 +202,7 @@ public final class ViewBot
/**
* @return the unitType
* @since 2022-04-16
+ * @since v0.4.0
*/
public UnitType unitType() {
return this.unitType;
@@ -236,6 +251,7 @@ public final class ViewBot
* Creates a new {@code ViewBot} with a new presenter.
*
* @since 2022-01-29
+ * @since v0.4.0
*/
public ViewBot() {
this.presenter = new Presenter(this);
@@ -249,6 +265,7 @@ public final class ViewBot
/**
* @return list of records of expression conversions done by this bot
* @since 2022-04-09
+ * @since v0.4.0
*/
public List<UnitConversionRecord> expressionConversionList() {
return Collections.unmodifiableList(this.expressionConversions);
@@ -257,6 +274,7 @@ public final class ViewBot
/**
* @return the available dimensions
* @since 2022-01-29
+ * @since v0.4.0
*/
@Override
public Set<String> getDimensionNames() {
@@ -276,6 +294,7 @@ public final class ViewBot
/**
* @return the units available for selection in From
* @since 2022-01-29
+ * @since v0.4.0
*/
@Override
public Set<String> getFromUnitNames() {
@@ -290,6 +309,7 @@ public final class ViewBot
/**
* @return the presenter associated with tihs view
* @since 2022-01-29
+ * @since v0.4.0
*/
@Override
public Presenter getPresenter() {
@@ -314,6 +334,7 @@ public final class ViewBot
/**
* @return the units available for selection in To
* @since 2022-01-29
+ * @since v0.4.0
*/
@Override
public Set<String> getToUnitNames() {
@@ -333,6 +354,7 @@ public final class ViewBot
/**
* @return list of records of this viewBot's prefix views
* @since 2022-04-16
+ * @since v0.4.0
*/
public List<PrefixViewingRecord> prefixViewList() {
return Collections.unmodifiableList(this.prefixViewingRecords);
@@ -350,6 +372,7 @@ public final class ViewBot
* @param fromExpression the expression to convert from
* @throws NullPointerException if {@code fromExpression} is null
* @since 2022-01-29
+ * @since v0.4.0
*/
public void setFromExpression(String fromExpression) {
this.fromExpression = Objects.requireNonNull(fromExpression,
@@ -359,6 +382,7 @@ public final class ViewBot
/**
* @param fromSelection the fromSelection to set
* @since 2022-01-29
+ * @since v0.4.0
*/
public void setFromSelection(Optional<String> fromSelection) {
this.fromSelection = Objects.requireNonNull(fromSelection,
@@ -368,6 +392,7 @@ public final class ViewBot
/**
* @param fromSelection the fromSelection to set
* @since 2022-02-10
+ * @since v0.4.0
*/
public void setFromSelection(String fromSelection) {
this.setFromSelection(Optional.of(fromSelection));
@@ -381,20 +406,27 @@ public final class ViewBot
/**
* @param inputValue the inputValue to set
* @since 2022-01-29
+ * @since v0.4.0
*/
public void setInputValue(String inputValue) {
this.inputValue = inputValue;
}
/**
- * @param selectedDimension the selectedDimension to set
+ * @param selectedDimensionName the selectedDimensionName to set
* @since 2022-01-29
+ * @since v0.4.0
*/
public void setSelectedDimensionName(
Optional<String> selectedDimensionName) {
this.selectedDimensionName = selectedDimensionName;
}
+ /**
+ * Sets the view's selected dimension
+ *
+ * @param selectedDimensionName name of dimension to select (string)
+ */
public void setSelectedDimensionName(String selectedDimensionName) {
this.setSelectedDimensionName(Optional.of(selectedDimensionName));
}
@@ -405,6 +437,7 @@ public final class ViewBot
* @param toExpression the expression to convert to
* @throws NullPointerException if {@code toExpression} is null
* @since 2022-01-29
+ * @since v0.4.0
*/
public void setToExpression(String toExpression) {
this.toExpression = Objects.requireNonNull(toExpression,
@@ -412,14 +445,16 @@ public final class ViewBot
}
/**
- * @param toSelection the toSelection to set
+ * @param toSelection unit set in the 'To' selection
* @since 2022-01-29
+ * @since v0.4.0
*/
public void setToSelection(Optional<String> toSelection) {
this.toSelection = Objects.requireNonNull(toSelection,
"toSelection cannot be null.");
}
+ /** @param toSelection unit set in the 'To' selection */
public void setToSelection(String toSelection) {
this.setToSelection(Optional.of(toSelection));
}
@@ -439,18 +474,28 @@ public final class ViewBot
// do nothing, ViewBot supports selecting any unit
}
+ /** @param viewedPrefixName name of prefix being used */
public void setViewedPrefixName(Optional<String> viewedPrefixName) {
this.prefixViewerSelection = viewedPrefixName;
}
+ /**
+ * @param viewedPrefixName name of prefix being used (may not be null)
+ * @throws NullPointerException if {@code viewedPrefixName} is null
+ */
public void setViewedPrefixName(String viewedPrefixName) {
this.setViewedPrefixName(Optional.of(viewedPrefixName));
}
+ /** @param viewedUnitName name of unit being used */
public void setViewedUnitName(Optional<String> viewedUnitName) {
this.unitViewerSelection = viewedUnitName;
}
+ /**
+ * @param viewedUnitName name of unit being used (may not be null)
+ * @throws NullPointerException if {@code viewedUnitName} is null
+ */
public void setViewedUnitName(String viewedUnitName) {
this.setViewedUnitName(Optional.of(viewedUnitName));
}
@@ -493,6 +538,7 @@ public final class ViewBot
/**
* @return list of records of every unit conversion made by this bot
* @since 2022-04-09
+ * @since v0.4.0
*/
public List<UnitConversionRecord> unitConversionList() {
return Collections.unmodifiableList(this.unitConversions);
@@ -501,8 +547,14 @@ public final class ViewBot
/**
* @return list of records of unit viewings made by this bot
* @since 2022-04-16
+ * @since v0.4.0
*/
public List<UnitViewingRecord> unitViewList() {
return Collections.unmodifiableList(this.unitViewingRecords);
}
+
+ @Override
+ public void updateText() {
+ // do nothing, since ViewBot is not localized
+ }
}
diff --git a/src/main/java/sevenUnitsGUI/package-info.java b/src/main/java/sevenUnitsGUI/package-info.java
index cff1ded..9432960 100644
--- a/src/main/java/sevenUnitsGUI/package-info.java
+++ b/src/main/java/sevenUnitsGUI/package-info.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021 Adrien Hopkins
+ * Copyright (C) 2021-2025 Adrien Hopkins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -16,8 +16,9 @@
*/
/**
* The MVP GUI of SevenUnits
- *
+ *
* @author Adrien Hopkins
* @since 2021-12-15
+ * @since v0.4.0
*/
package sevenUnitsGUI; \ No newline at end of file