From da3a5098602f8177f6d5dac4a322f70d6fdf9126 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 24 Dec 2021 16:03:26 -0500 Subject: Did some API design for user settings, and moved GUI to a new package --- .../java/sevenUnitsGUI/UnitConversionView.java | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/main/java/sevenUnitsGUI/UnitConversionView.java (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java new file mode 100644 index 0000000..f653051 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2021 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 . + */ +package sevenUnitsGUI; + +import java.util.List; +import java.util.Optional; + +import sevenUnits.unit.BaseDimension; +import sevenUnits.unit.Unit; +import sevenUnits.utils.NamedObjectProduct; +import sevenUnits.utils.ObjectProduct; + +/** + * A View that supports single unit-based conversion + * + * @author Adrien Hopkins + * @since 2021-12-15 + */ +public interface UnitConversionView extends View { + /** + * @return unit to convert from + * @since 2021-12-15 + */ + Optional getFromSelection(); + + /** + * @return value to convert between the units (specifically, the numeric + * string provided by the user) + * @since 2021-12-15 + */ + Optional getInputValue(); + + /** + * @return selected dimension + * @since 2021-12-15 + */ + Optional> getSelectedDimension(); + + /** + * @return unit to convert to + * @since 2021-12-15 + */ + Optional getToSelection(); + + /** + * Sets the available dimensions for filtering. + * + * @param dimensions dimensions to use + * @since 2021-12-15 + */ + void setDimensions(List> dimensions); + + /** + * Sets the available units to convert from. {@link #getFromSelection} is not + * required to use one of these units; this method is to be used for views + * that allow the user to select units from a list. + * + * @param units units to convert from + * @since 2021-12-15 + */ + void setFromUnits(List units); + + /** + * Sets the available units to convert to. {@link #getToSelection} is not + * required to use one of these units; this method is to be used for views + * that allow the user to select units from a list. + * + * @param units units to convert to + * @since 2021-12-15 + */ + void setToUnits(List units); + + /** + * Shows the output of a unit conversion. + * + * @param outputString string that shows output of conversion + * @since 2021-12-24 + */ + void showUnitConversionOutput(String outputString); +} -- cgit v1.2.3 From 47b71bb5170fc2c3e1e052452864890826e6848d Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 29 Jan 2022 15:51:26 -0500 Subject: Created the ViewBot in preparation for GUI testing --- .../java/sevenUnitsGUI/UnitConversionView.java | 10 +- src/main/java/sevenUnitsGUI/ViewBot.java | 230 +++++++++++++++++++++ 2 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 src/main/java/sevenUnitsGUI/ViewBot.java (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index f653051..97ec30f 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -31,6 +31,12 @@ import sevenUnits.utils.ObjectProduct; * @since 2021-12-15 */ public interface UnitConversionView extends View { + /** + * @return dimensions available for filtering + * @since 2022-01-29 + */ + List> getDimensions(); + /** * @return unit to convert from * @since 2021-12-15 @@ -42,13 +48,13 @@ public interface UnitConversionView extends View { * string provided by the user) * @since 2021-12-15 */ - Optional getInputValue(); + String getInputValue(); /** * @return selected dimension * @since 2021-12-15 */ - Optional> getSelectedDimension(); + Optional> getSelectedDimension(); /** * @return unit to convert to diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java new file mode 100644 index 0000000..bc4103c --- /dev/null +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2022 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 . + */ +package sevenUnitsGUI; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import sevenUnits.unit.BaseDimension; +import sevenUnits.unit.Unit; +import sevenUnits.utils.NamedObjectProduct; +import sevenUnits.utils.ObjectProduct; + +/** + * A class that simulates a View (supports both unit and expression conversion) + * for testing. Getters and setters work as expected. + * + * @author Adrien Hopkins + * @since 2022-01-29 + */ +final class ViewBot implements UnitConversionView, ExpressionConversionView { + /** The presenter that works with this ViewBot */ + private final Presenter presenter; + + /** The dimensions available to select from */ + private List> dimensions; + /** The expression in the From field */ + private String fromExpression; + /** The expression in the To field */ + private String toExpression; + /** + * The user-provided string representing the value in {@code fromSelection} + */ + private String inputValue; + /** The unit selected in the From selection */ + private Optional fromSelection; + /** The unit selected in the To selection */ + private Optional toSelection; + /** The currently selected dimension */ + private Optional> selectedDimension; + /** The units available in the From selection */ + private List fromUnits; + /** The units available in the To selection */ + private List toUnits; + + /** + * Creates a new {@code ViewBot} with a new presenter. + * + * @since 2022-01-29 + */ + public ViewBot() { + this.presenter = new Presenter(this); + } + + /** + * @return the available dimensions + * @since 2022-01-29 + */ + @Override + public List> getDimensions() { + return this.dimensions; + } + + @Override + public String getFromExpression() { + return this.fromExpression; + } + + @Override + public Optional getFromSelection() { + return this.fromSelection; + } + + /** + * @return the units available for selection in From + * @since 2022-01-29 + */ + public List getFromUnits() { + return this.fromUnits; + } + + @Override + public String getInputValue() { + return this.inputValue; + } + + /** + * @return the presenter associated with tihs view + * @since 2022-01-29 + */ + public Presenter getPresenter() { + return this.presenter; + } + + @Override + public Optional> getSelectedDimension() { + return this.selectedDimension; + } + + @Override + public String getToExpression() { + return this.toExpression; + } + + @Override + public Optional getToSelection() { + return this.toSelection; + } + + /** + * @return the units available for selection in To + * @since 2022-01-29 + */ + public List getToUnits() { + return this.toUnits; + } + + @Override + public void setDimensions( + List> dimensions) { + this.dimensions = Objects.requireNonNull(dimensions, + "dimensions may not be null"); + } + + /** + * Sets the From expression (as in {@link #getFromExpression}). + * + * @param fromExpression the expression to convert from + * @throws NullPointerException if {@code fromExpression} is null + * @since 2022-01-29 + */ + public void setFromExpression(String fromExpression) { + this.fromExpression = Objects.requireNonNull(fromExpression, + "fromExpression cannot be null."); + } + + /** + * @param fromSelection the fromSelection to set + * @since 2022-01-29 + */ + public void setFromSelection(Optional fromSelection) { + this.fromSelection = Objects.requireNonNull(fromSelection, + "fromSelection cannot be null"); + } + + @Override + public void setFromUnits(List units) { + this.fromUnits = Objects.requireNonNull(units, "units may not be null"); + } + + /** + * @param inputValue the inputValue to set + * @since 2022-01-29 + */ + public void setInputValue(String inputValue) { + this.inputValue = inputValue; + } + + /** + * @param selectedDimension the selectedDimension to set + * @since 2022-01-29 + */ + public void setSelectedDimension( + Optional> selectedDimension) { + this.selectedDimension = selectedDimension; + } + + /** + * Sets the To expression (as in {@link #getToExpression}). + * + * @param toExpression the expression to convert to + * @throws NullPointerException if {@code toExpression} is null + * @since 2022-01-29 + */ + public void setToExpression(String toExpression) { + this.toExpression = Objects.requireNonNull(toExpression, + "toExpression cannot be null."); + } + + /** + * @param toSelection the toSelection to set + * @since 2022-01-29 + */ + public void setToSelection(Optional toSelection) { + this.toSelection = Objects.requireNonNull(toSelection, + "toSelection cannot be null."); + } + + @Override + public void setToUnits(List units) { + this.toUnits = Objects.requireNonNull(units, "units may not be null"); + } + + @Override + public void showErrorMessage(String title, String message) { + System.err.printf("%s: %s%n", title, message); + } + + @Override + public void showExpressionConversionOutput(String fromExpression, + String toExpression, double value) { + System.out.printf("Expression Conversion: %s = %d * (%s)%n", + fromExpression, value, toExpression); + } + + @Override + public void showUnitConversionOutput(String outputString) { + System.out.println("Unit conversion: " + outputString); + } + + @Override + public String toString() { + return super.toString() + String.format("[presenter=%s]", this.presenter); + } + +} -- cgit v1.2.3 From 540b798e397fb787fd81c8e6e636a2343655a42f Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 19 Feb 2022 16:59:26 -0500 Subject: Made barebones GUI (TabbedView) --- CHANGELOG.org | 1 + src/main/java/sevenUnits/ProgramInfo.java | 2 +- .../sevenUnits/converterGUI/SevenUnitsGUI.java | 2 +- src/main/java/sevenUnitsGUI/DelegateListModel.java | 242 +++++++++ src/main/java/sevenUnitsGUI/FilterComparator.java | 128 +++++ src/main/java/sevenUnitsGUI/GridBagBuilder.java | 479 ++++++++++++++++ src/main/java/sevenUnitsGUI/MutablePredicate.java | 70 +++ src/main/java/sevenUnitsGUI/Presenter.java | 88 ++- src/main/java/sevenUnitsGUI/SearchBoxList.java | 331 +++++++++++ src/main/java/sevenUnitsGUI/TabbedView.java | 605 +++++++++++++++++++++ .../java/sevenUnitsGUI/UnitConversionView.java | 10 +- src/main/java/sevenUnitsGUI/ViewBot.java | 23 +- src/main/resources/about.txt | 2 +- src/test/java/sevenUnitsGUI/PresenterTest.java | 12 +- 14 files changed, 1969 insertions(+), 26 deletions(-) create mode 100644 src/main/java/sevenUnitsGUI/DelegateListModel.java create mode 100644 src/main/java/sevenUnitsGUI/FilterComparator.java create mode 100644 src/main/java/sevenUnitsGUI/GridBagBuilder.java create mode 100644 src/main/java/sevenUnitsGUI/MutablePredicate.java create mode 100644 src/main/java/sevenUnitsGUI/SearchBoxList.java create mode 100644 src/main/java/sevenUnitsGUI/TabbedView.java (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/CHANGELOG.org b/CHANGELOG.org index 54983fa..bb6185e 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -5,6 +5,7 @@ - Added tests for the GUI *** Changed - Rewrote the GUI code internally using an MVP model to make it easier to maintain and improve + - Tweaked the look of the unit and expression conversion sections of the view ** v0.3.2 - [2021-12-02 Thu] *** Added - Added lots more tests for the backend and utilities diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index ba6bc7a..0d67824 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -24,7 +24,7 @@ package sevenUnits; */ public final class ProgramInfo { - public static final String VERSION = "0.3.2"; + public static final String VERSION = "0.4.0-dev"; private ProgramInfo() { // this class is only for static variables, you shouldn't be able to diff --git a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java index bfd5974..e21c25f 100644 --- a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java +++ b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java @@ -69,8 +69,8 @@ import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.LinearUnitValue; -import sevenUnits.unit.NameSymbol; import sevenUnits.unit.Metric; +import sevenUnits.unit.NameSymbol; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; diff --git a/src/main/java/sevenUnitsGUI/DelegateListModel.java b/src/main/java/sevenUnitsGUI/DelegateListModel.java new file mode 100644 index 0000000..5938b59 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/DelegateListModel.java @@ -0,0 +1,242 @@ +/** + * Copyright (C) 2018 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 . + */ +package sevenUnitsGUI; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import javax.swing.AbstractListModel; + +/** + * A list model that delegates to a list. + *

+ * It is recommended to use the delegate methods in DelegateListModel instead of the delegated list's methods because + * the delegate methods handle updating the list. + *

+ * + * @author Adrien Hopkins + * @since 2019-01-14 + * @since v0.1.0 + */ +final class DelegateListModel extends AbstractListModel implements List { + /** + * @since 2019-01-14 + * @since v0.1.0 + */ + private static final long serialVersionUID = 8985494428224810045L; + + /** + * The list that this model is a delegate to. + * + * @since 2019-01-14 + * @since v0.1.0 + */ + private final List delegate; + + /** + * Creates an empty {@code DelegateListModel}. + * + * @since 2019-04-13 + */ + public DelegateListModel() { + this(new ArrayList<>()); + } + + /** + * Creates the {@code DelegateListModel}. + * + * @param delegate + * list to delegate + * @since 2019-01-14 + * @since v0.1.0 + */ + public DelegateListModel(final List delegate) { + this.delegate = delegate; + } + + @Override + public boolean add(final E element) { + final int index = this.delegate.size(); + final boolean success = this.delegate.add(element); + this.fireIntervalAdded(this, index, index); + return success; + } + + @Override + public void add(final int index, final E element) { + this.delegate.add(index, element); + this.fireIntervalAdded(this, index, index); + } + + @Override + public boolean addAll(final Collection c) { + boolean changed = false; + for (final E e : c) { + if (this.add(e)) { + changed = true; + } + } + return changed; + } + + @Override + public boolean addAll(final int index, final Collection c) { + for (final E e : c) { + this.add(index, e); + } + return !c.isEmpty(); // Since this is a list, it will always change if c has elements. + } + + @Override + public void clear() { + final int oldSize = this.delegate.size(); + this.delegate.clear(); + if (oldSize >= 1) { + this.fireIntervalRemoved(this, 0, oldSize - 1); + } + } + + @Override + public boolean contains(final Object elem) { + return this.delegate.contains(elem); + } + + @Override + public boolean containsAll(final Collection c) { + for (final Object e : c) { + if (!c.contains(e)) + return false; + } + return true; + } + + @Override + public E get(final int index) { + return this.delegate.get(index); + } + + @Override + public E getElementAt(final int index) { + return this.delegate.get(index); + } + + @Override + public int getSize() { + return this.delegate.size(); + } + + @Override + public int indexOf(final Object elem) { + return this.delegate.indexOf(elem); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public Iterator iterator() { + return this.delegate.iterator(); + } + + @Override + public int lastIndexOf(final Object elem) { + return this.delegate.lastIndexOf(elem); + } + + @Override + public ListIterator listIterator() { + return this.delegate.listIterator(); + } + + @Override + public ListIterator listIterator(final int index) { + return this.delegate.listIterator(index); + } + + @Override + public E remove(final int index) { + final E returnValue = this.delegate.get(index); + this.delegate.remove(index); + this.fireIntervalRemoved(this, index, index); + return returnValue; + } + + @Override + public boolean remove(final Object o) { + final int index = this.delegate.indexOf(o); + final boolean returnValue = this.delegate.remove(o); + this.fireIntervalRemoved(this, index, index); + return returnValue; + } + + @Override + public boolean removeAll(final Collection c) { + boolean changed = false; + for (final Object e : c) { + if (this.remove(e)) { + changed = true; + } + } + return changed; + } + + @Override + public boolean retainAll(final Collection c) { + final int oldSize = this.size(); + final boolean 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); + this.delegate.set(index, element); + this.fireContentsChanged(this, index, index); + return returnValue; + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + return this.delegate.subList(fromIndex, toIndex); + } + + @Override + public Object[] toArray() { + return this.delegate.toArray(); + } + + @Override + public T[] toArray(final T[] a) { + return this.delegate.toArray(a); + } + + @Override + public String toString() { + return this.delegate.toString(); + } +} diff --git a/src/main/java/sevenUnitsGUI/FilterComparator.java b/src/main/java/sevenUnitsGUI/FilterComparator.java new file mode 100644 index 0000000..f34d0c0 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/FilterComparator.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2018 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 . + */ +package sevenUnitsGUI; + +import java.util.Comparator; +import java.util.Objects; + +/** + * A comparator that compares strings using a filter. + * + * @param type of element being compared + * + * @author Adrien Hopkins + * @since 2019-01-15 + * @since v0.1.0 + */ +final class FilterComparator implements Comparator { + /** + * 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 comparator; + /** + * Whether or not the comparison is case-sensitive. + * + * @since 2019-04-14 + * @since v0.2.0 + */ + private final boolean caseSensitive; + + /** + * Creates the {@code FilterComparator}. + * + * @param filter + * @since 2019-01-15 + * @since v0.1.0 + */ + public FilterComparator(final String filter) { + this(filter, null); + } + + /** + * Creates the {@code FilterComparator}. + * + * @param filter string to filter by + * @param comparator comparator to fall back to if all else fails, null is + * compareTo. + * @throws NullPointerException if filter is null + * @since 2019-01-15 + * @since v0.1.0 + */ + public FilterComparator(final String filter, + final Comparator comparator) { + this(filter, comparator, false); + } + + /** + * Creates the {@code FilterComparator}. + * + * @param filter string to filter by + * @param comparator comparator to fall back to if all else fails, null is + * compareTo. + * @param caseSensitive whether or not the comparator is case-sensitive + * @throws NullPointerException if filter is null + * @since 2019-04-14 + * @since v0.2.0 + */ + public FilterComparator(final String filter, final Comparator comparator, + final boolean caseSensitive) { + this.filter = Objects.requireNonNull(filter, "filter must not be null."); + this.comparator = comparator; + this.caseSensitive = caseSensitive; + } + + @Override + public int compare(final T arg0, final T arg1) { + // if this is case insensitive, make them lowercase + final String str0, str1; + if (this.caseSensitive) { + str0 = arg0.toString(); + str1 = arg1.toString(); + } else { + str0 = arg0.toString().toLowerCase(); + str1 = arg1.toString().toLowerCase(); + } + + // 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)) + 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)) + return 1; + + // other elements go last + if (this.comparator == null) + return str0.compareTo(str1); + else + return this.comparator.compare(arg0, arg1); + } +} diff --git a/src/main/java/sevenUnitsGUI/GridBagBuilder.java b/src/main/java/sevenUnitsGUI/GridBagBuilder.java new file mode 100644 index 0000000..32e94d7 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/GridBagBuilder.java @@ -0,0 +1,479 @@ +/** + * Copyright (C) 2018 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 . + */ +package sevenUnitsGUI; + +import java.awt.GridBagConstraints; +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 + */ +final class GridBagBuilder { + /** + * The built {@code GridBagConstraints}'s {@code gridx} property. + *

+ * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has + * gridx=0. The leading edge of a component's display area is its left edge for a horizontal, + * left-to-right container and its right edge for a horizontal, right-to-left container. The value + * RELATIVE specifies that the component be placed immediately following the component that was added + * to the container just before this component was added. + *

+ * The default value is RELATIVE. gridx should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridy + * @see java.awt.ComponentOrientation + */ + private final int gridx; + + /** + * The built {@code GridBagConstraints}'s {@code gridy} property. + *

+ * Specifies the cell at the top of the component's display area, where the topmost cell has gridy=0. + * The value RELATIVE specifies that the component be placed just below the component that was added to + * the container just before this component was added. + *

+ * The default value is RELATIVE. gridy should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridx + */ + private final int gridy; + + /** + * The built {@code GridBagConstraints}'s {@code gridwidth} property. + *

+ * Specifies the number of cells in a row for the component's display area. + *

+ * Use REMAINDER to specify that the component's display area will be from gridx to the + * last cell in the row. Use RELATIVE to specify that the component's display area will be from + * gridx to the next to the last one in its row. + *

+ * gridwidth should be non-negative and the default value is 1. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridheight + */ + private final int gridwidth; + + /** + * The built {@code GridBagConstraints}'s {@code gridheight} property. + *

+ * Specifies the number of cells in a column for the component's display area. + *

+ * Use REMAINDER to specify that the component's display area will be from gridy to the + * last cell in the column. Use RELATIVE to specify that the component's display area will be from + * gridy to the next to the last one in its column. + *

+ * gridheight should be a non-negative value and the default value is 1. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridwidth + */ + private final int gridheight; + + /** + * The built {@code GridBagConstraints}'s {@code weightx} property. + *

+ * Specifies how to distribute extra horizontal space. + *

+ * The grid bag layout manager calculates the weight of a column to be the maximum weightx of all the + * components in a column. If the resulting layout is smaller horizontally than the area it needs to fill, the extra + * space is distributed to each column in proportion to its weight. A column that has a weight of zero receives no + * extra space. + *

+ * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right + * edges. + *

+ * The default value of this field is 0. weightx should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#weighty + */ + private double weightx; + + /** + * The built {@code GridBagConstraints}'s {@code weighty} property. + *

+ * Specifies how to distribute extra vertical space. + *

+ * The grid bag layout manager calculates the weight of a row to be the maximum weighty of all the + * components in a row. If the resulting layout is smaller vertically than the area it needs to fill, the extra + * space is distributed to each row in proportion to its weight. A row that has a weight of zero receives no extra + * space. + *

+ * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom + * edges. + *

+ * The default value of this field is 0. weighty should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#weightx + */ + private double weighty; + + /** + * The built {@code GridBagConstraints}'s {@code anchor} property. + *

+ * This field is used when the component is smaller than its display area. It determines where, within the display + * area, to place the component. + *

+ * There are three kinds of possible values: orientation relative, baseline relative and absolute. Orientation + * relative values are interpreted relative to the container's component orientation property, baseline relative + * values are interpreted relative to the baseline and absolute values are not. The absolute values are: + * CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, + * SOUTH, SOUTHWEST, WEST, and NORTHWEST. The orientation + * relative values are: PAGE_START, PAGE_END, LINE_START, + * LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_START + * and LAST_LINE_END. The baseline relative values are: BASELINE, + * BASELINE_LEADING, BASELINE_TRAILING, ABOVE_BASELINE, + * ABOVE_BASELINE_LEADING, ABOVE_BASELINE_TRAILING, BELOW_BASELINE, + * BELOW_BASELINE_LEADING, and BELOW_BASELINE_TRAILING. The default value is + * CENTER. + * + * @serial + * @see #clone() + * @see java.awt.ComponentOrientation + */ + private int anchor; + + /** + * The built {@code GridBagConstraints}'s {@code fill} property. + *

+ * This field is used when the component's display area is larger than the component's requested size. It determines + * whether to resize the component, and if so, how. + *

+ * The following values are valid for fill: + * + *

    + *
  • NONE: Do not resize the component. + *
  • HORIZONTAL: Make the component wide enough to fill its display area horizontally, but do not + * change its height. + *
  • VERTICAL: Make the component tall enough to fill its display area vertically, but do not change + * its width. + *
  • BOTH: Make the component fill its display area entirely. + *
+ *

+ * The default value is NONE. + * + * @serial + * @see #clone() + */ + private int fill; + + /** + * The built {@code GridBagConstraints}'s {@code insets} property. + *

+ * This field specifies the external padding of the component, the minimum amount of space between the component and + * the edges of its display area. + *

+ * The default value is new Insets(0, 0, 0, 0). + * + * @serial + * @see #clone() + */ + private Insets insets; + + /** + * The built {@code GridBagConstraints}'s {@code ipadx} property. + *

+ * This field specifies the internal padding of the component, how much space to add to the minimum width of the + * component. The width of the component is at least its minimum width plus ipadx pixels. + *

+ * The default value is 0. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#ipady + */ + private int ipadx; + + /** + * The built {@code GridBagConstraints}'s {@code ipady} property. + *

+ * This field specifies the internal padding, that is, how much space to add to the minimum height of the component. + * The height of the component is at least its minimum height plus ipady pixels. + *

+ * The default value is 0. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#ipadx + */ + private int ipady; + + /** + * @param gridx + * x position + * @param gridy + * y position + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder(final int gridx, final int gridy) { + this(gridx, gridy, 1, 1); + } + + /** + * @param gridx + * x position + * @param gridy + * y position + * @param gridwidth + * number of cells occupied horizontally + * @param gridheight + * number of cells occupied vertically + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) { + this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + } + + /** + * @param gridx + * x position + * @param gridy + * y position + * @param gridwidth + * number of cells occupied horizontally + * @param gridheight + * number of cells occupied vertically + * @param weightx + * @param weighty + * @param anchor + * @param fill + * @param insets + * @param ipadx + * @param ipady + * @since 2018-11-30 + * @since v0.1.0 + */ + private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, 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; + this.gridheight = gridheight; + this.weightx = weightx; + this.weighty = weighty; + this.anchor = anchor; + this.fill = fill; + this.insets = (Insets) insets.clone(); + this.ipadx = ipadx; + this.ipady = ipady; + } + + /** + * @return {@code GridBagConstraints} created by this builder + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagConstraints build() { + return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx, + this.weighty, this.anchor, this.fill, this.insets, this.ipadx, this.ipady); + } + + /** + * @return anchor + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getAnchor() { + return this.anchor; + } + + /** + * @return fill + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getFill() { + return this.fill; + } + + /** + * @return gridheight + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getGridheight() { + return this.gridheight; + } + + /** + * @return gridwidth + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getGridwidth() { + return this.gridwidth; + } + + /** + * @return gridx + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getGridx() { + return this.gridx; + } + + /** + * @return gridy + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getGridy() { + return this.gridy; + } + + /** + * @return insets + * @since 2018-11-30 + * @since v0.1.0 + */ + public Insets getInsets() { + return this.insets; + } + + /** + * @return ipadx + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getIpadx() { + return this.ipadx; + } + + /** + * @return ipady + * @since 2018-11-30 + * @since v0.1.0 + */ + public int getIpady() { + return this.ipady; + } + + /** + * @return weightx + * @since 2018-11-30 + * @since v0.1.0 + */ + public double getWeightx() { + return this.weightx; + } + + /** + * @return weighty + * @since 2018-11-30 + * @since v0.1.0 + */ + public double getWeighty() { + return this.weighty; + } + + /** + * @param anchor + * anchor to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setAnchor(final int anchor) { + this.anchor = anchor; + return this; + } + + /** + * @param fill + * fill to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setFill(final int fill) { + this.fill = fill; + return this; + } + + /** + * @param insets + * insets to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setInsets(final Insets insets) { + this.insets = insets; + return this; + } + + /** + * @param ipadx + * ipadx to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setIpadx(final int ipadx) { + this.ipadx = ipadx; + return this; + } + + /** + * @param ipady + * ipady to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setIpady(final int ipady) { + this.ipady = ipady; + return this; + } + + /** + * @param weightx + * weightx to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setWeightx(final double weightx) { + this.weightx = weightx; + return this; + } + + /** + * @param weighty + * weighty to set + * @since 2018-11-30 + * @since v0.1.0 + */ + public GridBagBuilder setWeighty(final double weighty) { + this.weighty = weighty; + return this; + } +} diff --git a/src/main/java/sevenUnitsGUI/MutablePredicate.java b/src/main/java/sevenUnitsGUI/MutablePredicate.java new file mode 100644 index 0000000..6cb8689 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/MutablePredicate.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2019 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 . + */ +package sevenUnitsGUI; + +import java.util.function.Predicate; + +/** + * A container for a predicate, which can be changed later. + * + * @author Adrien Hopkins + * @since 2019-04-13 + * @since v0.2.0 + */ +final class MutablePredicate implements Predicate { + /** + * The predicate stored in this {@code MutablePredicate} + * + * @since 2019-04-13 + * @since v0.2.0 + */ + private Predicate predicate; + + /** + * Creates the {@code MutablePredicate}. + * + * @since 2019-04-13 + * @since v0.2.0 + */ + public MutablePredicate(final Predicate predicate) { + this.predicate = predicate; + } + + /** + * @return predicate + * @since 2019-04-13 + * @since v0.2.0 + */ + public final Predicate getPredicate() { + return this.predicate; + } + + /** + * @param predicate + * new value of predicate + * @since 2019-04-13 + * @since v0.2.0 + */ + public final void setPredicate(final Predicate predicate) { + this.predicate = predicate; + } + + @Override + public boolean test(final T t) { + return this.predicate.test(t); + } +} diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 4373049..07671e4 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -16,12 +16,21 @@ */ package sevenUnitsGUI; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.List; +import java.util.Scanner; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; +import sevenUnits.ProgramInfo; +import sevenUnits.unit.BaseDimension; +import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; +import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; /** @@ -31,6 +40,62 @@ import sevenUnits.utils.UncertainDouble; * @since 2021-12-15 */ public final class Presenter { + /** + * @return text in About file + * @since 2022-02-19 + */ + static final String getAboutText() { + return Presenter.getLinesFromResource("/about.txt").stream() + .map(Presenter::withoutComments).collect(Collectors.joining("\n")) + .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION); + } + + /** + * Gets the text of a resource file as a set of strings (each one is one line + * of the text). + * + * @param filename filename to get resource from + * @return contents of file + * @since 2021-03-27 + */ + private static final List getLinesFromResource(String filename) { + final List lines = new ArrayList<>(); + + try (InputStream stream = inputStream(filename); + Scanner scanner = new Scanner(stream)) { + while (scanner.hasNextLine()) { + lines.add(scanner.nextLine()); + } + } catch (final IOException e) { + throw new AssertionError( + "Error occurred while loading file " + filename, e); + } + + return lines; + } + + /** + * Gets an input stream for a resource file. + * + * @param filepath file to use as resource + * @return obtained Path + * @since 2021-03-27 + */ + private static final InputStream inputStream(String filepath) { + return Presenter.class.getResourceAsStream(filepath); + } + + /** + * @return {@code line} with any comments removed. + * @since 2021-03-13 + */ + private static final String withoutComments(String line) { + final int index = line.indexOf('#'); + return index == -1 ? line : line.substring(index); + } + + // ====== SETTINGS ====== + /** * The view that this presenter communicates with */ @@ -41,8 +106,6 @@ public final class Presenter { */ private final UnitDatabase database; - // ====== SETTINGS ====== - /** * The rule used for parsing input numbers. Any number-string inputted into * this program will be parsed using this method. @@ -136,6 +199,8 @@ public final class Presenter { */ public void loadSettings() {} + void prefixSelected() {} + /** * Gets user settings from the view then saves them to the user's settings * file. @@ -143,4 +208,23 @@ public final class Presenter { * @since 2021-12-15 */ public void saveSettings() {} + + /** + * Returns true if and only if the unit represented by {@code unitName} has + * the dimension represented by {@code dimensionName}. + * + * @param unitName name of unit to test + * @param dimensionName name of dimension to test + * @return whether unit has dimenision + * @since 2019-04-13 + * @since v0.2.0 + */ + boolean unitMatchesDimension(String unitName, String dimensionName) { + final Unit unit = this.database.getUnit(unitName); + final ObjectProduct dimension = this.database + .getDimension(dimensionName); + return unit.getDimension().equals(dimension); + } + + void unitNameSelected() {} } diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java new file mode 100644 index 0000000..2b935d0 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -0,0 +1,331 @@ +/** + * Copyright (C) 2019 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 . + */ +package sevenUnitsGUI; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; + +/** + * @param type of element in list + * @author Adrien Hopkins + * @since 2019-04-13 + * @since v0.2.0 + */ +final class SearchBoxList extends JPanel { + + /** + * @since 2019-04-13 + * @since v0.2.0 + */ + private static final long serialVersionUID = 6226930279415983433L; + + /** + * The text to place in an empty search box. + * + * @since 2019-04-13 + * @since v0.2.0 + */ + private static final String EMPTY_TEXT = "Search..."; + + /** + * The color to use for an empty foreground. + * + * @since 2019-04-13 + * @since v0.2.0 + */ + private static final Color EMPTY_FOREGROUND = new Color(192, 192, 192); + + // the components + private final Collection itemsToFilter; + private final DelegateListModel listModel; + private final JTextField searchBox; + private final JList searchItems; + + private boolean searchBoxEmpty = true; + + // I need to do this because, for some reason, Swing is auto-focusing my + // search box without triggering a focus + // event. + private boolean searchBoxFocused = false; + + private Predicate customSearchFilter = o -> true; + private final Comparator defaultOrdering; + private final boolean caseSensitive; + + /** + * Creates an empty SearchBoxList + * + * @since 2022-02-19 + */ + public SearchBoxList() { + this(List.of(), null, false); + } + + /** + * Creates the {@code SearchBoxList}. + * + * @param itemsToFilter items to put in the list + * @since 2019-04-14 + */ + public SearchBoxList(final Collection itemsToFilter) { + this(itemsToFilter, null, false); + } + + /** + * 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 + */ + public SearchBoxList(final Collection itemsToFilter, + final Comparator defaultOrdering, final boolean caseSensitive) { + super(new BorderLayout(), true); + this.itemsToFilter = new ArrayList<>(itemsToFilter); + this.defaultOrdering = defaultOrdering; + this.caseSensitive = caseSensitive; + + // create the components + this.listModel = new DelegateListModel<>(new ArrayList<>(itemsToFilter)); + this.searchItems = new JList<>(this.listModel); + + this.searchBox = new JTextField(EMPTY_TEXT); + this.searchBox.setForeground(EMPTY_FOREGROUND); + + // add them to the panel + this.add(this.searchBox, BorderLayout.PAGE_START); + this.add(new JScrollPane(this.searchItems), BorderLayout.CENTER); + + // set up the search box + this.searchBox.addFocusListener(new FocusListener() { + @Override + public void focusGained(final FocusEvent e) { + SearchBoxList.this.searchBoxFocusGained(e); + } + + @Override + public void focusLost(final FocusEvent e) { + SearchBoxList.this.searchBoxFocusLost(e); + } + }); + + this.searchBox.addCaretListener(e -> this.searchBoxTextChanged()); + this.searchBoxEmpty = true; + } + + /** + * Adds an additional filter for searching. + * + * @param filter filter to add. + * @since 2019-04-13 + * @since v0.2.0 + */ + public void addSearchFilter(final Predicate filter) { + this.customSearchFilter = this.customSearchFilter.and(filter); + } + + /** + * Resets the search filter. + * + * @since 2019-04-13 + * @since v0.2.0 + */ + public void clearSearchFilters() { + this.customSearchFilter = o -> true; + } + + /** + * @return this component's search box component + * @since 2019-04-14 + * @since v0.2.0 + */ + public final JTextField getSearchBox() { + return this.searchBox; + } + + /** + * @param searchText text to search for + * @return a filter that filters out that text, based on this list's case + * sensitive setting + * @since 2019-04-14 + * @since v0.2.0 + */ + private Predicate getSearchFilter(final String searchText) { + if (this.caseSensitive) + return item -> item.toString().contains(searchText); + else + return item -> item.toString().toLowerCase() + .contains(searchText.toLowerCase()); + } + + /** + * @return this component's list component + * @since 2019-04-14 + * @since v0.2.0 + */ + public final JList getSearchList() { + return this.searchItems; + } + + /** + * @return index selected in item list, -1 if no selection + * @since 2019-04-14 + * @since v0.2.0 + */ + public int getSelectedIndex() { + return this.searchItems.getSelectedIndex(); + } + + /** + * @return value selected in item list + * @since 2019-04-13 + * @since v0.2.0 + */ + public Optional getSelectedValue() { + return Optional.ofNullable(this.searchItems.getSelectedValue()); + } + + /** + * Re-applies the filters. + * + * @since 2019-04-13 + * @since v0.2.0 + */ + public void reapplyFilter() { + final String searchText = this.searchBoxEmpty ? "" + : this.searchBox.getText(); + final FilterComparator comparator = new FilterComparator<>(searchText, + this.defaultOrdering, this.caseSensitive); + final Predicate searchFilter = this.getSearchFilter(searchText); + + this.listModel.clear(); + this.itemsToFilter.forEach(item -> { + if (searchFilter.test(item)) { + this.listModel.add(item); + } + }); + + // applies the custom filters + this.listModel.removeIf(this.customSearchFilter.negate()); + + // sorts the remaining items + this.listModel.sort(comparator); + } + + /** + * Runs whenever the search box gains focus. + * + * @param e focus event + * @since 2019-04-13 + * @since v0.2.0 + */ + private void searchBoxFocusGained(final FocusEvent e) { + this.searchBoxFocused = true; + if (this.searchBoxEmpty) { + this.searchBox.setText(""); + this.searchBox.setForeground(Color.BLACK); + } + } + + /** + * Runs whenever the search box loses focus. + * + * @param e focus event + * @since 2019-04-13 + * @since v0.2.0 + */ + private void searchBoxFocusLost(final FocusEvent e) { + this.searchBoxFocused = false; + if (this.searchBoxEmpty) { + this.searchBox.setText(EMPTY_TEXT); + this.searchBox.setForeground(EMPTY_FOREGROUND); + } + } + + /** + * Runs whenever the text in the search box is changed. + *

+ * Reapplies the search filter, and custom filters. + *

+ * + * @since 2019-04-14 + * @since v0.2.0 + */ + private void searchBoxTextChanged() { + if (this.searchBoxFocused) { + this.searchBoxEmpty = this.searchBox.getText().equals(""); + } + final String searchText = this.searchBoxEmpty ? "" + : this.searchBox.getText(); + final FilterComparator comparator = new FilterComparator<>(searchText, + this.defaultOrdering, this.caseSensitive); + final Predicate searchFilter = this.getSearchFilter(searchText); + + // initialize list with items that match the filter then sort + this.listModel.clear(); + this.itemsToFilter.forEach(string -> { + if (searchFilter.test(string)) { + this.listModel.add(string); + } + }); + + // applies the custom filters + this.listModel.removeIf(this.customSearchFilter.negate()); + + // sorts the remaining items + this.listModel.sort(comparator); + } + + /** + * Resets the search box list's contents to the provided items, removing any + * old items + * + * @param newItems new items to put in list + * @since 2021-05-22 + */ + public void setItems(Collection newItems) { + this.itemsToFilter.clear(); + this.itemsToFilter.addAll(newItems); + this.reapplyFilter(); + } + + /** + * Manually updates the search box's item list. + * + * @since 2020-08-27 + */ + public void updateList() { + this.searchBoxTextChanged(); + } +} diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java new file mode 100644 index 0000000..e92b661 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -0,0 +1,605 @@ +/** + * Copyright (C) 2022 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 . + */ +package sevenUnitsGUI; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.KeyEvent; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFormattedTextField; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; + +import sevenUnits.ProgramInfo; +import sevenUnits.unit.BaseDimension; +import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitPrefix; +import sevenUnits.utils.NamedObjectProduct; +import sevenUnits.utils.ObjectProduct; + +/** + * A View that separates its functions into multiple tabs + * + * @since 2022-02-19 + */ +final class TabbedView implements ExpressionConversionView, UnitConversionView { + /** + * A List-like view of a JComboBox's items + * + * @param type of item in list + * + * @since 2022-02-19 + */ + private static final class JComboBoxItemSet extends AbstractSet { + private final JComboBox comboBox; + + /** + * @param comboBox combo box to get items from + * @since 2022-02-19 + */ + public JComboBoxItemSet(JComboBox comboBox) { + this.comboBox = comboBox; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + private int index = 0; + + @Override + public boolean hasNext() { + return this.index < JComboBoxItemSet.this.size(); + } + + @Override + public E next() { + if (this.hasNext()) + return JComboBoxItemSet.this.comboBox.getItemAt(this.index++); + else + throw new NoSuchElementException( + "Iterator has finished iteration"); + } + }; + } + + @Override + public int size() { + return this.comboBox.getItemCount(); + } + + } + + private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat(); + + /** + * Creates a TabbedView. + * + * @param args command line arguments + * @since 2022-02-19 + */ + public static void main(String[] args) { + // This view doesn't need to do anything, the side effects of creating it + // are enough to start the program + @SuppressWarnings("unused") + final View view = new TabbedView(); + } + + /** The Presenter that handles this View */ + final Presenter presenter; + /** The frame that this view lives on */ + final JFrame frame; + /** The tabbed pane that contains all of the components */ + final JTabbedPane masterPane; + + // DIMENSION-BASED CONVERTER + /** The combo box that selects dimensions */ + private final JComboBox> dimensionSelector; + /** The panel for inputting values in the dimension-based converter */ + private final JTextField valueInput; + /** The panel for "From" in the dimension-based converter */ + private final SearchBoxList fromSearch; + /** The panel for "To" in the dimension-based converter */ + private final SearchBoxList toSearch; + /** The output area in the dimension-based converter */ + private final JTextArea unitOutput; + + // EXPRESSION-BASED CONVERTER + /** The "From" entry in the conversion panel */ + private final JTextField fromEntry; + /** The "To" entry in the conversion panel */ + private final JTextField toEntry; + /** The output area in the conversion panel */ + private final JTextArea expressionOutput; + + // UNIT AND PREFIX VIEWERS + /** The searchable list of unit names in the unit viewer */ + private final SearchBoxList unitNameList; + /** The searchable list of prefix names in the prefix viewer */ + private final SearchBoxList prefixNameList; + /** The text box for unit data in the unit viewer */ + private final JTextArea unitTextBox; + /** The text box for prefix data in the prefix viewer */ + private final JTextArea prefixTextBox; + + /** + * Creates the view and makes it visible to the user + * + * @since 2022-02-19 + */ + public TabbedView() { + // enable system look and feel + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException | InstantiationException + | IllegalAccessException | UnsupportedLookAndFeelException e) { + // oh well, just use default theme + System.err.println("Failed to enable system look-and-feel."); + e.printStackTrace(); + } + + // initialize important components + this.presenter = new Presenter(this); + this.frame = new JFrame("7Units " + ProgramInfo.VERSION); + this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + + // master components (those that contain everything else within them) + this.masterPane = new JTabbedPane(); + this.frame.add(this.masterPane); + + // ============ UNIT CONVERSION TAB ============ + final JPanel convertUnitPanel = new JPanel(); + this.masterPane.addTab("Convert Units", convertUnitPanel); + this.masterPane.setMnemonicAt(0, KeyEvent.VK_U); + convertUnitPanel.setLayout(new BorderLayout()); + + { // panel for input part + final JPanel inputPanel = new JPanel(); + convertUnitPanel.add(inputPanel, BorderLayout.CENTER); + inputPanel.setLayout(new GridLayout(1, 3)); + inputPanel.setBorder(new EmptyBorder(6, 6, 3, 6)); + + this.fromSearch = new SearchBoxList<>(); + inputPanel.add(this.fromSearch); + + final JPanel inBetweenPanel = new JPanel(); + inputPanel.add(inBetweenPanel); + inBetweenPanel.setLayout(new BorderLayout()); + + this.dimensionSelector = new JComboBox<>(); + inBetweenPanel.add(this.dimensionSelector, BorderLayout.PAGE_START); + this.dimensionSelector + .addItemListener(e -> this.presenter.applyDimensionFilter()); + + final JLabel arrowLabel = new JLabel("-->"); + inBetweenPanel.add(arrowLabel, BorderLayout.CENTER); + arrowLabel.setHorizontalAlignment(SwingConstants.CENTER); + + this.toSearch = new SearchBoxList<>(); + inputPanel.add(this.toSearch); + } + + { // panel for submit and output, and also value entry + final JPanel outputPanel = new JPanel(); + convertUnitPanel.add(outputPanel, BorderLayout.PAGE_END); + outputPanel.setLayout(new BorderLayout()); + outputPanel.setBorder(new EmptyBorder(3, 6, 6, 6)); + + final JLabel valuePrompt = new JLabel("Value to convert: "); + outputPanel.add(valuePrompt, BorderLayout.LINE_START); + + this.valueInput = new JFormattedTextField(NUMBER_FORMATTER); + outputPanel.add(this.valueInput, BorderLayout.CENTER); + + // conversion button + final JButton convertButton = new JButton("Convert"); + outputPanel.add(convertButton, BorderLayout.LINE_END); + convertButton.addActionListener(e -> this.presenter.convertUnits()); + convertButton.setMnemonic(KeyEvent.VK_ENTER); + + // conversion output + this.unitOutput = new JTextArea(2, 32); + outputPanel.add(this.unitOutput, BorderLayout.PAGE_END); + this.unitOutput.setEditable(false); + } + + // ============ EXPRESSION CONVERSION TAB ============ + final JPanel convertExpressionPanel = new JPanel(); + this.masterPane.addTab("Convert Unit Expressions", + convertExpressionPanel); + this.masterPane.setMnemonicAt(1, KeyEvent.VK_E); + convertExpressionPanel.setLayout(new GridLayout(4, 1)); + + // from and to expressions + this.fromEntry = new JTextField(); + convertExpressionPanel.add(this.fromEntry); + this.fromEntry.setBorder(BorderFactory.createTitledBorder("From")); + + this.toEntry = new JTextField(); + convertExpressionPanel.add(this.toEntry); + this.toEntry.setBorder(BorderFactory.createTitledBorder("To")); + + // button to convert + final JButton convertButton = new JButton("Convert"); + convertExpressionPanel.add(convertButton); + + convertButton.addActionListener(e -> this.presenter.convertExpressions()); + convertButton.setMnemonic(KeyEvent.VK_ENTER); + + // output of conversion + this.expressionOutput = new JTextArea(2, 32); + convertExpressionPanel.add(this.expressionOutput); + this.expressionOutput + .setBorder(BorderFactory.createTitledBorder("Output")); + this.expressionOutput.setEditable(false); + + // =========== UNIT VIEWER =========== + final JPanel unitLookupPanel = new JPanel(); + this.masterPane.addTab("Unit Viewer", unitLookupPanel); + this.masterPane.setMnemonicAt(2, KeyEvent.VK_V); + unitLookupPanel.setLayout(new GridLayout()); + + this.unitNameList = new SearchBoxList<>(); + unitLookupPanel.add(this.unitNameList); + this.unitNameList.getSearchList() + .addListSelectionListener(e -> this.presenter.unitNameSelected()); + + // the text box for unit's toString + this.unitTextBox = new JTextArea(); + unitLookupPanel.add(this.unitTextBox); + this.unitTextBox.setEditable(false); + this.unitTextBox.setLineWrap(true); + + // ============ PREFIX VIEWER ============= + final JPanel prefixLookupPanel = new JPanel(); + this.masterPane.addTab("Prefix Viewer", prefixLookupPanel); + this.masterPane.setMnemonicAt(3, KeyEvent.VK_P); + prefixLookupPanel.setLayout(new GridLayout(1, 2)); + + this.prefixNameList = new SearchBoxList<>(); + prefixLookupPanel.add(this.prefixNameList); + this.prefixNameList.getSearchList() + .addListSelectionListener(e -> this.presenter.prefixSelected()); + + // the text box for prefix's toString + this.prefixTextBox = new JTextArea(); + prefixLookupPanel.add(this.prefixTextBox); + this.prefixTextBox.setEditable(false); + this.prefixTextBox.setLineWrap(true); + + final JPanel infoPanel = new JPanel(); + this.masterPane.addTab("\uD83D\uDEC8", // info (i) character + new JScrollPane(infoPanel)); + + final JTextArea infoTextArea = new JTextArea(); + infoTextArea.setEditable(false); + infoTextArea.setOpaque(false); + infoPanel.add(infoTextArea); + infoTextArea.setText(Presenter.getAboutText()); + + // ============ SETTINGS PANEL ============ + this.masterPane.addTab("\u2699", + new JScrollPane(this.createSettingsPanel())); + this.masterPane.setMnemonicAt(5, KeyEvent.VK_S); + + // ============ FINALIZE CREATION OF VIEW ============ + this.frame.pack(); + this.frame.setVisible(true); + + } + + /** + * Creates and returns the settings panel (in its own function to make this + * code more organized, as this function is massive!) + * + * @since 2022-02-19 + */ + private JPanel createSettingsPanel() { + final JPanel settingsPanel = new JPanel(); + + settingsPanel + .setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS)); + + // ============ ROUNDING SETTINGS ============ + { + final JPanel roundingPanel = new JPanel(); + settingsPanel.add(roundingPanel); + roundingPanel.setBorder(new TitledBorder("Rounding Settings")); + roundingPanel.setLayout(new GridBagLayout()); + + // rounding rule selection + final ButtonGroup roundingRuleButtons = new ButtonGroup(); + + final JLabel roundingRuleLabel = new JLabel("Rounding Rule:"); + roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton fixedPrecision = new JRadioButton( + "Fixed Precision"); +// if (this.presenter.roundingType == RoundingType.SIGNIFICANT_DIGITS) { +// fixedPrecision.setSelected(true); +// } +// fixedPrecision.addActionListener(e -> this.presenter +// .setRoundingType(RoundingType.SIGNIFICANT_DIGITS)); + roundingRuleButtons.add(fixedPrecision); + roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton fixedDecimals = new JRadioButton( + "Fixed Decimal Places"); +// if (this.presenter.roundingType == RoundingType.DECIMAL_PLACES) { +// fixedDecimals.setSelected(true); +// } +// fixedDecimals.addActionListener(e -> this.presenter +// .setRoundingType(RoundingType.DECIMAL_PLACES)); + roundingRuleButtons.add(fixedDecimals); + roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton relativePrecision = new JRadioButton( + "Scientific Precision"); +// if (this.presenter.roundingType == RoundingType.SCIENTIFIC) { +// relativePrecision.setSelected(true); +// } +// relativePrecision.addActionListener( +// e -> this.presenter.setRoundingType(RoundingType.SCIENTIFIC)); + roundingRuleButtons.add(relativePrecision); + roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JLabel sliderLabel = new JLabel("Precision:"); + roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JSlider sigDigSlider = new JSlider(0, 12); + roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5) + .setAnchor(GridBagConstraints.LINE_START).build()); + + sigDigSlider.setMajorTickSpacing(4); + sigDigSlider.setMinorTickSpacing(1); + sigDigSlider.setSnapToTicks(true); + sigDigSlider.setPaintTicks(true); + sigDigSlider.setPaintLabels(true); +// sigDigSlider.setValue(this.presenter.precision); + +// sigDigSlider.addChangeListener( +// e -> this.presenter.setPrecision(sigDigSlider.getValue())); + } + + // ============ PREFIX REPETITION SETTINGS ============ + { + final JPanel prefixRepetitionPanel = new JPanel(); + settingsPanel.add(prefixRepetitionPanel); + prefixRepetitionPanel + .setBorder(new TitledBorder("Prefix Repetition Settings")); + prefixRepetitionPanel.setLayout(new GridBagLayout()); + + // prefix rules + final ButtonGroup prefixRuleButtons = new ButtonGroup(); + + final JRadioButton noRepetition = new JRadioButton("No Repetition"); +// if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) { +// noRepetition.setSelected(true); +// } +// noRepetition +// .addActionListener(e -> this.presenter.setPrefixRepetitionRule( +// DefaultPrefixRepetitionRule.NO_REPETITION)); + prefixRuleButtons.add(noRepetition); + prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton noRestriction = new JRadioButton("No Restriction"); +// if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) { +// noRestriction.setSelected(true); +// } +// noRestriction +// .addActionListener(e -> this.presenter.setPrefixRepetitionRule( +// DefaultPrefixRepetitionRule.NO_RESTRICTION)); + prefixRuleButtons.add(noRestriction); + prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton customRepetition = new JRadioButton( + "Complex Repetition"); +// if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) { +// customRepetition.setSelected(true); +// } +// customRepetition +// .addActionListener(e -> this.presenter.setPrefixRepetitionRule( +// DefaultPrefixRepetitionRule.COMPLEX_REPETITION)); + prefixRuleButtons.add(customRepetition); + prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2) + .setAnchor(GridBagConstraints.LINE_START).build()); + } + + // ============ SEARCH SETTINGS ============ + { + final JPanel searchingPanel = new JPanel(); + settingsPanel.add(searchingPanel); + searchingPanel.setBorder(new TitledBorder("Search Settings")); + searchingPanel.setLayout(new GridBagLayout()); + + // searching rules + final ButtonGroup searchRuleButtons = new ButtonGroup(); + + final JRadioButton noPrefixes = new JRadioButton( + "Never Include Prefixed Units"); + noPrefixes.setEnabled(false); + searchRuleButtons.add(noPrefixes); + searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton fixedPrefixes = new JRadioButton( + "Include Some Prefixes"); + fixedPrefixes.setEnabled(false); + searchRuleButtons.add(fixedPrefixes); + searchingPanel.add(fixedPrefixes, new GridBagBuilder(0, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton explicitPrefixes = new JRadioButton( + "Include Explicit Prefixes"); + explicitPrefixes.setEnabled(false); + searchRuleButtons.add(explicitPrefixes); + searchingPanel.add(explicitPrefixes, new GridBagBuilder(0, 2) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JRadioButton alwaysInclude = new JRadioButton( + "Include All Single Prefixes"); + alwaysInclude.setEnabled(false); + searchRuleButtons.add(alwaysInclude); + searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3) + .setAnchor(GridBagConstraints.LINE_START).build()); + } + + // ============ OTHER SETTINGS ============ + { + final JPanel miscPanel = new JPanel(); + settingsPanel.add(miscPanel); + miscPanel.setLayout(new GridBagLayout()); + + final JCheckBox oneWay = new JCheckBox("Convert One Way Only"); +// oneWay.setSelected(this.presenter.oneWay); +// oneWay.addItemListener( +// e -> this.presenter.setOneWay(e.getStateChange() == 1)); + miscPanel.add(oneWay, new GridBagBuilder(0, 0) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JCheckBox showAllVariations = new JCheckBox( + "Show Duplicates in \"Convert Units\""); +// showAllVariations.setSelected(this.presenter.includeDuplicateUnits); +// showAllVariations.addItemListener(e -> this.presenter +// .setIncludeDuplicateUnits(e.getStateChange() == 1)); + miscPanel.add(showAllVariations, new GridBagBuilder(0, 1) + .setAnchor(GridBagConstraints.LINE_START).build()); + + final JButton unitFileButton = new JButton("Manage Unit Data Files"); + unitFileButton.setEnabled(false); + miscPanel.add(unitFileButton, new GridBagBuilder(0, 2) + .setAnchor(GridBagConstraints.LINE_START).build()); + } + + return settingsPanel; + } + + @Override + public Set> getDimensions() { + return Collections + .unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector)); + } + + @Override + public String getFromExpression() { + return this.fromEntry.getText(); + } + + @Override + public Optional getFromSelection() { + return this.fromSearch.getSelectedValue(); + } + + @Override + public String getInputValue() { + return this.valueInput.getText(); + } + + @Override + public Optional> getSelectedDimension() { + // this must work because this function can only return items that are in + // the selector, which are all of type ObjectProduct + @SuppressWarnings("unchecked") + final ObjectProduct selectedItem = (ObjectProduct) this.dimensionSelector + .getSelectedItem(); + return Optional.ofNullable(selectedItem); + } + + @Override + public String getToExpression() { + return this.toEntry.getText(); + } + + @Override + public Optional getToSelection() { + return this.toSearch.getSelectedValue(); + } + + @Override + public void setDimensions( + Set> dimensions) { + this.dimensionSelector.removeAllItems(); + for (final NamedObjectProduct d : dimensions) { + this.dimensionSelector.addItem(d); + } + } + + @Override + public void setFromUnits(Set units) { + this.fromSearch.setItems(units); + } + + @Override + public void setToUnits(Set units) { + this.toSearch.setItems(units); + } + + @Override + public void showErrorMessage(String title, String message) { + JOptionPane.showMessageDialog(this.frame, message, title, + JOptionPane.ERROR_MESSAGE); + } + + @Override + public void showExpressionConversionOutput(String fromExpression, + String toExpression, double value) { + this.expressionOutput.setText( + String.format("%s = %s %s", fromExpression, value, toExpression)); + } + + @Override + public void showUnitConversionOutput(String outputString) { + this.unitOutput.setText(outputString); + } + +} diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index 97ec30f..5fd5a82 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -16,8 +16,8 @@ */ package sevenUnitsGUI; -import java.util.List; import java.util.Optional; +import java.util.Set; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; @@ -35,7 +35,7 @@ public interface UnitConversionView extends View { * @return dimensions available for filtering * @since 2022-01-29 */ - List> getDimensions(); + Set> getDimensions(); /** * @return unit to convert from @@ -68,7 +68,7 @@ public interface UnitConversionView extends View { * @param dimensions dimensions to use * @since 2021-12-15 */ - void setDimensions(List> dimensions); + void setDimensions(Set> dimensions); /** * Sets the available units to convert from. {@link #getFromSelection} is not @@ -78,7 +78,7 @@ public interface UnitConversionView extends View { * @param units units to convert from * @since 2021-12-15 */ - void setFromUnits(List units); + void setFromUnits(Set units); /** * Sets the available units to convert to. {@link #getToSelection} is not @@ -88,7 +88,7 @@ public interface UnitConversionView extends View { * @param units units to convert to * @since 2021-12-15 */ - void setToUnits(List units); + void setToUnits(Set units); /** * Shows the output of a unit conversion. diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index cc070e2..0c0d189 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; @@ -38,7 +39,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { private final Presenter presenter; /** The dimensions available to select from */ - private List> dimensions; + private Set> dimensions; /** The expression in the From field */ private String fromExpression; /** The expression in the To field */ @@ -54,9 +55,9 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { /** The currently selected dimension */ private Optional> selectedDimension; /** The units available in the From selection */ - private List fromUnits; + private Set fromUnits; /** The units available in the To selection */ - private List toUnits; + private Set toUnits; /** Saved output values of all unit conversions */ private List unitConversionOutputValues; @@ -74,7 +75,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @since 2022-01-29 */ @Override - public List> getDimensions() { + public Set> getDimensions() { return this.dimensions; } @@ -96,8 +97,8 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @return the units available for selection in From * @since 2022-01-29 */ - public List getFromUnits() { - return Collections.unmodifiableList(this.fromUnits); + public Set getFromUnits() { + return Collections.unmodifiableSet(this.fromUnits); } @Override @@ -132,8 +133,8 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @return the units available for selection in To * @since 2022-01-29 */ - public List getToUnits() { - return Collections.unmodifiableList(this.toUnits); + public Set getToUnits() { + return Collections.unmodifiableSet(this.toUnits); } /** @@ -146,7 +147,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { @Override public void setDimensions( - List> dimensions) { + Set> dimensions) { this.dimensions = Objects.requireNonNull(dimensions, "dimensions may not be null"); } @@ -181,7 +182,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public void setFromUnits(List units) { + public void setFromUnits(Set units) { this.fromUnits = Objects.requireNonNull(units, "units may not be null"); } @@ -233,7 +234,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public void setToUnits(List units) { + public void setToUnits(Set units) { this.toUnits = Objects.requireNonNull(units, "units may not be null"); } diff --git a/src/main/resources/about.txt b/src/main/resources/about.txt index f175396..7780db3 100644 --- a/src/main/resources/about.txt +++ b/src/main/resources/about.txt @@ -2,7 +2,7 @@ About 7Units v[VERSION] Copyright Notice: -Unit Converter Copyright (C) 2018-2021 Adrien Hopkins +Unit Converter Copyright (C) 2018-2022 Adrien Hopkins This program comes with ABSOLUTELY NO WARRANTY; for details read the LICENSE file, section 15 diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 675e3ab..3e7c2b5 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Optional; +import java.util.Set; import org.junit.jupiter.api.Test; @@ -36,9 +37,9 @@ import sevenUnits.utils.NamedObjectProduct; * @since 2022-02-10 */ public final class PresenterTest { - List testUnits = List.of(Metric.METRE, Metric.KILOMETRE, + Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE, Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR); - List> testDimensions = List.of( + Set> testDimensions = Set.of( Metric.Dimensions.LENGTH.withName(NameSymbol.ofName("Length")), Metric.Dimensions.VELOCITY.withName(NameSymbol.ofName("Velocity"))); @@ -56,12 +57,13 @@ public final class PresenterTest { viewBot.setFromUnits(this.testUnits); viewBot.setToUnits(this.testUnits); viewBot.setDimensions(this.testDimensions); - viewBot.setSelectedDimension(Optional.of(this.testDimensions.get(0))); + viewBot.setSelectedDimension( + Optional.of(this.testDimensions.iterator().next())); // filter to length units only, then get the filtered sets of units presenter.applyDimensionFilter(); - final List fromUnits = viewBot.getFromUnits(); - final List toUnits = viewBot.getToUnits(); + final Set fromUnits = viewBot.getFromUnits(); + final Set toUnits = viewBot.getToUnits(); // test that fromUnits/toUnits is [METRE, KILOMETRE] // HOWEVER I don't care about the order so I'm testing it this way -- cgit v1.2.3 From 07c86e02be29aa3d3d878adce62c5c0a9a458e47 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Feb 2022 09:53:24 -0500 Subject: Implemented unit conversion, with a few problems TabbedView now displays its units, but with their toString method which shows their definition in addition to their name --- CHANGELOG.org | 3 + .../sevenUnits/converterGUI/SevenUnitsGUI.java | 2 +- src/main/java/sevenUnits/unit/BaseDimension.java | 50 ++-- src/main/java/sevenUnits/unit/BaseUnit.java | 2 + src/main/java/sevenUnits/unit/BritishImperial.java | 2 + src/main/java/sevenUnits/unit/FunctionalUnit.java | 1 + .../java/sevenUnits/unit/FunctionalUnitlike.java | 1 + src/main/java/sevenUnits/unit/LinearUnit.java | 1 + src/main/java/sevenUnits/unit/Metric.java | 1 + src/main/java/sevenUnits/unit/MultiUnit.java | 1 + src/main/java/sevenUnits/unit/NameSymbol.java | 280 ------------------- src/main/java/sevenUnits/unit/Nameable.java | 59 ---- src/main/java/sevenUnits/unit/Unit.java | 2 + src/main/java/sevenUnits/unit/UnitDatabase.java | 23 +- src/main/java/sevenUnits/unit/UnitPrefix.java | 1 + src/main/java/sevenUnits/unit/UnitValue.java | 2 + src/main/java/sevenUnits/unit/Unitlike.java | 2 + src/main/java/sevenUnits/unit/UnitlikeValue.java | 2 + src/main/java/sevenUnits/utils/NameSymbol.java | 299 +++++++++++++++++++++ src/main/java/sevenUnits/utils/Nameable.java | 59 ++++ .../java/sevenUnits/utils/NamedObjectProduct.java | 7 +- src/main/java/sevenUnits/utils/ObjectProduct.java | 6 +- src/main/java/sevenUnitsGUI/Presenter.java | 202 +++++++++++++- src/main/java/sevenUnitsGUI/TabbedView.java | 14 +- .../java/sevenUnitsGUI/UnitConversionView.java | 3 +- src/main/java/sevenUnitsGUI/ViewBot.java | 7 +- .../java/sevenUnits/unit/UnitDatabaseTest.java | 1 + src/test/java/sevenUnits/unit/UnitTest.java | 1 + src/test/java/sevenUnitsGUI/PresenterTest.java | 11 +- 29 files changed, 636 insertions(+), 409 deletions(-) delete mode 100644 src/main/java/sevenUnits/unit/NameSymbol.java delete mode 100644 src/main/java/sevenUnits/unit/Nameable.java create mode 100644 src/main/java/sevenUnits/utils/NameSymbol.java create mode 100644 src/main/java/sevenUnits/utils/Nameable.java (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/CHANGELOG.org b/CHANGELOG.org index 2abee52..3bc46c2 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -4,8 +4,11 @@ *** Added - Added tests for the GUI - Added an object for the version numbers (SemanticVersionNumber) + - Added some toString methods to NameSymbol *** Changed - Rewrote the GUI code internally using an MVP model to make it easier to maintain and improve + - BaseDimension is now Nameable. As a consequence, its name and symbol return Optional instead of String, even though they will always succeed. + - The UnitDatabase's units and dimensions are now always named - Tweaked the look of the unit and expression conversion sections of the view ** v0.3.2 - [2021-12-02 Thu] *** Added diff --git a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java index 9c6ae0a..55e1546 100644 --- a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java +++ b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java @@ -70,12 +70,12 @@ import sevenUnits.unit.BritishImperial; import sevenUnits.unit.LinearUnit; import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.Metric; -import sevenUnits.unit.NameSymbol; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; import sevenUnits.unit.UnitValue; import sevenUnits.utils.ConditionalExistenceCollections; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index d5e98ca..bcd57d9 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -18,70 +18,58 @@ package sevenUnits.unit; import java.util.Objects; +import sevenUnits.utils.NameSymbol; +import sevenUnits.utils.Nameable; + /** * A dimension that defines a {@code BaseUnit} * * @author Adrien Hopkins * @since 2019-10-16 */ -public final class BaseDimension { +public final class BaseDimension implements Nameable { /** * Gets a {@code BaseDimension} with the provided name and symbol. * - * @param name - * name of dimension - * @param symbol - * symbol used for dimension + * @param name name of dimension + * @param symbol symbol used for dimension * @return dimension * @since 2019-10-16 */ public static BaseDimension valueOf(final String name, final String symbol) { return new BaseDimension(name, symbol); } - + /** * The name of the dimension. */ private final String name; /** - * The symbol used by the dimension. Symbols should be short, generally one or two characters. + * The symbol used by the dimension. Symbols should be short, generally one + * or two characters. */ private final String symbol; - + /** * Creates the {@code BaseDimension}. * - * @param name - * name of unit - * @param symbol - * symbol of unit - * @throws NullPointerException - * if any argument is null + * @param name name of unit + * @param symbol symbol of unit + * @throws NullPointerException if any argument is null * @since 2019-10-16 */ private BaseDimension(final String name, final String symbol) { this.name = Objects.requireNonNull(name, "name must not be null."); this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); } - - /** - * @return name - * @since 2019-10-16 - */ - public final String getName() { - return this.name; - } - - /** - * @return symbol - * @since 2019-10-16 - */ - public final String getSymbol() { - return this.symbol; + + @Override + public NameSymbol getNameSymbol() { + return NameSymbol.of(this.name, this.symbol); } - + @Override public String toString() { - return String.format("%s (%s)", this.getName(), this.getSymbol()); + return String.format("%s (%s)", this.name, this.symbol); } } diff --git a/src/main/java/sevenUnits/unit/BaseUnit.java b/src/main/java/sevenUnits/unit/BaseUnit.java index ee2c277..dba7f52 100644 --- a/src/main/java/sevenUnits/unit/BaseUnit.java +++ b/src/main/java/sevenUnits/unit/BaseUnit.java @@ -20,6 +20,8 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import sevenUnits.utils.NameSymbol; + /** * A unit that other units are defined by. *

diff --git a/src/main/java/sevenUnits/unit/BritishImperial.java b/src/main/java/sevenUnits/unit/BritishImperial.java index 743beeb..c6e65fb 100644 --- a/src/main/java/sevenUnits/unit/BritishImperial.java +++ b/src/main/java/sevenUnits/unit/BritishImperial.java @@ -16,6 +16,8 @@ */ package sevenUnits.unit; +import sevenUnits.utils.NameSymbol; + /** * A static utility class that contains units in the British Imperial system. * diff --git a/src/main/java/sevenUnits/unit/FunctionalUnit.java b/src/main/java/sevenUnits/unit/FunctionalUnit.java index df457e4..720b0af 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnit.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnit.java @@ -19,6 +19,7 @@ package sevenUnits.unit; import java.util.Objects; import java.util.function.DoubleUnaryOperator; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java index 2ee9e19..d6046c0 100644 --- a/src/main/java/sevenUnits/unit/FunctionalUnitlike.java +++ b/src/main/java/sevenUnits/unit/FunctionalUnitlike.java @@ -19,6 +19,7 @@ package sevenUnits.unit; import java.util.function.DoubleFunction; import java.util.function.ToDoubleFunction; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/LinearUnit.java b/src/main/java/sevenUnits/unit/LinearUnit.java index 25c2e2e..deefc9a 100644 --- a/src/main/java/sevenUnits/unit/LinearUnit.java +++ b/src/main/java/sevenUnits/unit/LinearUnit.java @@ -19,6 +19,7 @@ package sevenUnits.unit; import java.util.Objects; import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; diff --git a/src/main/java/sevenUnits/unit/Metric.java b/src/main/java/sevenUnits/unit/Metric.java index 3c4d291..78e3769 100644 --- a/src/main/java/sevenUnits/unit/Metric.java +++ b/src/main/java/sevenUnits/unit/Metric.java @@ -18,6 +18,7 @@ package sevenUnits.unit; import java.util.Set; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/MultiUnit.java b/src/main/java/sevenUnits/unit/MultiUnit.java index 83cdb03..bc240e3 100644 --- a/src/main/java/sevenUnits/unit/MultiUnit.java +++ b/src/main/java/sevenUnits/unit/MultiUnit.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/NameSymbol.java b/src/main/java/sevenUnits/unit/NameSymbol.java deleted file mode 100644 index 3e26138..0000000 --- a/src/main/java/sevenUnits/unit/NameSymbol.java +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Copyright (C) 2019 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 . - */ -package sevenUnits.unit; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -/** - * A class that can be used to specify names and a symbol for a unit. - * - * @author Adrien Hopkins - * @since 2019-10-21 - */ -public final class NameSymbol { - public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), - Optional.empty(), new HashSet<>()); - - /** - * Creates a {@code NameSymbol}, ensuring that if primaryName is null and - * otherNames is not empty, one name is moved from otherNames to primaryName - * - * Ensure that otherNames is a copy of the inputted argument. - */ - private static final NameSymbol create(final String name, - final String symbol, final Set otherNames) { - final Optional primaryName; - - if (name == null && !otherNames.isEmpty()) { - // get primary name and remove it from savedNames - final Iterator it = otherNames.iterator(); - assert it.hasNext(); - primaryName = Optional.of(it.next()); - otherNames.remove(primaryName.get()); - } else { - primaryName = Optional.ofNullable(name); - } - - return new NameSymbol(primaryName, Optional.ofNullable(symbol), - otherNames); - } - - /** - * Gets a {@code NameSymbol} with a primary name, a symbol and no other - * names. - * - * @param name name to use - * @param symbol symbol to use - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if name or symbol is null - */ - public static final NameSymbol of(final String name, final String symbol) { - return new NameSymbol(Optional.of(name), Optional.of(symbol), - new HashSet<>()); - } - - /** - * Gets a {@code NameSymbol} with a primary name, a symbol and additional - * names. - * - * @param name name to use - * @param symbol symbol to use - * @param otherNames other names to use - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if any argument is null - */ - public static final NameSymbol of(final String name, final String symbol, - final Set otherNames) { - return new NameSymbol(Optional.of(name), Optional.of(symbol), - new HashSet<>(Objects.requireNonNull(otherNames, - "otherNames must not be null."))); - } - - /** - * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional - * names. - * - * @param name name to use - * @param symbol symbol to use - * @param otherNames other names to use - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if any argument is null - */ - public static final NameSymbol of(final String name, final String symbol, - final String... otherNames) { - return new NameSymbol(Optional.of(name), Optional.of(symbol), - new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, - "otherNames must not be null.")))); - } - - /** - * Gets a {@code NameSymbol} with a primary name, no symbol, and no other - * names. - * - * @param name name to use - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if name is null - */ - public static final NameSymbol ofName(final String name) { - return new NameSymbol(Optional.of(name), Optional.empty(), - new HashSet<>()); - } - - /** - * Gets a {@code NameSymbol} with a primary name, a symbol and additional - * names. - *

- * If any argument is null, this static factory replaces it with an empty - * Optional or empty Set. - *

- * If {@code name} is null and {@code otherNames} is not empty, a primary - * name will be picked from {@code otherNames}. This name will not appear in - * getOtherNames(). - * - * @param name name to use - * @param symbol symbol to use - * @param otherNames other names to use - * @return NameSymbol instance - * @since 2019-11-26 - */ - public static final NameSymbol ofNullable(final String name, - final String symbol, final Set otherNames) { - return NameSymbol.create(name, symbol, - otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); - } - - /** - * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional - * names. - *

- * If any argument is null, this static factory replaces it with an empty - * Optional or empty Set. - *

- * If {@code name} is null and {@code otherNames} is not empty, a primary - * name will be picked from {@code otherNames}. This name will not appear in - * getOtherNames(). - * - * @param name name to use - * @param symbol symbol to use - * @param otherNames other names to use - * @return NameSymbol instance - * @since 2019-11-26 - */ - public static final NameSymbol ofNullable(final String name, - final String symbol, final String... otherNames) { - return create(name, symbol, otherNames == null ? new HashSet<>() - : new HashSet<>(Arrays.asList(otherNames))); - } - - /** - * Gets a {@code NameSymbol} with a symbol and no names. - * - * @param symbol symbol to use - * @return NameSymbol instance - * @since 2019-10-21 - * @throws NullPointerException if symbol is null - */ - public static final NameSymbol ofSymbol(final String symbol) { - return new NameSymbol(Optional.empty(), Optional.of(symbol), - new HashSet<>()); - } - - private final Optional primaryName; - private final Optional symbol; - - private final Set otherNames; - - /** - * Creates the {@code NameSymbol}. - * - * @param primaryName primary name of unit - * @param symbol symbol used to represent unit - * @param otherNames other names and/or spellings, should be a mutable copy - * of the argument - * @since 2019-10-21 - */ - private NameSymbol(final Optional primaryName, - final Optional symbol, final Set otherNames) { - this.primaryName = primaryName; - this.symbol = symbol; - otherNames.remove(null); - this.otherNames = Collections.unmodifiableSet(otherNames); - - if (this.primaryName.isEmpty()) { - assert this.otherNames.isEmpty(); - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof NameSymbol)) - return false; - final NameSymbol other = (NameSymbol) obj; - if (this.otherNames == null) { - if (other.otherNames != null) - return false; - } else if (!this.otherNames.equals(other.otherNames)) - return false; - if (this.primaryName == null) { - if (other.primaryName != null) - return false; - } else if (!this.primaryName.equals(other.primaryName)) - return false; - if (this.symbol == null) { - if (other.symbol != null) - return false; - } else if (!this.symbol.equals(other.symbol)) - return false; - return true; - } - - /** - * @return otherNames - * @since 2019-10-21 - */ - public final Set getOtherNames() { - return this.otherNames; - } - - /** - * @return primaryName - * @since 2019-10-21 - */ - public final Optional getPrimaryName() { - return this.primaryName; - } - - /** - * @return symbol - * @since 2019-10-21 - */ - public final Optional getSymbol() { - return this.symbol; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + (this.otherNames == null ? 0 : this.otherNames.hashCode()); - result = prime * result - + (this.primaryName == null ? 0 : this.primaryName.hashCode()); - result = prime * result - + (this.symbol == null ? 0 : this.symbol.hashCode()); - return result; - } - - /** - * @return true iff this {@code NameSymbol} contains no names or symbols. - */ - public final boolean isEmpty() { - // if primaryName is empty, otherNames must also be empty - return this.primaryName.isEmpty() && this.symbol.isEmpty(); - } -} \ No newline at end of file diff --git a/src/main/java/sevenUnits/unit/Nameable.java b/src/main/java/sevenUnits/unit/Nameable.java deleted file mode 100644 index ed23687..0000000 --- a/src/main/java/sevenUnits/unit/Nameable.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (C) 2020 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 . - */ -package sevenUnits.unit; - -import java.util.Optional; -import java.util.Set; - -/** - * An object that can hold one or more names, and possibly a symbol. The name - * and symbol data should be immutable. - * - * @since 2020-09-07 - */ -public interface Nameable { - /** - * @return a {@code NameSymbol} that contains this object's primary name, - * symbol and other names - * @since 2020-09-07 - */ - NameSymbol getNameSymbol(); - - /** - * @return set of alternate names - * @since 2020-09-07 - */ - default Set getOtherNames() { - return this.getNameSymbol().getOtherNames(); - } - - /** - * @return preferred name of object - * @since 2020-09-07 - */ - default Optional getPrimaryName() { - return this.getNameSymbol().getPrimaryName(); - } - - /** - * @return short symbol representing object - * @since 2020-09-07 - */ - default Optional getSymbol() { - return this.getNameSymbol().getSymbol(); - } -} diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index 005b6f7..9866e9c 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -22,6 +22,8 @@ import java.util.Objects; import java.util.function.DoubleUnaryOperator; import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.NameSymbol; +import sevenUnits.utils.Nameable; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index 18ac619..b029539 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -47,6 +47,8 @@ import java.util.regex.Pattern; import sevenUnits.utils.ConditionalExistenceCollections; import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.ExpressionParser; +import sevenUnits.utils.NameSymbol; +import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; @@ -1197,7 +1199,7 @@ public final class UnitDatabase { * @since 2019-03-14 * @since v0.2.0 */ - private final Map> dimensions; + private final Map> dimensions; /** * A map mapping strings to units (including prefixes) @@ -1313,9 +1315,16 @@ public final class UnitDatabase { */ public void addDimension(final String name, final ObjectProduct dimension) { - this.dimensions.put( - Objects.requireNonNull(name, "name must not be null."), - Objects.requireNonNull(dimension, "dimension must not be null.")); + Objects.requireNonNull(name, "name may not be null"); + Objects.requireNonNull(dimension, "dimension may not be null"); + if (dimension instanceof NamedObjectProduct) { + this.dimensions.put(name, + (NamedObjectProduct) dimension); + } else { + final NamedObjectProduct namedDimension = dimension + .withName(NameSymbol.ofName(name)); + this.dimensions.put(name, namedDimension); + } } /** @@ -1367,7 +1376,7 @@ public final class UnitDatabase { throw e; } - this.addDimension(name, dimension); + this.addDimension(name, dimension.withName(NameSymbol.ofName(name))); } } @@ -1463,7 +1472,7 @@ public final class UnitDatabase { throw e; } - this.addUnit(name, unit); + this.addUnit(name, unit.withName(NameSymbol.ofName(name))); } } } @@ -1510,7 +1519,7 @@ public final class UnitDatabase { * @since 2019-04-13 * @since v0.2.0 */ - public Map> dimensionMap() { + public Map> dimensionMap() { return Collections.unmodifiableMap(this.dimensions); } diff --git a/src/main/java/sevenUnits/unit/UnitPrefix.java b/src/main/java/sevenUnits/unit/UnitPrefix.java index 308f4b0..bf9d1fd 100644 --- a/src/main/java/sevenUnits/unit/UnitPrefix.java +++ b/src/main/java/sevenUnits/unit/UnitPrefix.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.Set; import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.NameSymbol; /** * A prefix that can be applied to a {@code LinearUnit} to multiply it by some value diff --git a/src/main/java/sevenUnits/unit/UnitValue.java b/src/main/java/sevenUnits/unit/UnitValue.java index f6d18f8..339263d 100644 --- a/src/main/java/sevenUnits/unit/UnitValue.java +++ b/src/main/java/sevenUnits/unit/UnitValue.java @@ -19,6 +19,8 @@ package sevenUnits.unit; import java.util.Objects; import java.util.Optional; +import sevenUnits.utils.NameSymbol; + /** * A value expressed in a unit. * diff --git a/src/main/java/sevenUnits/unit/Unitlike.java b/src/main/java/sevenUnits/unit/Unitlike.java index d2dcbbb..68de2c2 100644 --- a/src/main/java/sevenUnits/unit/Unitlike.java +++ b/src/main/java/sevenUnits/unit/Unitlike.java @@ -22,6 +22,8 @@ import java.util.Objects; import java.util.function.DoubleFunction; import java.util.function.ToDoubleFunction; +import sevenUnits.utils.NameSymbol; +import sevenUnits.utils.Nameable; import sevenUnits.utils.ObjectProduct; /** diff --git a/src/main/java/sevenUnits/unit/UnitlikeValue.java b/src/main/java/sevenUnits/unit/UnitlikeValue.java index edc13ca..26354b1 100644 --- a/src/main/java/sevenUnits/unit/UnitlikeValue.java +++ b/src/main/java/sevenUnits/unit/UnitlikeValue.java @@ -18,6 +18,8 @@ package sevenUnits.unit; import java.util.Optional; +import sevenUnits.utils.NameSymbol; + /** * * @since 2020-09-07 diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java new file mode 100644 index 0000000..aea274b --- /dev/null +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -0,0 +1,299 @@ +/** + * Copyright (C) 2019 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 . + */ +package sevenUnits.utils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A class that can be used to specify names and a symbol for a unit. + * + * @author Adrien Hopkins + * @since 2019-10-21 + */ +public final class NameSymbol { + public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), + Optional.empty(), new HashSet<>()); + + /** + * Creates a {@code NameSymbol}, ensuring that if primaryName is null and + * otherNames is not empty, one name is moved from otherNames to primaryName + * + * Ensure that otherNames is a copy of the inputted argument. + */ + private static final NameSymbol create(final String name, + final String symbol, final Set otherNames) { + final Optional primaryName; + + if (name == null && !otherNames.isEmpty()) { + // get primary name and remove it from savedNames + final Iterator it = otherNames.iterator(); + assert it.hasNext(); + primaryName = Optional.of(it.next()); + otherNames.remove(primaryName.get()); + } else { + primaryName = Optional.ofNullable(name); + } + + return new NameSymbol(primaryName, Optional.ofNullable(symbol), + otherNames); + } + + /** + * Gets a {@code NameSymbol} with a primary name, a symbol and no other + * names. + * + * @param name name to use + * @param symbol symbol to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if name or symbol is null + */ + public static final NameSymbol of(final String name, final String symbol) { + return new NameSymbol(Optional.of(name), Optional.of(symbol), + new HashSet<>()); + } + + /** + * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. + * + * @param name name to use + * @param symbol symbol to use + * @param otherNames other names to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if any argument is null + */ + public static final NameSymbol of(final String name, final String symbol, + final Set otherNames) { + return new NameSymbol(Optional.of(name), Optional.of(symbol), + new HashSet<>(Objects.requireNonNull(otherNames, + "otherNames must not be null."))); + } + + /** + * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. + * + * @param name name to use + * @param symbol symbol to use + * @param otherNames other names to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if any argument is null + */ + public static final NameSymbol of(final String name, final String symbol, + final String... otherNames) { + return new NameSymbol(Optional.of(name), Optional.of(symbol), + new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, + "otherNames must not be null.")))); + } + + /** + * Gets a {@code NameSymbol} with a primary name, no symbol, and no other + * names. + * + * @param name name to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if name is null + */ + public static final NameSymbol ofName(final String name) { + return new NameSymbol(Optional.of(name), Optional.empty(), + new HashSet<>()); + } + + /** + * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. + *

+ * If any argument is null, this static factory replaces it with an empty + * Optional or empty Set. + *

+ * If {@code name} is null and {@code otherNames} is not empty, a primary + * name will be picked from {@code otherNames}. This name will not appear in + * getOtherNames(). + * + * @param name name to use + * @param symbol symbol to use + * @param otherNames other names to use + * @return NameSymbol instance + * @since 2019-11-26 + */ + public static final NameSymbol ofNullable(final String name, + final String symbol, final Set otherNames) { + return NameSymbol.create(name, symbol, + otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); + } + + /** + * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional + * names. + *

+ * If any argument is null, this static factory replaces it with an empty + * Optional or empty Set. + *

+ * If {@code name} is null and {@code otherNames} is not empty, a primary + * name will be picked from {@code otherNames}. This name will not appear in + * getOtherNames(). + * + * @param name name to use + * @param symbol symbol to use + * @param otherNames other names to use + * @return NameSymbol instance + * @since 2019-11-26 + */ + public static final NameSymbol ofNullable(final String name, + final String symbol, final String... otherNames) { + return create(name, symbol, otherNames == null ? new HashSet<>() + : new HashSet<>(Arrays.asList(otherNames))); + } + + /** + * Gets a {@code NameSymbol} with a symbol and no names. + * + * @param symbol symbol to use + * @return NameSymbol instance + * @since 2019-10-21 + * @throws NullPointerException if symbol is null + */ + public static final NameSymbol ofSymbol(final String symbol) { + return new NameSymbol(Optional.empty(), Optional.of(symbol), + new HashSet<>()); + } + + private final Optional primaryName; + private final Optional symbol; + + private final Set otherNames; + + /** + * Creates the {@code NameSymbol}. + * + * @param primaryName primary name of unit + * @param symbol symbol used to represent unit + * @param otherNames other names and/or spellings, should be a mutable copy + * of the argument + * @since 2019-10-21 + */ + private NameSymbol(final Optional primaryName, + final Optional symbol, final Set otherNames) { + this.primaryName = primaryName; + this.symbol = symbol; + otherNames.remove(null); + this.otherNames = Collections.unmodifiableSet(otherNames); + + if (this.primaryName.isEmpty()) { + assert this.otherNames.isEmpty(); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof NameSymbol)) + return false; + final NameSymbol other = (NameSymbol) obj; + if (this.otherNames == null) { + if (other.otherNames != null) + return false; + } else if (!this.otherNames.equals(other.otherNames)) + return false; + if (this.primaryName == null) { + if (other.primaryName != null) + return false; + } else if (!this.primaryName.equals(other.primaryName)) + return false; + if (this.symbol == null) { + if (other.symbol != null) + return false; + } else if (!this.symbol.equals(other.symbol)) + return false; + return true; + } + + /** + * @return otherNames + * @since 2019-10-21 + */ + public final Set getOtherNames() { + return this.otherNames; + } + + /** + * @return primaryName + * @since 2019-10-21 + */ + public final Optional getPrimaryName() { + return this.primaryName; + } + + /** + * @return symbol + * @since 2019-10-21 + */ + public final Optional getSymbol() { + return this.symbol; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + (this.otherNames == null ? 0 : this.otherNames.hashCode()); + result = prime * result + + (this.primaryName == null ? 0 : this.primaryName.hashCode()); + result = prime * result + + (this.symbol == null ? 0 : this.symbol.hashCode()); + return result; + } + + /** + * @return true iff this {@code NameSymbol} contains no names or symbols. + */ + public final boolean isEmpty() { + // if primaryName is empty, otherNames must also be empty + return this.primaryName.isEmpty() && this.symbol.isEmpty(); + } + + /** + * @return a short version of this NameSymbol (defaults to symbol instead of + * primary name) + * @since 2022-02-26 + */ + public String shortName() { + return this.symbol.or(this::getPrimaryName).orElse("EMPTY"); + } + + @Override + public String toString() { + if (this.isEmpty()) + return "NameSymbol.EMPTY"; + else if (this.primaryName.isPresent() && this.symbol.isPresent()) + return this.primaryName + " (" + this.symbol + ")"; + else + return this.primaryName.orElseGet(this.symbol::orElseThrow); + } +} \ No newline at end of file diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java new file mode 100644 index 0000000..3cfc05a --- /dev/null +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2020 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 . + */ +package sevenUnits.utils; + +import java.util.Optional; +import java.util.Set; + +/** + * An object that can hold one or more names, and possibly a symbol. The name + * and symbol data should be immutable. + * + * @since 2020-09-07 + */ +public interface Nameable { + /** + * @return a {@code NameSymbol} that contains this object's primary name, + * symbol and other names + * @since 2020-09-07 + */ + NameSymbol getNameSymbol(); + + /** + * @return set of alternate names + * @since 2020-09-07 + */ + default Set getOtherNames() { + return this.getNameSymbol().getOtherNames(); + } + + /** + * @return preferred name of object + * @since 2020-09-07 + */ + default Optional getPrimaryName() { + return this.getNameSymbol().getPrimaryName(); + } + + /** + * @return short symbol representing object + * @since 2020-09-07 + */ + default Optional getSymbol() { + return this.getNameSymbol().getSymbol(); + } +} diff --git a/src/main/java/sevenUnits/utils/NamedObjectProduct.java b/src/main/java/sevenUnits/utils/NamedObjectProduct.java index 514f0b1..9c3079c 100644 --- a/src/main/java/sevenUnits/utils/NamedObjectProduct.java +++ b/src/main/java/sevenUnits/utils/NamedObjectProduct.java @@ -18,9 +18,6 @@ package sevenUnits.utils; import java.util.Map; -import sevenUnits.unit.NameSymbol; -import sevenUnits.unit.Nameable; - /** * An ObjectProduct with name(s) and/or a symbol. Can be created with the * {@link ObjectProduct#withName} method. @@ -43,4 +40,8 @@ public class NamedObjectProduct extends ObjectProduct return this.nameSymbol; } + @Override + public String toString() { + return this.nameSymbol.toString() + ", " + super.toString(); + } } diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index d4f88b9..926ce10 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -26,8 +26,6 @@ import java.util.Objects; import java.util.Set; import java.util.function.Function; -import sevenUnits.unit.NameSymbol; - /** * An immutable product of multiple objects of a type, such as base units. The * objects can be multiplied and exponentiated. @@ -246,7 +244,9 @@ public class ObjectProduct { */ @Override public String toString() { - return this.toString(Object::toString); + return this.toString(o -> o instanceof Nameable + ? ((Nameable) o).getNameSymbol().shortName() + : o.toString()); } /** diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 23a631d..be02364 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -19,17 +19,26 @@ package sevenUnitsGUI; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalDouble; import java.util.Scanner; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import sevenUnits.ProgramInfo; import sevenUnits.unit.BaseDimension; +import sevenUnits.unit.BritishImperial; +import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; import sevenUnits.unit.UnitPrefix; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.ObjectProduct; import sevenUnits.utils.UncertainDouble; @@ -40,6 +49,44 @@ import sevenUnits.utils.UncertainDouble; * @since 2021-12-15 */ public final class Presenter { + /** The default place where settings are stored. */ + private static final String DEFAULT_SETTINGS_FILEPATH = "settings.txt"; + /** The default place where units are stored. */ + private static final String DEFAULT_UNITS_FILEPATH = "/unitsfile.txt"; + /** The default place where dimensions are stored. */ + 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"; + + /** + * Adds default units and dimensions to a database. + * + * @param database database to add to + * @since 2019-04-14 + * @since v0.2.0 + */ + private static void addDefaults(final UnitDatabase database) { + database.addUnit("metre", Metric.METRE); + database.addUnit("kilogram", Metric.KILOGRAM); + database.addUnit("gram", Metric.KILOGRAM.dividedBy(1000)); + database.addUnit("second", Metric.SECOND); + database.addUnit("ampere", Metric.AMPERE); + database.addUnit("kelvin", Metric.KELVIN); + database.addUnit("mole", Metric.MOLE); + database.addUnit("candela", Metric.CANDELA); + database.addUnit("bit", Metric.BIT); + database.addUnit("unit", Metric.ONE); + // nonlinear units - must be loaded manually + database.addUnit("tempCelsius", Metric.CELSIUS); + database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT); + + // load initial dimensions + database.addDimension("LENGTH", Metric.Dimensions.LENGTH); + database.addDimension("MASS", Metric.Dimensions.MASS); + database.addDimension("TIME", Metric.Dimensions.TIME); + database.addDimension("TEMPERATURE", Metric.Dimensions.TEMPERATURE); + } + /** * @return text in About file * @since 2022-02-19 @@ -85,6 +132,25 @@ public final class Presenter { return Presenter.class.getResourceAsStream(filepath); } + /** + * Accepts a collection and returns a set with the unique elements in that + * collection + * + * @param type of element in collection + * @param collection collection to uniquify + * @return unique collection + * @since 2022-02-26 + */ + private static Set unique(Collection collection) { + final Set uniqueSet = new HashSet<>(); + for (final E e : collection) { + if (!uniqueSet.contains(e)) { + uniqueSet.add(e); + } + } + return uniqueSet; + } + /** * @return {@code line} with any comments removed. * @since 2021-03-13 @@ -126,6 +192,13 @@ public final class Presenter { */ private Predicate> prefixRepetitionRule; + /** + * The set of units that is considered neither metric nor nonmetric for the + * purposes of the metric-imperial one-way conversion. These units are + * included in both From and To, even if One Way Conversion is enabled. + */ + private final Set metricExceptions; + /** * 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 @@ -148,19 +221,42 @@ public final class Presenter { public Presenter(View view) { this.view = view; this.database = new UnitDatabase(); + addDefaults(this.database); + + // load units and prefixes + try (final InputStream units = inputStream(DEFAULT_UNITS_FILEPATH)) { + this.database.loadUnitsFromStream(units); + } catch (final IOException e) { + throw new AssertionError("Loading of unitsfile.txt failed.", e); + } + + // load dimensions + try (final InputStream dimensions = inputStream( + DEFAULT_DIMENSIONS_FILEPATH)) { + 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); + } } - /** - * Sets the dimension of the view's From and To units. - * - * @throws UnsupportedOperationException if the view does not support - * unit-based conversion (does not - * implement - * {@link UnitConversionView}) - * @since 2021-12-15 - */ - public void applyDimensionFilter() {} - /** * Gets settings from the view and applies them to both view and presenter. * @@ -190,7 +286,52 @@ public final class Presenter { * {@link UnitConversionView}) * @since 2021-12-15 */ - public void convertUnits() {} + public void convertUnits() { + if (this.view instanceof UnitConversionView) { + final UnitConversionView ucview = (UnitConversionView) this.view; + + final Optional fromUnitOptional = ucview.getFromSelection(); + final Optional toUnitOptional = ucview.getToSelection(); + final OptionalDouble valueOptional = ucview.getInputValue(); + + // ensure everything is obtained + final Unit fromUnit, toUnit; + final double value; + if (fromUnitOptional.isPresent()) { + fromUnit = fromUnitOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Conversion Error", + "Please specify a From unit"); + return; + } + if (toUnitOptional.isPresent()) { + toUnit = toUnitOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Conversion Error", + "Please specify a To unit"); + return; + } + if (valueOptional.isPresent()) { + value = valueOptional.orElseThrow(); + } else { + this.view.showErrorMessage("Unit Conversion Error", + "Please specify a valid value"); + return; + } + + if (!fromUnit.canConvertTo(toUnit)) + throw new AssertionError( + "From and To units incompatible (should be impossible)"); + + // convert! + final UnitValue initialValue = UnitValue.of(fromUnit, value); + final UnitValue converted = initialValue.convertTo(toUnit); + ucview.showUnitConversionOutput( + String.format("%s = %s", initialValue, converted)); + } else + throw new UnsupportedOperationException( + "This function can only be called when the view is a UnitConversionView."); + } /** * Loads settings from the user's settings file and applies them to the view. @@ -199,6 +340,21 @@ public final class Presenter { */ public void loadSettings() {} + /** + * Completes creation of the presenter. This part of the initialization + * depends on the view's functions, so it cannot be run if the components + * they depend on are not created yet. + * + * @since 2022-02-26 + */ + public void postViewInitialize() { + // unit conversion specific stuff + if (this.view instanceof UnitConversionView) { + final UnitConversionView ucview = (UnitConversionView) this.view; + ucview.setDimensions(unique(this.database.dimensionMap().values())); + } + } + void prefixSelected() {} /** @@ -227,4 +383,26 @@ public final class Presenter { } void unitNameSelected() {} + + /** + * Updates the view's From and To units, if it has some + * + * @since 2021-12-15 + */ + public void updateView() { + if (this.view instanceof UnitConversionView) { + final UnitConversionView ucview = (UnitConversionView) this.view; + final ObjectProduct viewDimension = ucview + .getSelectedDimension().orElseThrow(); + + final Set units = this.database + .unitMapPrefixless(this.showDuplicateUnits).entrySet().stream() + .map(Map.Entry::getValue) + .filter(u -> viewDimension.equals(u.getDimension())) + .collect(Collectors.toSet()); + + ucview.setFromUnits(units); + ucview.setToUnits(units); + } + } } diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index e92b661..c3a05e2 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.OptionalDouble; import java.util.Set; import javax.swing.BorderFactory; @@ -212,7 +213,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.dimensionSelector = new JComboBox<>(); inBetweenPanel.add(this.dimensionSelector, BorderLayout.PAGE_START); this.dimensionSelector - .addItemListener(e -> this.presenter.applyDimensionFilter()); + .addItemListener(e -> this.presenter.updateView()); final JLabel arrowLabel = new JLabel("-->"); inBetweenPanel.add(arrowLabel, BorderLayout.CENTER); @@ -326,9 +327,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.masterPane.setMnemonicAt(5, KeyEvent.VK_S); // ============ FINALIZE CREATION OF VIEW ============ + this.presenter.postViewInitialize(); this.frame.pack(); this.frame.setVisible(true); - } /** @@ -541,8 +542,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public String getInputValue() { - return this.valueInput.getText(); + public OptionalDouble getInputValue() { + final String text = this.valueInput.getText(); + try { + return OptionalDouble.of(Double.parseDouble(text)); + } catch (final NumberFormatException e) { + return OptionalDouble.empty(); + } } @Override diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index 5fd5a82..e411a44 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -17,6 +17,7 @@ package sevenUnitsGUI; import java.util.Optional; +import java.util.OptionalDouble; import java.util.Set; import sevenUnits.unit.BaseDimension; @@ -48,7 +49,7 @@ public interface UnitConversionView extends View { * string provided by the user) * @since 2021-12-15 */ - String getInputValue(); + OptionalDouble getInputValue(); /** * @return selected dimension diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 0c0d189..bc5302b 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.OptionalDouble; import java.util.Set; import sevenUnits.unit.BaseDimension; @@ -47,7 +48,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { /** * The user-provided string representing the value in {@code fromSelection} */ - private String inputValue; + private OptionalDouble inputValue; /** The unit selected in the From selection */ private Optional fromSelection; /** The unit selected in the To selection */ @@ -102,7 +103,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public String getInputValue() { + public OptionalDouble getInputValue() { return this.inputValue; } @@ -190,7 +191,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @param inputValue the inputValue to set * @since 2022-01-29 */ - public void setInputValue(String inputValue) { + public void setInputValue(OptionalDouble inputValue) { this.inputValue = inputValue; } diff --git a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java index 2276d7c..b8669cb 100644 --- a/src/test/java/sevenUnits/unit/UnitDatabaseTest.java +++ b/src/test/java/sevenUnits/unit/UnitDatabaseTest.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.UncertainDouble; /** diff --git a/src/test/java/sevenUnits/unit/UnitTest.java b/src/test/java/sevenUnits/unit/UnitTest.java index bb2e6a4..f174e7c 100644 --- a/src/test/java/sevenUnits/unit/UnitTest.java +++ b/src/test/java/sevenUnits/unit/UnitTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.Test; import sevenUnits.utils.DecimalComparison; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.UncertainDouble; /** diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 3e7c2b5..ff1450b 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -21,14 +21,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Optional; +import java.util.OptionalDouble; import java.util.Set; import org.junit.jupiter.api.Test; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Metric; -import sevenUnits.unit.NameSymbol; import sevenUnits.unit.Unit; +import sevenUnits.utils.NameSymbol; import sevenUnits.utils.NamedObjectProduct; /** @@ -44,12 +45,12 @@ public final class PresenterTest { Metric.Dimensions.VELOCITY.withName(NameSymbol.ofName("Velocity"))); /** - * Test for {@link Presenter#applyDimensionFilter()} + * Test for {@link Presenter#updateView()} * * @since 2022-02-12 */ @Test - void testApplyDimensionFilter() { + void testUpdateView() { // setup final ViewBot viewBot = new ViewBot(); final Presenter presenter = new Presenter(viewBot); @@ -61,7 +62,7 @@ public final class PresenterTest { Optional.of(this.testDimensions.iterator().next())); // filter to length units only, then get the filtered sets of units - presenter.applyDimensionFilter(); + presenter.updateView(); final Set fromUnits = viewBot.getFromUnits(); final Set toUnits = viewBot.getToUnits(); @@ -118,7 +119,7 @@ public final class PresenterTest { viewBot.setToUnits(this.testUnits); viewBot.setFromSelection(Optional.of(Metric.METRE)); viewBot.setToSelection(Optional.of(Metric.KILOMETRE)); - viewBot.setInputValue("10000.0"); + viewBot.setInputValue(OptionalDouble.of(10000.0)); // convert units presenter.convertUnits(); -- cgit v1.2.3 From 934213e08e85cc20bd994d0f39567426c21b89eb Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Feb 2022 11:15:04 -0500 Subject: Implemented expression conversion, tests now pass --- CHANGELOG.org | 2 +- src/main/java/sevenUnits/unit/UnitDatabase.java | 11 +++ src/main/java/sevenUnits/utils/NameSymbol.java | 9 -- src/main/java/sevenUnits/utils/Nameable.java | 20 ++++ src/main/java/sevenUnits/utils/ObjectProduct.java | 6 +- src/main/java/sevenUnitsGUI/Presenter.java | 77 ++++++++++----- src/main/java/sevenUnitsGUI/TabbedView.java | 15 +-- .../java/sevenUnitsGUI/UnitConversionView.java | 6 +- src/main/java/sevenUnitsGUI/ViewBot.java | 36 +++++-- src/test/java/sevenUnitsGUI/PresenterTest.java | 104 ++++++++++++--------- 10 files changed, 195 insertions(+), 91 deletions(-) (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/CHANGELOG.org b/CHANGELOG.org index 3bc46c2..681fead 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -4,7 +4,7 @@ *** Added - Added tests for the GUI - Added an object for the version numbers (SemanticVersionNumber) - - Added some toString methods to NameSymbol + - Added some toString methods to NameSymbol and Nameable *** Changed - Rewrote the GUI code internally using an MVP model to make it easier to maintain and improve - BaseDimension is now Nameable. As a consequence, its name and symbol return Optional instead of String, even though they will always succeed. diff --git a/src/main/java/sevenUnits/unit/UnitDatabase.java b/src/main/java/sevenUnits/unit/UnitDatabase.java index b029539..bf6ae64 100644 --- a/src/main/java/sevenUnits/unit/UnitDatabase.java +++ b/src/main/java/sevenUnits/unit/UnitDatabase.java @@ -1477,6 +1477,17 @@ public final class UnitDatabase { } } + /** + * Removes all units, prefixes and dimensions from this database. + * + * @since 2022-02-26 + */ + public void clear() { + this.dimensions.clear(); + this.prefixes.clear(); + this.prefixlessUnits.clear(); + } + /** * Tests if the database has a unit dimension with this name. * diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index aea274b..255e82f 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -278,15 +278,6 @@ public final class NameSymbol { return this.primaryName.isEmpty() && this.symbol.isEmpty(); } - /** - * @return a short version of this NameSymbol (defaults to symbol instead of - * primary name) - * @since 2022-02-26 - */ - public String shortName() { - return this.symbol.or(this::getPrimaryName).orElse("EMPTY"); - } - @Override public String toString() { if (this.isEmpty()) diff --git a/src/main/java/sevenUnits/utils/Nameable.java b/src/main/java/sevenUnits/utils/Nameable.java index 3cfc05a..e469d04 100644 --- a/src/main/java/sevenUnits/utils/Nameable.java +++ b/src/main/java/sevenUnits/utils/Nameable.java @@ -26,6 +26,16 @@ import java.util.Set; * @since 2020-09-07 */ public interface Nameable { + /** + * @return a name for the object - if there's a primary name, it's that, + * otherwise the symbol, otherwise "Unnamed" + * @since 2022-02-26 + */ + default String getName() { + final NameSymbol ns = this.getNameSymbol(); + return ns.getPrimaryName().or(ns::getSymbol).orElse("Unnamed"); + } + /** * @return a {@code NameSymbol} that contains this object's primary name, * symbol and other names @@ -49,6 +59,16 @@ public interface Nameable { return this.getNameSymbol().getPrimaryName(); } + /** + * @return a short name for the object - if there's a symbol, it's that, + * otherwise the symbol, otherwise "Unnamed" + * @since 2022-02-26 + */ + default String getShortName() { + final NameSymbol ns = this.getNameSymbol(); + return ns.getSymbol().or(ns::getPrimaryName).orElse("Unnamed"); + } + /** * @return short symbol representing object * @since 2020-09-07 diff --git a/src/main/java/sevenUnits/utils/ObjectProduct.java b/src/main/java/sevenUnits/utils/ObjectProduct.java index 926ce10..830f9d7 100644 --- a/src/main/java/sevenUnits/utils/ObjectProduct.java +++ b/src/main/java/sevenUnits/utils/ObjectProduct.java @@ -244,9 +244,9 @@ public class ObjectProduct { */ @Override public String toString() { - return this.toString(o -> o instanceof Nameable - ? ((Nameable) o).getNameSymbol().shortName() - : o.toString()); + return this + .toString(o -> o instanceof Nameable ? ((Nameable) o).getShortName() + : o.toString()); } /** diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index be02364..57d353d 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.OptionalDouble; import java.util.Scanner; @@ -34,6 +35,7 @@ import java.util.stream.Collectors; import sevenUnits.ProgramInfo; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.BritishImperial; +import sevenUnits.unit.LinearUnitValue; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitDatabase; @@ -170,7 +172,7 @@ public final class Presenter { /** * The database that this presenter communicates with (effectively the model) */ - private final UnitDatabase database; + final UnitDatabase database; /** * The rule used for parsing input numbers. Any number-string inputted into @@ -274,7 +276,58 @@ public final class Presenter { * {@link ExpressionConversionView}) * @since 2021-12-15 */ - public void convertExpressions() {} + public void convertExpressions() { + if (this.view instanceof ExpressionConversionView) { + final ExpressionConversionView xcview = (ExpressionConversionView) this.view; + + final String fromExpression = xcview.getFromExpression(); + final String toExpression = xcview.getToExpression(); + + // expressions must not be empty + if (fromExpression.isEmpty()) { + this.view.showErrorMessage("Parse Error", + "Please enter a unit expression in the From: box."); + return; + } + if (toExpression.isEmpty()) { + this.view.showErrorMessage("Parse Error", + "Please enter a unit expression in the To: box."); + return; + } + + // 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; + } + 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; + } + + // convert and show output + if (from.getUnit().canConvertTo(to)) { + final double value = from.asUnitValue().convertTo(to).getValue(); + xcview.showExpressionConversionOutput(fromExpression, toExpression, + value); + } else { + this.view.showErrorMessage("Conversion Error", + "Cannot convert between \"" + fromExpression + "\" and \"" + + toExpression + "\"."); + } + + } else + throw new UnsupportedOperationException( + "This function can only be called when the view is an ExpressionConversionView"); + } /** * Converts from the view's input unit to its output unit. Displays an error @@ -326,8 +379,7 @@ public final class Presenter { // convert! final UnitValue initialValue = UnitValue.of(fromUnit, value); final UnitValue converted = initialValue.convertTo(toUnit); - ucview.showUnitConversionOutput( - String.format("%s = %s", initialValue, converted)); + ucview.showUnitConversionOutput(initialValue, converted); } else throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); @@ -365,23 +417,6 @@ public final class Presenter { */ public void saveSettings() {} - /** - * Returns true if and only if the unit represented by {@code unitName} has - * the dimension represented by {@code dimensionName}. - * - * @param unitName name of unit to test - * @param dimensionName name of dimension to test - * @return whether unit has dimenision - * @since 2019-04-13 - * @since v0.2.0 - */ - boolean unitMatchesDimension(String unitName, String dimensionName) { - final Unit unit = this.database.getUnit(unitName); - final ObjectProduct dimension = this.database - .getDimension(dimensionName); - return unit.getDimension().equals(dimension); - } - void unitNameSelected() {} /** diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index c3a05e2..1d40087 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -23,6 +23,7 @@ import java.awt.GridLayout; import java.awt.event.KeyEvent; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.text.ParseException; import java.util.AbstractSet; import java.util.Collections; import java.util.Iterator; @@ -59,6 +60,7 @@ import sevenUnits.ProgramInfo; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitPrefix; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; @@ -140,7 +142,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { /** The combo box that selects dimensions */ private final JComboBox> dimensionSelector; /** The panel for inputting values in the dimension-based converter */ - private final JTextField valueInput; + private final JFormattedTextField valueInput; /** The panel for "From" in the dimension-based converter */ private final SearchBoxList fromSearch; /** The panel for "To" in the dimension-based converter */ @@ -543,12 +545,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { @Override public OptionalDouble getInputValue() { - final String text = this.valueInput.getText(); try { - return OptionalDouble.of(Double.parseDouble(text)); - } catch (final NumberFormatException e) { + this.valueInput.commitEdit(); + } catch (final ParseException e) { return OptionalDouble.empty(); } + return OptionalDouble + .of(((Number) this.valueInput.getValue()).doubleValue()); } @Override @@ -604,8 +607,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public void showUnitConversionOutput(String outputString) { - this.unitOutput.setText(outputString); + public void showUnitConversionOutput(UnitValue input, UnitValue output) { + this.unitOutput.setText(input + " = " + output); } } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index e411a44..67d3ddc 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -22,6 +22,7 @@ import java.util.Set; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; @@ -94,8 +95,9 @@ public interface UnitConversionView extends View { /** * Shows the output of a unit conversion. * - * @param outputString string that shows output of conversion + * @param input input unit & value (obtained from this view) + * @param output output unit & value * @since 2021-12-24 */ - void showUnitConversionOutput(String outputString); + void showUnitConversionOutput(UnitValue input, UnitValue output); } diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index bc5302b..43d73bb 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -16,6 +16,7 @@ */ package sevenUnitsGUI; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -25,6 +26,7 @@ import java.util.Set; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; @@ -59,8 +61,12 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { private Set fromUnits; /** The units available in the To selection */ private Set toUnits; + /** Saved input values of all unit conversions */ + private final List unitConversionInputValues; /** Saved output values of all unit conversions */ - private List unitConversionOutputValues; + private final List unitConversionOutputValues; + /** Saved outputs of all unit expressions */ + private final List expressionConversionOutputs; /** * Creates a new {@code ViewBot} with a new presenter. @@ -69,6 +75,10 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { */ public ViewBot() { this.presenter = new Presenter(this); + + this.unitConversionInputValues = new ArrayList<>(); + this.unitConversionOutputValues = new ArrayList<>(); + this.expressionConversionOutputs = new ArrayList<>(); } /** @@ -81,7 +91,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } public List getExpressionConversionOutputs() { - throw new UnsupportedOperationException("Not implemented yet"); + return this.expressionConversionOutputs; } @Override @@ -138,11 +148,19 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { return Collections.unmodifiableSet(this.toUnits); } + /** + * @return the unitConversionInputValues + * @since 2022-02-26 + */ + public List getUnitConversionInputValues() { + return this.unitConversionInputValues; + } + /** * @return the unitConversionOutputValues * @since 2022-02-10 */ - public List getUnitConversionOutputValues() { + public List getUnitConversionOutputValues() { return this.unitConversionOutputValues; } @@ -247,13 +265,17 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { @Override public void showExpressionConversionOutput(String fromExpression, String toExpression, double value) { - System.out.printf("Expression Conversion: %s = %d * (%s)%n", - fromExpression, value, toExpression); + final String output = String.format("%s = %s %s", fromExpression, value, + toExpression); + this.expressionConversionOutputs.add(output); + System.out.println("Expression Conversion: " + output); } @Override - public void showUnitConversionOutput(String outputString) { - System.out.println("Unit conversion: " + outputString); + public void showUnitConversionOutput(UnitValue input, UnitValue output) { + this.unitConversionInputValues.add(input); + this.unitConversionOutputValues.add(output); + System.out.println("Unit conversion: " + input + " = " + output); } @Override diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index ff1450b..deb16d7 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -19,16 +19,19 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.OptionalDouble; import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; +import sevenUnits.unit.UnitValue; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.NamedObjectProduct; @@ -38,50 +41,19 @@ import sevenUnits.utils.NamedObjectProduct; * @since 2022-02-10 */ public final class PresenterTest { + private static final List unitNames( + Collection units) { + return units.stream().map(Unit::getShortName) + .collect(Collectors.toList()); + } + Set testUnits = Set.of(Metric.METRE, Metric.KILOMETRE, Metric.METRE_PER_SECOND, Metric.KILOMETRE_PER_HOUR); + Set> testDimensions = Set.of( Metric.Dimensions.LENGTH.withName(NameSymbol.ofName("Length")), Metric.Dimensions.VELOCITY.withName(NameSymbol.ofName("Velocity"))); - /** - * Test for {@link Presenter#updateView()} - * - * @since 2022-02-12 - */ - @Test - void testUpdateView() { - // setup - final ViewBot viewBot = new ViewBot(); - final Presenter presenter = new Presenter(viewBot); - - viewBot.setFromUnits(this.testUnits); - viewBot.setToUnits(this.testUnits); - viewBot.setDimensions(this.testDimensions); - viewBot.setSelectedDimension( - Optional.of(this.testDimensions.iterator().next())); - - // filter to length units only, then get the filtered sets of units - presenter.updateView(); - final Set fromUnits = viewBot.getFromUnits(); - final Set toUnits = viewBot.getToUnits(); - - // test that fromUnits/toUnits is [METRE, KILOMETRE] - // HOWEVER I don't care about the order so I'm testing it this way - assertEquals(2, fromUnits.size(), - "Invalid fromUnits (length != 2): " + fromUnits); - assertEquals(2, toUnits.size(), - "Invalid toUnits (length != 2): " + toUnits); - assertTrue(fromUnits.contains(Metric.METRE), - "Invaild fromUnits (METRE missing): " + fromUnits); - assertTrue(toUnits.contains(Metric.METRE), - "Invaild toUnits (METRE missing): " + toUnits); - assertTrue(fromUnits.contains(Metric.KILOMETRE), - "Invaild fromUnits (KILOMETRE missing): " + fromUnits); - assertTrue(toUnits.contains(Metric.KILOMETRE), - "Invaild toUnits (KILOMETRE missing): " + toUnits); - } - /** * Test method for {@link Presenter#convertExpressions} * @@ -129,10 +101,58 @@ public final class PresenterTest { * here (that's for the backend tests), I'm just testing that it correctly * calls the unit conversion system */ - final String expected = String - .valueOf(Metric.METRE.convertTo(Metric.KILOMETRE, 10000.0)); + final UnitValue expectedInput = UnitValue.of(Metric.METRE, 10000.0); + final UnitValue expectedOutput = expectedInput + .convertTo(Metric.KILOMETRE); + + final List inputs = viewBot.getUnitConversionInputValues(); + final List outputs = viewBot.getUnitConversionOutputValues(); + assertEquals(expectedInput, inputs.get(inputs.size() - 1)); + assertEquals(expectedOutput, outputs.get(outputs.size() - 1)); + } + + /** + * Test for {@link Presenter#updateView()} + * + * @since 2022-02-12 + */ + @Test + void testUpdateView() { + // setup + final ViewBot viewBot = new ViewBot(); + final Presenter presenter = new Presenter(viewBot); + + // override default database units + presenter.database.clear(); + for (final Unit unit : this.testUnits) { + presenter.database.addUnit(unit.getPrimaryName().orElseThrow(), unit); + } + + // set from and to units + viewBot.setFromUnits(this.testUnits); + viewBot.setToUnits(this.testUnits); + viewBot.setDimensions(this.testDimensions); + viewBot.setSelectedDimension( + Optional.of(this.testDimensions.iterator().next())); + + // filter to length units only, then get the filtered sets of units + presenter.updateView(); + final Set fromUnits = viewBot.getFromUnits(); + final Set toUnits = viewBot.getToUnits(); - final List outputs = viewBot.getUnitConversionOutputValues(); - assertEquals(expected, outputs.get(outputs.size() - 1)); + // test that fromUnits/toUnits is [METRE, KILOMETRE] + // HOWEVER I don't care about the order so I'm testing it this way + assertEquals(2, fromUnits.size(), + "Invalid fromUnits (length != 2): " + unitNames(fromUnits)); + assertEquals(2, toUnits.size(), + "Invalid toUnits (length != 2): " + unitNames(toUnits)); + assertTrue(fromUnits.contains(Metric.METRE), + "Invaild fromUnits (METRE missing): " + unitNames(fromUnits)); + assertTrue(toUnits.contains(Metric.METRE), + "Invaild toUnits (METRE missing): " + unitNames(toUnits)); + assertTrue(fromUnits.contains(Metric.KILOMETRE), + "Invaild fromUnits (KILOMETRE missing): " + unitNames(fromUnits)); + assertTrue(toUnits.contains(Metric.KILOMETRE), + "Invaild toUnits (KILOMETRE missing): " + unitNames(toUnits)); } } -- cgit v1.2.3 From c421e474a7b0d0d453e4a527907f327f2ddef320 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 9 Apr 2022 11:32:43 -0500 Subject: View now sends and recieves Strings instead of data --- .../sevenUnitsGUI/ExpressionConversionView.java | 7 +- src/main/java/sevenUnitsGUI/Presenter.java | 153 +++++++++++------- src/main/java/sevenUnitsGUI/SearchBoxList.java | 10 ++ src/main/java/sevenUnitsGUI/TabbedView.java | 76 +++++---- .../java/sevenUnitsGUI/UnitConversionRecord.java | 180 +++++++++++++++++++++ .../java/sevenUnitsGUI/UnitConversionView.java | 47 +++--- src/main/java/sevenUnitsGUI/View.java | 2 +- src/main/java/sevenUnitsGUI/ViewBot.java | 141 +++++++--------- src/test/java/sevenUnitsGUI/PresenterTest.java | 85 +++++----- 9 files changed, 460 insertions(+), 241 deletions(-) create mode 100644 src/main/java/sevenUnitsGUI/UnitConversionRecord.java (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java index 5a587d4..872ca10 100644 --- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java +++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java @@ -38,11 +38,8 @@ public interface ExpressionConversionView extends View { /** * Shows the output of an expression conversion to the user. * - * @param fromExpression expression converted from - * @param toExpression expression converted to - * @param value conversion factor between two expressions + * @param uc unit conversion to show * @since 2021-12-15 */ - void showExpressionConversionOutput(String fromExpression, - String toExpression, double value); + void showExpressionConversionOutput(UnitConversionRecord uc); } diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 57d353d..9f8fe69 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Adrien Hopkins + * Copyright (C) 2021-2022 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,13 +19,11 @@ package sevenUnitsGUI; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; -import java.util.OptionalDouble; import java.util.Scanner; import java.util.Set; import java.util.function.Function; @@ -134,25 +132,6 @@ public final class Presenter { return Presenter.class.getResourceAsStream(filepath); } - /** - * Accepts a collection and returns a set with the unique elements in that - * collection - * - * @param type of element in collection - * @param collection collection to uniquify - * @return unique collection - * @since 2022-02-26 - */ - private static Set unique(Collection collection) { - final Set uniqueSet = new HashSet<>(); - for (final E e : collection) { - if (!uniqueSet.contains(e)) { - uniqueSet.add(e); - } - } - return uniqueSet; - } - /** * @return {@code line} with any comments removed. * @since 2021-03-13 @@ -206,7 +185,7 @@ public final class Presenter { * removed from the From unit list and imperial/USC units removed from the To * unit list. */ - private boolean oneWayConversion; + private boolean oneWayConversionEnabled; /** * If this is false, duplicate units will be removed from the unit view in @@ -259,13 +238,6 @@ public final class Presenter { } } - /** - * Gets settings from the view and applies them to both view and presenter. - * - * @since 2021-12-15 - */ - public void applySettings() {} - /** * Converts from the view's input expression to its output expression. * Displays an error message if any of the required fields are invalid. @@ -316,8 +288,9 @@ public final class Presenter { // convert and show output if (from.getUnit().canConvertTo(to)) { final double value = from.asUnitValue().convertTo(to).getValue(); - xcview.showExpressionConversionOutput(fromExpression, toExpression, - value); + final UnitConversionRecord uc = UnitConversionRecord.valueOf( + fromExpression, toExpression, "", String.valueOf(value)); + xcview.showExpressionConversionOutput(uc); } else { this.view.showErrorMessage("Conversion Error", "Cannot convert between \"" + fromExpression + "\" and \"" @@ -343,48 +316,81 @@ public final class Presenter { if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; - final Optional fromUnitOptional = ucview.getFromSelection(); - final Optional toUnitOptional = ucview.getToSelection(); - final OptionalDouble valueOptional = ucview.getInputValue(); + final Optional fromUnitOptional = ucview.getFromSelection(); + final Optional toUnitOptional = ucview.getToSelection(); + final String valueString = ucview.getInputValue(); - // ensure everything is obtained - final Unit fromUnit, toUnit; - final double value; + // extract values from optionals + final String fromUnitString, toUnitString; if (fromUnitOptional.isPresent()) { - fromUnit = fromUnitOptional.orElseThrow(); + fromUnitString = fromUnitOptional.orElseThrow(); } else { - this.view.showErrorMessage("Unit Conversion Error", + this.view.showErrorMessage("Unit Selection Error", "Please specify a From unit"); return; } if (toUnitOptional.isPresent()) { - toUnit = toUnitOptional.orElseThrow(); + toUnitString = toUnitOptional.orElseThrow(); } else { - this.view.showErrorMessage("Unit Conversion Error", + this.view.showErrorMessage("Unit Selection Error", "Please specify a To unit"); return; } - if (valueOptional.isPresent()) { - value = valueOptional.orElseThrow(); - } else { - this.view.showErrorMessage("Unit Conversion Error", - "Please specify a valid value"); + + // convert strings to data, checking if anything is invalid + final Unit fromUnit, toUnit; + final double value; + + 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 { + value = Double.parseDouble(valueString); + } catch (final NumberFormatException e) { + this.view.showErrorMessage("Value Error", + "Invalid value " + valueString); return; } if (!fromUnit.canConvertTo(toUnit)) - throw new AssertionError( - "From and To units incompatible (should be impossible)"); + throw this.viewError("Could not convert between %s and %s", + fromUnit, toUnit); // convert! final UnitValue initialValue = UnitValue.of(fromUnit, value); final UnitValue converted = initialValue.convertTo(toUnit); - ucview.showUnitConversionOutput(initialValue, converted); + + ucview.showUnitConversionOutput( + UnitConversionRecord.fromValues(initialValue, converted)); } else throw new UnsupportedOperationException( "This function can only be called when the view is a UnitConversionView."); } + /** + * @return true iff duplicate units are shown in unit lists + * @since 2022-03-30 + */ + public boolean duplicateUnitsShown() { + return this.showDuplicateUnits; + } + + /** + * @return true iff the One-Way Conversion feature is available (views that + * show units as a list will have metric units removed from the From + * unit list and imperial/USC units removed from the To unit list) + * + * @since 2022-03-30 + */ + public boolean isOneWayConversionEnabled() { + return this.oneWayConversionEnabled; + } + /** * Loads settings from the user's settings file and applies them to the view. * @@ -403,7 +409,7 @@ public final class Presenter { // unit conversion specific stuff if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; - ucview.setDimensions(unique(this.database.dimensionMap().values())); + ucview.setDimensionNames(this.database.dimensionMap().keySet()); } } @@ -417,6 +423,26 @@ public final class Presenter { */ public void saveSettings() {} + /** + * @param oneWayConversionEnabled whether not one-way conversion should be + * enabled + * @since 2022-03-30 + * @see {@link #isOneWayConversionEnabled} + */ + public void setOneWayConversionEnabled(boolean oneWayConversionEnabled) { + this.oneWayConversionEnabled = oneWayConversionEnabled; + this.updateView(); + } + + /** + * @param showDuplicateUnits whether or not duplicate units should be shown + * @since 2022-03-30 + */ + public void setShowDuplicateUnits(boolean showDuplicateUnits) { + this.showDuplicateUnits = showDuplicateUnits; + this.updateView(); + } + void unitNameSelected() {} /** @@ -427,17 +453,30 @@ public final class Presenter { public void updateView() { if (this.view instanceof UnitConversionView) { final UnitConversionView ucview = (UnitConversionView) this.view; - final ObjectProduct viewDimension = ucview - .getSelectedDimension().orElseThrow(); + final ObjectProduct viewDimension = this.database + .getDimension(((UnitConversionView) this.view) + .getSelectedDimensionName().orElseThrow()); - final Set units = this.database + final Set units = this.database .unitMapPrefixless(this.showDuplicateUnits).entrySet().stream() .map(Map.Entry::getValue) .filter(u -> viewDimension.equals(u.getDimension())) - .collect(Collectors.toSet()); + .map(Unit::getName).collect(Collectors.toSet()); - ucview.setFromUnits(units); - ucview.setToUnits(units); + ucview.setFromUnitNames(units); + ucview.setToUnitNames(units); } } + + /** + * @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 + */ + private AssertionError viewError(String message, Object... args) { + return new AssertionError("View Programming Error (from " + this.view + + "): " + String.format(message, args)); + } } diff --git a/src/main/java/sevenUnitsGUI/SearchBoxList.java b/src/main/java/sevenUnitsGUI/SearchBoxList.java index 2b935d0..9b41601 100644 --- a/src/main/java/sevenUnitsGUI/SearchBoxList.java +++ b/src/main/java/sevenUnitsGUI/SearchBoxList.java @@ -22,6 +22,7 @@ import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -165,6 +166,15 @@ final class SearchBoxList extends JPanel { this.customSearchFilter = o -> true; } + /** + * @return items available in search list, including items that are hidden by + * the search filter + * @since 2022-03-30 + */ + public Collection getItems() { + return Collections.unmodifiableCollection(this.itemsToFilter); + } + /** * @return this component's search box component * @since 2019-04-14 diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index 0461cb6..ed45011 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -23,13 +23,12 @@ import java.awt.GridLayout; import java.awt.event.KeyEvent; import java.text.DecimalFormat; import java.text.NumberFormat; -import java.text.ParseException; import java.util.AbstractSet; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Optional; -import java.util.OptionalDouble; import java.util.Set; import javax.swing.BorderFactory; @@ -57,12 +56,6 @@ import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; import sevenUnits.ProgramInfo; -import sevenUnits.unit.BaseDimension; -import sevenUnits.unit.Unit; -import sevenUnits.unit.UnitPrefix; -import sevenUnits.unit.UnitValue; -import sevenUnits.utils.NamedObjectProduct; -import sevenUnits.utils.ObjectProduct; /** * A View that separates its functions into multiple tabs @@ -140,13 +133,13 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // DIMENSION-BASED CONVERTER /** The combo box that selects dimensions */ - private final JComboBox> dimensionSelector; + private final JComboBox dimensionSelector; /** The panel for inputting values in the dimension-based converter */ private final JFormattedTextField valueInput; /** The panel for "From" in the dimension-based converter */ - private final SearchBoxList fromSearch; + private final SearchBoxList fromSearch; /** The panel for "To" in the dimension-based converter */ - private final SearchBoxList toSearch; + private final SearchBoxList toSearch; /** The output area in the dimension-based converter */ private final JTextArea unitOutput; @@ -160,9 +153,9 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { // UNIT AND PREFIX VIEWERS /** The searchable list of unit names in the unit viewer */ - private final SearchBoxList unitNameList; + private final SearchBoxList unitNameList; /** The searchable list of prefix names in the prefix viewer */ - private final SearchBoxList prefixNameList; + private final SearchBoxList prefixNameList; /** The text box for unit data in the unit viewer */ private final JTextArea unitTextBox; /** The text box for prefix data in the prefix viewer */ @@ -528,7 +521,7 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public Set> getDimensions() { + public Set getDimensionNames() { return Collections .unmodifiableSet(new JComboBoxItemSet<>(this.dimensionSelector)); } @@ -539,27 +532,25 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public Optional getFromSelection() { + public Optional getFromSelection() { return this.fromSearch.getSelectedValue(); } @Override - public OptionalDouble getInputValue() { - try { - this.valueInput.commitEdit(); - } catch (final ParseException e) { - return OptionalDouble.empty(); - } - return OptionalDouble - .of(((Number) this.valueInput.getValue()).doubleValue()); + public Set getFromUnitNames() { + // this should work because the only way I can mutate the item list is + // with setFromUnits which only accepts a Set + return new HashSet<>(this.fromSearch.getItems()); + } + + @Override + public String getInputValue() { + return this.valueInput.getText(); } @Override - public Optional> getSelectedDimension() { - // this must work because this function can only return items that are in - // the selector, which are all of type ObjectProduct - @SuppressWarnings("unchecked") - final ObjectProduct selectedItem = (ObjectProduct) this.dimensionSelector + public Optional getSelectedDimensionName() { + final String selectedItem = (String) this.dimensionSelector .getSelectedItem(); return Optional.ofNullable(selectedItem); } @@ -570,26 +561,32 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public Optional getToSelection() { + public Optional getToSelection() { return this.toSearch.getSelectedValue(); } @Override - public void setDimensions( - Set> dimensions) { + public Set getToUnitNames() { + // this should work because the only way I can mutate the item list is + // with setToUnits which only accepts a Set + return new HashSet<>(this.toSearch.getItems()); + } + + @Override + public void setDimensionNames(Set dimensionNames) { this.dimensionSelector.removeAllItems(); - for (final NamedObjectProduct d : dimensions) { + for (final String d : dimensionNames) { this.dimensionSelector.addItem(d); } } @Override - public void setFromUnits(Set units) { + public void setFromUnitNames(Set units) { this.fromSearch.setItems(units); } @Override - public void setToUnits(Set units) { + public void setToUnitNames(Set units) { this.toSearch.setItems(units); } @@ -600,15 +597,14 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { } @Override - public void showExpressionConversionOutput(String fromExpression, - String toExpression, double value) { - this.expressionOutput.setText( - String.format("%s = %s %s", fromExpression, value, toExpression)); + public void showExpressionConversionOutput(UnitConversionRecord uc) { + this.expressionOutput.setText(String.format("%s = %s %s", uc.fromName(), + uc.outputValueString(), uc.toName())); } @Override - public void showUnitConversionOutput(UnitValue input, UnitValue output) { - this.unitOutput.setText(input + " = " + output); + public void showUnitConversionOutput(UnitConversionRecord uc) { + this.unitOutput.setText(uc.toString()); } } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java new file mode 100644 index 0000000..60675e2 --- /dev/null +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -0,0 +1,180 @@ +/** + * Copyright (C) 2022 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 . + */ +package sevenUnitsGUI; + +import sevenUnits.unit.UnitValue; + +/** + * A record of a conversion between units or expressions + * + * @since 2022-04-09 + */ +public final class UnitConversionRecord { + /** + * Gets a {@code UnitConversionRecord} from two unit values + * + * @param input input unit & value + * @param output output unit & value + * @return unit conversion record + * @since 2022-04-09 + */ + public static UnitConversionRecord fromValues(UnitValue input, + UnitValue output) { + return UnitConversionRecord.valueOf(input.getUnit().getName(), + output.getUnit().getName(), String.valueOf(input.getValue()), + String.valueOf(output.getValue())); + } + + /** + * Gets a {@code UnitConversionRecord} + * + * @param fromName name of unit or expression that was converted + * from + * @param toName name of unit or expression that was converted to + * @param inputValueString string representing input value + * @param outputValueString string representing output value + * @return unit conversion record + * @since 2022-04-09 + */ + public static UnitConversionRecord valueOf(String fromName, String toName, + String inputValueString, String outputValueString) { + return new UnitConversionRecord(fromName, toName, inputValueString, + outputValueString); + } + + /** + * 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 + */ + private final String toName; + + /** + * A string representing the input value. It doesn't need to be the same as + * the input value's string representation; it could be rounded, for example. + */ + private final String inputValueString; + /** + * A string representing the input value. It doesn't need to be the same as + * the input value's string representation; it could be rounded, for example. + */ + private final String outputValueString; + + /** + * @param fromName name of unit or expression that was converted + * from + * @param toName name of unit or expression that was converted to + * @param inputValueString string representing input value + * @param outputValueString string representing output value + * @since 2022-04-09 + */ + private UnitConversionRecord(String fromName, String toName, + String inputValueString, String outputValueString) { + this.fromName = fromName; + this.toName = toName; + this.inputValueString = inputValueString; + this.outputValueString = outputValueString; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof UnitConversionRecord)) + return false; + final UnitConversionRecord other = (UnitConversionRecord) obj; + if (this.fromName == null) { + if (other.fromName != null) + return false; + } else if (!this.fromName.equals(other.fromName)) + return false; + if (this.inputValueString == null) { + if (other.inputValueString != null) + return false; + } else if (!this.inputValueString.equals(other.inputValueString)) + return false; + if (this.outputValueString == null) { + if (other.outputValueString != null) + return false; + } else if (!this.outputValueString.equals(other.outputValueString)) + return false; + if (this.toName == null) { + if (other.toName != null) + return false; + } else if (!this.toName.equals(other.toName)) + return false; + return true; + } + + /** + * @return name of unit or expression that was converted from + * @since 2022-04-09 + */ + public String fromName() { + return this.fromName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + (this.fromName == null ? 0 : this.fromName.hashCode()); + result = prime * result + (this.inputValueString == null ? 0 + : this.inputValueString.hashCode()); + result = prime * result + (this.outputValueString == null ? 0 + : this.outputValueString.hashCode()); + result = prime * result + + (this.toName == null ? 0 : this.toName.hashCode()); + return result; + } + + /** + * @return string representing input value + * @since 2022-04-09 + */ + public String inputValueString() { + return this.inputValueString; + } + + /** + * @return string representing output value + * @since 2022-04-09 + */ + public String outputValueString() { + return this.outputValueString; + } + + /** + * @return name of unit or expression that was converted to + * @since 2022-04-09 + */ + public String toName() { + return this.toName; + } + + @Override + public String toString() { + final String inputString = this.inputValueString.isBlank() ? this.fromName + : this.inputValueString + " " + this.fromName; + final String 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 67d3ddc..9d3a67b 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -17,15 +17,8 @@ package sevenUnitsGUI; import java.util.Optional; -import java.util.OptionalDouble; import java.util.Set; -import sevenUnits.unit.BaseDimension; -import sevenUnits.unit.Unit; -import sevenUnits.unit.UnitValue; -import sevenUnits.utils.NamedObjectProduct; -import sevenUnits.utils.ObjectProduct; - /** * A View that supports single unit-based conversion * @@ -37,60 +30,72 @@ public interface UnitConversionView extends View { * @return dimensions available for filtering * @since 2022-01-29 */ - Set> getDimensions(); + Set getDimensionNames(); /** - * @return unit to convert from + * @return name of unit to convert from * @since 2021-12-15 */ - Optional getFromSelection(); + Optional getFromSelection(); + + /** + * @return list of names of units available to convert from + * @since 2022-03-30 + */ + Set getFromUnitNames(); /** * @return value to convert between the units (specifically, the numeric * string provided by the user) * @since 2021-12-15 */ - OptionalDouble getInputValue(); + String getInputValue(); /** * @return selected dimension * @since 2021-12-15 */ - Optional> getSelectedDimension(); + Optional getSelectedDimensionName(); /** - * @return unit to convert to + * @return name of unit to convert to * @since 2021-12-15 */ - Optional getToSelection(); + Optional getToSelection(); + + /** + * @return list of names of units available to convert to + * @since 2022-03-30 + */ + Set getToUnitNames(); /** * Sets the available dimensions for filtering. * - * @param dimensions dimensions to use + * @param dimensionNames names of dimensions to use * @since 2021-12-15 */ - void setDimensions(Set> dimensions); + void setDimensionNames(Set dimensionNames); /** * Sets the available units to convert from. {@link #getFromSelection} is not * required to use one of these units; this method is to be used for views * that allow the user to select units from a list. * - * @param units units to convert from + * @param unitNames names of units to convert from * @since 2021-12-15 */ - void setFromUnits(Set units); + void setFromUnitNames(Set unitNames); /** * Sets the available units to convert to. {@link #getToSelection} is not * required to use one of these units; this method is to be used for views * that allow the user to select units from a list. * - * @param units units to convert to + * @param unitNames names of units to convert to * @since 2021-12-15 */ - void setToUnits(Set units); + void setToUnitNames(Set unitNames); /** * Shows the output of a unit conversion. @@ -99,5 +104,5 @@ public interface UnitConversionView extends View { * @param output output unit & value * @since 2021-12-24 */ - void showUnitConversionOutput(UnitValue input, UnitValue output); + void showUnitConversionOutput(UnitConversionRecord uc); } diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index a93c76a..e78c9cc 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Adrien Hopkins + * Copyright (C) 2021-2022 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 diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 43d73bb..0195dd6 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -21,15 +21,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.OptionalDouble; import java.util.Set; -import sevenUnits.unit.BaseDimension; -import sevenUnits.unit.Unit; -import sevenUnits.unit.UnitValue; -import sevenUnits.utils.NamedObjectProduct; -import sevenUnits.utils.ObjectProduct; - /** * A class that simulates a View (supports both unit and expression conversion) * for testing. Getters and setters work as expected. @@ -42,7 +35,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { private final Presenter presenter; /** The dimensions available to select from */ - private Set> dimensions; + private Set dimensionNames; /** The expression in the From field */ private String fromExpression; /** The expression in the To field */ @@ -50,23 +43,22 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { /** * The user-provided string representing the value in {@code fromSelection} */ - private OptionalDouble inputValue; + private String inputValue; /** The unit selected in the From selection */ - private Optional fromSelection; + private Optional fromSelection; /** The unit selected in the To selection */ - private Optional toSelection; + private Optional toSelection; /** The currently selected dimension */ - private Optional> selectedDimension; + private Optional selectedDimensionName; /** The units available in the From selection */ - private Set fromUnits; + private Set fromUnits; /** The units available in the To selection */ - private Set toUnits; - /** Saved input values of all unit conversions */ - private final List unitConversionInputValues; - /** Saved output values of all unit conversions */ - private final List unitConversionOutputValues; + private Set toUnits; + + /** Saved outputs of all unit conversions */ + private final List unitConversions; /** Saved outputs of all unit expressions */ - private final List expressionConversionOutputs; + private final List expressionConversions; /** * Creates a new {@code ViewBot} with a new presenter. @@ -76,9 +68,16 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { public ViewBot() { this.presenter = new Presenter(this); - this.unitConversionInputValues = new ArrayList<>(); - this.unitConversionOutputValues = new ArrayList<>(); - this.expressionConversionOutputs = new ArrayList<>(); + this.unitConversions = new ArrayList<>(); + this.expressionConversions = new ArrayList<>(); + } + + /** + * @return list of records of expression conversions done by this bot + * @since 2022-04-09 + */ + public List expressionConversionList() { + return this.expressionConversions; } /** @@ -86,12 +85,8 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @since 2022-01-29 */ @Override - public Set> getDimensions() { - return this.dimensions; - } - - public List getExpressionConversionOutputs() { - return this.expressionConversionOutputs; + public Set getDimensionNames() { + return this.dimensionNames; } @Override @@ -100,7 +95,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public Optional getFromSelection() { + public Optional getFromSelection() { return this.fromSelection; } @@ -108,12 +103,13 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @return the units available for selection in From * @since 2022-01-29 */ - public Set getFromUnits() { + @Override + public Set getFromUnitNames() { return Collections.unmodifiableSet(this.fromUnits); } @Override - public OptionalDouble getInputValue() { + public String getInputValue() { return this.inputValue; } @@ -126,8 +122,8 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public Optional> getSelectedDimension() { - return this.selectedDimension.map(x -> x); + public Optional getSelectedDimensionName() { + return this.selectedDimensionName; } @Override @@ -136,7 +132,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public Optional getToSelection() { + public Optional getToSelection() { return this.toSelection; } @@ -144,30 +140,14 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @return the units available for selection in To * @since 2022-01-29 */ - public Set getToUnits() { + @Override + public Set getToUnitNames() { return Collections.unmodifiableSet(this.toUnits); } - /** - * @return the unitConversionInputValues - * @since 2022-02-26 - */ - public List getUnitConversionInputValues() { - return this.unitConversionInputValues; - } - - /** - * @return the unitConversionOutputValues - * @since 2022-02-10 - */ - public List getUnitConversionOutputValues() { - return this.unitConversionOutputValues; - } - @Override - public void setDimensions( - Set> dimensions) { - this.dimensions = Objects.requireNonNull(dimensions, + public void setDimensionNames(Set dimensionNames) { + this.dimensionNames = Objects.requireNonNull(dimensionNames, "dimensions may not be null"); } @@ -187,7 +167,7 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @param fromSelection the fromSelection to set * @since 2022-01-29 */ - public void setFromSelection(Optional fromSelection) { + public void setFromSelection(Optional fromSelection) { this.fromSelection = Objects.requireNonNull(fromSelection, "fromSelection cannot be null"); } @@ -196,12 +176,12 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @param fromSelection the fromSelection to set * @since 2022-02-10 */ - public void setFromSelection(Unit fromSelection) { + public void setFromSelection(String fromSelection) { this.setFromSelection(Optional.of(fromSelection)); } @Override - public void setFromUnits(Set units) { + public void setFromUnitNames(Set units) { this.fromUnits = Objects.requireNonNull(units, "units may not be null"); } @@ -209,22 +189,21 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @param inputValue the inputValue to set * @since 2022-01-29 */ - public void setInputValue(OptionalDouble inputValue) { + public void setInputValue(String inputValue) { this.inputValue = inputValue; } - public void setSelectedDimension( - ObjectProduct selectedDimension) { - this.setSelectedDimension(Optional.of(selectedDimension)); - } - /** * @param selectedDimension the selectedDimension to set * @since 2022-01-29 */ - public void setSelectedDimension( - Optional> selectedDimension) { - this.selectedDimension = selectedDimension; + public void setSelectedDimensionName( + Optional selectedDimensionName) { + this.selectedDimensionName = selectedDimensionName; + } + + public void setSelectedDimensionName(String selectedDimensionName) { + this.setSelectedDimensionName(Optional.of(selectedDimensionName)); } /** @@ -243,17 +222,17 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { * @param toSelection the toSelection to set * @since 2022-01-29 */ - public void setToSelection(Optional toSelection) { + public void setToSelection(Optional toSelection) { this.toSelection = Objects.requireNonNull(toSelection, "toSelection cannot be null."); } - public void setToSelection(Unit toSelection) { + public void setToSelection(String toSelection) { this.setToSelection(Optional.of(toSelection)); } @Override - public void setToUnits(Set units) { + public void setToUnitNames(Set units) { this.toUnits = Objects.requireNonNull(units, "units may not be null"); } @@ -263,19 +242,15 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { } @Override - public void showExpressionConversionOutput(String fromExpression, - String toExpression, double value) { - final String output = String.format("%s = %s %s", fromExpression, value, - toExpression); - this.expressionConversionOutputs.add(output); - System.out.println("Expression Conversion: " + output); + public void showExpressionConversionOutput(UnitConversionRecord uc) { + this.expressionConversions.add(uc); + System.out.println("Expression Conversion: " + uc); } @Override - public void showUnitConversionOutput(UnitValue input, UnitValue output) { - this.unitConversionInputValues.add(input); - this.unitConversionOutputValues.add(output); - System.out.println("Unit conversion: " + input + " = " + output); + public void showUnitConversionOutput(UnitConversionRecord uc) { + this.unitConversions.add(uc); + System.out.println("Unit Conversion: " + uc); } @Override @@ -283,4 +258,12 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { return super.toString() + String.format("[presenter=%s]", this.presenter); } + /** + * @return list of records of every unit conversion made by this bot + * @since 2022-04-09 + */ + public List unitConversionList() { + return Collections.unmodifiableList(this.unitConversions); + } + } diff --git a/src/test/java/sevenUnitsGUI/PresenterTest.java b/src/test/java/sevenUnitsGUI/PresenterTest.java index 82842d8..dc2fb57 100644 --- a/src/test/java/sevenUnitsGUI/PresenterTest.java +++ b/src/test/java/sevenUnitsGUI/PresenterTest.java @@ -17,12 +17,9 @@ package sevenUnitsGUI; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.util.Collection; import java.util.List; -import java.util.Optional; -import java.util.OptionalDouble; import java.util.Set; import java.util.stream.Collectors; @@ -32,6 +29,7 @@ import sevenUnits.unit.BaseDimension; import sevenUnits.unit.Metric; import sevenUnits.unit.Unit; import sevenUnits.unit.UnitValue; +import sevenUnits.utils.Nameable; import sevenUnits.utils.NamedObjectProduct; /** @@ -46,10 +44,8 @@ public final class PresenterTest { static final Set> testDimensions = Set .of(Metric.Dimensions.LENGTH, Metric.Dimensions.VELOCITY); - private static final List unitNames( - Collection units) { - return units.stream().map(Unit::getShortName) - .collect(Collectors.toList()); + private static final Set names(Set units) { + return units.stream().map(Nameable::getName).collect(Collectors.toSet()); } /** @@ -70,8 +66,10 @@ public final class PresenterTest { presenter.convertExpressions(); // test result - final List outputs = viewBot.getExpressionConversionOutputs(); - assertEquals("10000.0 m = 10.0 km", outputs.get(outputs.size() - 1)); + final List outputs = viewBot + .expressionConversionList(); + assertEquals("10000.0 m = 10.0 km", + outputs.get(outputs.size() - 1).toString()); } /** @@ -85,11 +83,11 @@ public final class PresenterTest { final ViewBot viewBot = new ViewBot(); final Presenter presenter = new Presenter(viewBot); - viewBot.setFromUnits(testUnits); - viewBot.setToUnits(testUnits); - viewBot.setFromSelection(Optional.of(Metric.METRE)); - viewBot.setToSelection(Optional.of(Metric.KILOMETRE)); - viewBot.setInputValue(OptionalDouble.of(10000.0)); + viewBot.setFromUnitNames(names(testUnits)); + viewBot.setToUnitNames(names(testUnits)); + viewBot.setFromSelection("metre"); + viewBot.setToSelection("kilometre"); + viewBot.setInputValue("10000.0"); // convert units presenter.convertUnits(); @@ -102,11 +100,29 @@ public final class PresenterTest { final UnitValue expectedInput = UnitValue.of(Metric.METRE, 10000.0); final UnitValue expectedOutput = expectedInput .convertTo(Metric.KILOMETRE); + final UnitConversionRecord expectedUC = UnitConversionRecord + .fromValues(expectedInput, expectedOutput); - final List inputs = viewBot.getUnitConversionInputValues(); - final List outputs = viewBot.getUnitConversionOutputValues(); - assertEquals(expectedInput, inputs.get(inputs.size() - 1)); - assertEquals(expectedOutput, outputs.get(outputs.size() - 1)); + assertEquals(List.of(expectedUC), viewBot.unitConversionList()); + } + + @Test + void testDuplicateUnits() { + assumeTrue(false, "Not yet implemented"); + /* + * enable and disable duplicate units and check for those in From and To, + * include duplicate units in the input set + */ + } + + @Test + void testOneWayConversion() { + assumeTrue(false, "Not yet implemented"); + /* + * enable and disable one-way conversion, testing the units in From and To + * on each setting to ensure they match the rule. Include at least one + * metric exception. + */ } /** @@ -125,31 +141,24 @@ public final class PresenterTest { for (final Unit unit : testUnits) { presenter.database.addUnit(unit.getPrimaryName().orElseThrow(), unit); } + for (final var dimension : testDimensions) { + presenter.database.addDimension( + dimension.getPrimaryName().orElseThrow(), dimension); + } // set from and to units - viewBot.setFromUnits(testUnits); - viewBot.setToUnits(testUnits); - viewBot.setDimensions(testDimensions); - viewBot.setSelectedDimension(Optional.of(Metric.Dimensions.LENGTH)); + viewBot.setFromUnitNames(names(testUnits)); + viewBot.setToUnitNames(names(testUnits)); + viewBot.setDimensionNames(names(testDimensions)); + viewBot.setSelectedDimensionName(Metric.Dimensions.LENGTH.getName()); // filter to length units only, then get the filtered sets of units presenter.updateView(); - final Set fromUnits = viewBot.getFromUnits(); - final Set toUnits = viewBot.getToUnits(); + final Set fromUnits = viewBot.getFromUnitNames(); + final Set toUnits = viewBot.getToUnitNames(); // test that fromUnits/toUnits is [METRE, KILOMETRE] - // HOWEVER I don't care about the order so I'm testing it this way - assertEquals(2, fromUnits.size(), - "Invalid fromUnits (length != 2): " + unitNames(fromUnits)); - assertEquals(2, toUnits.size(), - "Invalid toUnits (length != 2): " + unitNames(toUnits)); - assertTrue(fromUnits.contains(Metric.METRE), - "Invaild fromUnits (METRE missing): " + unitNames(fromUnits)); - assertTrue(toUnits.contains(Metric.METRE), - "Invaild toUnits (METRE missing): " + unitNames(toUnits)); - assertTrue(fromUnits.contains(Metric.KILOMETRE), - "Invaild fromUnits (KILOMETRE missing): " + unitNames(fromUnits)); - assertTrue(toUnits.contains(Metric.KILOMETRE), - "Invaild toUnits (KILOMETRE missing): " + unitNames(toUnits)); + assertEquals(Set.of("metre", "kilometre"), fromUnits); + assertEquals(Set.of("metre", "kilometre"), toUnits); } } -- cgit v1.2.3 From b1affe92460637211f560d70ee5c8770f3952822 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 10 Apr 2022 14:22:29 -0500 Subject: Created API for settings and unit/prefix viewing --- src/main/java/sevenUnits/unit/Unit.java | 14 +++-- src/main/java/sevenUnits/unit/UnitType.java | 34 ++++++++++++ src/main/java/sevenUnitsGUI/Presenter.java | 60 +++++++++++++++++++--- src/main/java/sevenUnitsGUI/TabbedView.java | 36 ++++++++++++- .../java/sevenUnitsGUI/UnitConversionView.java | 2 +- src/main/java/sevenUnitsGUI/View.java | 56 ++++++++++++++++++++ src/main/java/sevenUnitsGUI/ViewBot.java | 35 ++++++++++++- 7 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 src/main/java/sevenUnits/unit/UnitType.java (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/src/main/java/sevenUnits/unit/Unit.java b/src/main/java/sevenUnits/unit/Unit.java index b80ccbd..826b59b 100644 --- a/src/main/java/sevenUnits/unit/Unit.java +++ b/src/main/java/sevenUnits/unit/Unit.java @@ -24,7 +24,6 @@ import java.util.function.DoubleUnaryOperator; import sevenUnits.utils.DecimalComparison; import sevenUnits.utils.NameSymbol; import sevenUnits.utils.Nameable; -import sevenUnits.utils.NamedObjectProduct; import sevenUnits.utils.ObjectProduct; /** @@ -191,7 +190,7 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in base unit * @return value expressed in this unit * @since 2018-12-22 @@ -207,7 +206,7 @@ public abstract class Unit implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unit to convert to * @param value value to convert * @return converted value @@ -234,7 +233,7 @@ public abstract class Unit implements Nameable { * {@code other.convertFromBase(this.convertToBase(value))}. * Therefore, overriding either of those methods will change the * output of this method. - * + * * @param other unitlike form to convert to * @param value value to convert * @param type of value to convert to @@ -269,7 +268,7 @@ public abstract class Unit implements Nameable { * * @implSpec This method is used by {@link #convertTo}, and its behaviour * affects the behaviour of {@code convertTo}. - * + * * @param value value expressed in this unit * @return value expressed in base unit * @since 2018-12-22 @@ -355,9 +354,8 @@ public abstract class Unit implements Nameable { * @since 2022-03-10 */ public String toDefinitionString() { - if (this.unitBase instanceof NamedObjectProduct) - return "derived from " - + ((NamedObjectProduct) this.unitBase).getName(); + if (this.unitBase instanceof Nameable) + return "derived from " + ((Nameable) this.unitBase).getName(); else return "derived from " + this.getBase().toString(BaseUnit::getShortName); diff --git a/src/main/java/sevenUnits/unit/UnitType.java b/src/main/java/sevenUnits/unit/UnitType.java new file mode 100644 index 0000000..a13051a --- /dev/null +++ b/src/main/java/sevenUnits/unit/UnitType.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2022 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 . + */ +package sevenUnits.unit; + +/** + * A type of unit, as chosen by the type of system it is in. + *

    + *
  • {@code METRIC} refers to metric/SI units that pass {@link Unit#isMetric} + *
  • {@code SEMI_METRIC} refers to the degree Celsius (which is an official SI + * unit but does not pass {@link Unit#isMetric}) and non-metric units intended + * for use with the SI. + *
  • {@code NON_METRIC} refers to units that are neither metric nor intended + * for use with the metric system (e.g. imperial and customary units) + *
+ * + * @since 2022-04-10 + */ +public enum UnitType { + METRIC, SEMI_METRIC, NON_METRIC; +} diff --git a/src/main/java/sevenUnitsGUI/Presenter.java b/src/main/java/sevenUnitsGUI/Presenter.java index 9f8fe69..b38f90b 100644 --- a/src/main/java/sevenUnitsGUI/Presenter.java +++ b/src/main/java/sevenUnitsGUI/Presenter.java @@ -91,7 +91,7 @@ public final class Presenter { * @return text in About file * @since 2022-02-19 */ - static final String getAboutText() { + public static final String getAboutText() { return Presenter.getLinesFromResource("/about.txt").stream() .map(Presenter::withoutComments).collect(Collectors.joining("\n")) .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION.toString()); @@ -155,7 +155,7 @@ public final class Presenter { /** * The rule used for parsing input numbers. Any number-string inputted into - * this program will be parsed using this method. + * this program will be parsed using this method. Not implemented yet. */ private Function numberParsingRule; @@ -380,6 +380,25 @@ public final class Presenter { return this.showDuplicateUnits; } + /** + * @return the rule that is used by this presenter to convert numbers into + * strings + * @since 2022-04-10 + */ + public Function getNumberDisplayRule() { + return this.numberDisplayRule; + } + + /** + * @return the rule that is used by this presenter to convert strings into + * numbers + * @since 2022-04-10 + */ + @SuppressWarnings("unused") // not implemented yet + private Function getNumberParsingRule() { + return this.numberParsingRule; + } + /** * @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 @@ -392,11 +411,12 @@ public final class Presenter { } /** - * Loads settings from the user's settings file and applies them to the view. + * Loads settings from the user's settings file and applies them to the + * presenter. * * @since 2021-12-15 */ - public void loadSettings() {} + private void loadSettings() {} /** * Completes creation of the presenter. This part of the initialization @@ -416,12 +436,32 @@ public final class Presenter { void prefixSelected() {} /** - * Gets user settings from the view then saves them to the user's settings - * file. + * Saves the presenter's settings to the user settings file. * * @since 2021-12-15 */ - public void saveSettings() {} + private void saveSettings() {} + + /** + * @param numberDisplayRule the new rule that will be used by this presenter + * to convert numbers into strings + * @since 2022-04-10 + */ + public void setNumberDisplayRule( + Function numberDisplayRule) { + this.numberDisplayRule = numberDisplayRule; + } + + /** + * @param numberParsingRule the new rule that will be used by this presenter + * to convert strings into numbers + * @since 2022-04-10 + */ + @SuppressWarnings("unused") // not implemented yet + private void setNumberParsingRule( + Function numberParsingRule) { + this.numberParsingRule = numberParsingRule; + } /** * @param oneWayConversionEnabled whether not one-way conversion should be @@ -443,6 +483,12 @@ public final class Presenter { this.updateView(); } + /** + * Runs whenever a unit name is selected in the unit viewer. Gets the + * description of a unit and displays it. + * + * @since 2022-04-10 + */ void unitNameSelected() {} /** diff --git a/src/main/java/sevenUnitsGUI/TabbedView.java b/src/main/java/sevenUnitsGUI/TabbedView.java index ed45011..fd48965 100644 --- a/src/main/java/sevenUnitsGUI/TabbedView.java +++ b/src/main/java/sevenUnitsGUI/TabbedView.java @@ -56,6 +56,8 @@ import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; import sevenUnits.ProgramInfo; +import sevenUnits.unit.UnitType; +import sevenUnits.utils.NameSymbol; /** * A View that separates its functions into multiple tabs @@ -306,6 +308,8 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.prefixTextBox.setEditable(false); this.prefixTextBox.setLineWrap(true); + // ============ INFO PANEL ============ + final JPanel infoPanel = new JPanel(); this.masterPane.addTab("\uD83D\uDEC8", // info (i) character new JScrollPane(infoPanel)); @@ -572,6 +576,16 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { return new HashSet<>(this.toSearch.getItems()); } + @Override + public Optional getViewedPrefixName() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public Optional getViewedUnitName() { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void setDimensionNames(Set dimensionNames) { this.dimensionSelector.removeAllItems(); @@ -590,6 +604,16 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { this.toSearch.setItems(units); } + @Override + public void setViewablePrefixNames(Set prefixNames) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void setViewableUnitNames(Set unitNames) { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void showErrorMessage(String title, String message) { JOptionPane.showMessageDialog(this.frame, message, title, @@ -602,9 +626,19 @@ final class TabbedView implements ExpressionConversionView, UnitConversionView { uc.outputValueString(), uc.toName())); } + @Override + public void showPrefix(NameSymbol name, String multiplierString) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void showUnit(NameSymbol name, String definition, + String dimensionName, UnitType type) { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void showUnitConversionOutput(UnitConversionRecord uc) { this.unitOutput.setText(uc.toString()); } - } diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index 9d3a67b..6a95aa5 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Adrien Hopkins + * Copyright (C) 2021-2022 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 diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index e78c9cc..da3749e 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -16,6 +16,12 @@ */ package sevenUnitsGUI; +import java.util.Optional; +import java.util.Set; + +import sevenUnits.unit.UnitType; +import sevenUnits.utils.NameSymbol; + /** * An object that controls user interaction with 7Units * @@ -23,6 +29,35 @@ package sevenUnitsGUI; * @since 2021-12-15 */ public interface View { + /** + * @return name of prefix currently being viewed + * @since 2022-04-10 + */ + Optional getViewedPrefixName(); + + /** + * @return name of unit currently being viewed + * @since 2022-04-10 + */ + Optional getViewedUnitName(); + + /** + * Sets the list of prefixes that are available to be viewed in a prefix + * viewer + * + * @param prefixNames prefix names to view + * @since 2022-04-10 + */ + void setViewablePrefixNames(Set prefixNames); + + /** + * Sets the list of units that are available to be viewed in a unit viewer + * + * @param unitNames unit names to view + * @since 2022-04-10 + */ + void setViewableUnitNames(Set unitNames); + /** * Shows an error message. * @@ -32,4 +67,25 @@ public interface View { * @since 2021-12-15 */ void showErrorMessage(String title, String message); + + /** + * Shows information about a prefix to the user. + * + * @param name name(s) and symbol of prefix + * @param multiplierString string representation of prefix multiplier + * @since 2022-04-10 + */ + void showPrefix(NameSymbol name, String multiplierString); + + /** + * Shows information about a unit to the user. + * + * @param name name(s) and symbol of unit + * @param definition unit's definition string + * @param dimensionName name of unit's dimension + * @param type type of unit (metric/semi-metric/non-metric) + * @since 2022-04-10 + */ + void showUnit(NameSymbol name, String definition, String dimensionName, + UnitType type); } diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index 0195dd6..9f9a524 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -23,6 +23,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import sevenUnits.unit.UnitType; +import sevenUnits.utils.NameSymbol; + /** * A class that simulates a View (supports both unit and expression conversion) * for testing. Getters and setters work as expected. @@ -145,6 +148,16 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { return Collections.unmodifiableSet(this.toUnits); } + @Override + public Optional getViewedPrefixName() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public Optional getViewedUnitName() { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void setDimensionNames(Set dimensionNames) { this.dimensionNames = Objects.requireNonNull(dimensionNames, @@ -236,6 +249,16 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { this.toUnits = Objects.requireNonNull(units, "units may not be null"); } + @Override + public void setViewablePrefixNames(Set prefixNames) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void setViewableUnitNames(Set unitNames) { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void showErrorMessage(String title, String message) { System.err.printf("%s: %s%n", title, message); @@ -247,6 +270,17 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { System.out.println("Expression Conversion: " + uc); } + @Override + public void showPrefix(NameSymbol name, String multiplierString) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void showUnit(NameSymbol name, String definition, + String dimensionName, UnitType type) { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void showUnitConversionOutput(UnitConversionRecord uc) { this.unitConversions.add(uc); @@ -265,5 +299,4 @@ final class ViewBot implements UnitConversionView, ExpressionConversionView { public List unitConversionList() { return Collections.unmodifiableList(this.unitConversions); } - } -- cgit v1.2.3 From 39668f4b274f0e7996f65b4f432a48ae0d88daca Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 8 Jul 2022 12:46:45 -0500 Subject: Bumped version number to 0.4.0b1 & added @since --- CHANGELOG.org | 2 +- README.org | 2 +- docs/design.org | 2 +- docs/design.pdf | Bin 348080 -> 348051 bytes docs/design.tex | 68 ++++++++++----------- docs/manual.org | 6 +- docs/manual.pdf | Bin 173138 -> 173293 bytes docs/manual.tex | 36 +++++------ screenshots/main-interface-settings.png | Bin 25953 -> 25953 bytes src/main/java/sevenUnits/ProgramInfo.java | 4 +- src/main/java/sevenUnits/unit/BaseDimension.java | 5 +- src/main/java/sevenUnits/utils/NameSymbol.java | 1 + .../sevenUnits/utils/SemanticVersionNumber.java | 25 ++++++++ .../sevenUnitsGUI/ExpressionConversionView.java | 4 ++ src/main/java/sevenUnitsGUI/Main.java | 6 +- src/main/java/sevenUnitsGUI/PrefixSearchRule.java | 12 ++++ .../java/sevenUnitsGUI/StandardDisplayRules.java | 8 +++ .../java/sevenUnitsGUI/UnitConversionRecord.java | 8 +++ .../java/sevenUnitsGUI/UnitConversionView.java | 12 ++++ src/main/java/sevenUnitsGUI/View.java | 10 +++ src/main/java/sevenUnitsGUI/ViewBot.java | 1 + 21 files changed, 150 insertions(+), 62 deletions(-) (limited to 'src/main/java/sevenUnitsGUI/UnitConversionView.java') diff --git a/CHANGELOG.org b/CHANGELOG.org index 7000828..de873b3 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -1,6 +1,6 @@ * Changelog All notable changes in this project will be shown in this file. -** Unreleased +** v0.4.0 (Unreleased) *** Added - Added tests for the GUI - Added an object for the version numbers (SemanticVersionNumber) diff --git a/README.org b/README.org index f891bdc..171b4d9 100644 --- a/README.org +++ b/README.org @@ -1,4 +1,4 @@ -* 7Units v0.4.0a1 +* 7Units v0.4.0b1 (this project uses Semantic Versioning) ** What is it? This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. diff --git a/docs/design.org b/docs/design.org index 467c018..0e4ca92 100644 --- a/docs/design.org +++ b/docs/design.org @@ -1,5 +1,5 @@ #+TITLE: 7Units Design Document -#+SUBTITLE: For version 0.4.0-alpha.1 +#+SUBTITLE: For version 0.4.0-beta.1 #+DATE: 2022 July 8 #+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} #+LaTeX_HEADER: \usepackage{xurl} diff --git a/docs/design.pdf b/docs/design.pdf index 56d73a6..99fbb3b 100644 Binary files a/docs/design.pdf and b/docs/design.pdf differ diff --git a/docs/design.tex b/docs/design.tex index 5023fb3..9434cc1 100644 --- a/docs/design.tex +++ b/docs/design.tex @@ -1,4 +1,4 @@ -% Created 2022-07-08 Fri 10:05 +% Created 2022-07-08 Fri 12:43 % Intended LaTeX compiler: pdflatex \documentclass[11pt]{article} \usepackage[utf8]{inputenc} @@ -18,7 +18,7 @@ \usepackage{xurl} \date{2022 July 8} \title{7Units Design Document\\\medskip -\large For version 0.4.0-alpha.1} +\large For version 0.4.0-beta.1} \hypersetup{ pdfauthor={}, pdftitle={7Units Design Document}, @@ -34,35 +34,35 @@ \newpage \section{Introduction} -\label{sec:orgd3381a6} +\label{sec:orga266915} 7Units is a program that can convert between units. This document details the internal design of 7Units, intended to be used by current and future developers. \section{System Overview} -\label{sec:org9bd9474} +\label{sec:org6d969dd} \begin{figure}[htbp] \centering \includegraphics[height=144px]{./diagrams/overview-diagram.plantuml.png} \caption{A big-picture diagram of 7Units, containing all of the major classes.} \end{figure} \subsection{Packages of 7Units} -\label{sec:org5c8f3ee} +\label{sec:orged5584a} 7Units splits its code into three main packages: \begin{description} -\item[{\texttt{sevenUnits.unit}}] The \hyperref[sec:org1171d15]{unit system} +\item[{\texttt{sevenUnits.unit}}] The \hyperref[sec:orgf969629]{unit system} \item[{\texttt{sevenUnits.utils}}] Extra classes that aid the unit system. -\item[{\texttt{sevenUnitsGUI}}] The \hyperref[sec:orgb716c37]{front end} code +\item[{\texttt{sevenUnitsGUI}}] The \hyperref[sec:org431a987]{front end} code \end{description} \texttt{sevenUnits.unit} depends on \texttt{sevenUnits.utils}, while \texttt{sevenUnitsGUI} depends on both \texttt{sevenUnits} packages. There is only one class that isn't in any of these packages, \texttt{sevenUnits.VersionInfo}. \subsection{Major Classes of 7Units} -\label{sec:org22c5367} +\label{sec:org147769f} \begin{description} -\item[{\hyperref[sec:orge128590]{sevenUnits.unit.Unit}}] The class representing a unit -\item[{\hyperref[sec:orgc8dc0e4]{sevenUnits.unit.UnitDatabase}}] A class that stores collections of units, prefixes and dimensions. -\item[{\hyperref[sec:orgf3ee40e]{sevenUnitsGUI.View}}] The class that handles interaction between the user and the program. -\item[{\hyperref[sec:orgcf5cc70]{sevenUnitsGUI.Presenter}}] The class that handles communication between the \texttt{View} and the unit system. +\item[{\hyperref[sec:org93aab3e]{sevenUnits.unit.Unit}}] The class representing a unit +\item[{\hyperref[sec:org0a33326]{sevenUnits.unit.UnitDatabase}}] A class that stores collections of units, prefixes and dimensions. +\item[{\hyperref[sec:org5460ab6]{sevenUnitsGUI.View}}] The class that handles interaction between the user and the program. +\item[{\hyperref[sec:org4f735c1]{sevenUnitsGUI.Presenter}}] The class that handles communication between the \texttt{View} and the unit system. \end{description} \newpage \subsection{Process of Unit Conversion} -\label{sec:org73700d8} +\label{sec:org65b1400} \begin{figure}[htbp] \centering \includegraphics[width=.9\linewidth]{./diagrams/convert-units.plantuml.png} @@ -77,7 +77,7 @@ \end{enumerate} \newpage \subsection{Process of Expression Conversion} -\label{sec:org3e8ae17} +\label{sec:orgc9346ba} The process of expression conversion is similar to that of unit conversion. \begin{figure}[htbp] \centering @@ -93,7 +93,7 @@ The process of expression conversion is similar to that of unit conversion. \end{enumerate} \newpage \section{Unit System Design} -\label{sec:org1171d15} +\label{sec:orgf969629} Any code related to the backend unit system is stored in the \texttt{sevenUnits.unit} package. Here is a class diagram of the system. Unimportant methods, methods inherited from Object, getters and setters have been omitted. @@ -104,11 +104,11 @@ Here is a class diagram of the system. Unimportant methods, methods inherited f \end{figure} \newpage \subsection{Dimensions} -\label{sec:org14cd421} -Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an \hyperref[sec:orgc3e831c]{ObjectProduct}, where \texttt{BaseDimension} is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions. +\label{sec:orgda7eb73} +Dimensions represent what a unit is measuring, such as length, time, or energy. Dimensions are represented as an \hyperref[sec:org9c5f1fc]{ObjectProduct}, where \texttt{BaseDimension} is a very simple class (its only properties are a name and a symbol) which represents the dimension of a base unit; these base dimensions can be multiplied to create all other Dimensions. \subsection{Unit Classes} -\label{sec:orge128590} -Units are internally represented by the abstract class \texttt{Unit}. All units have an \hyperref[sec:orgc3e831c]{ObjectProduct} (referred to as the base) that they are based on, a dimension (ObjectProduct), one or more names and a symbol (these last two bits of data are contained in the \texttt{NameSymbol} class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make \texttt{BaseUnit} instances, and a protected one used to make general units (for other subclasses of \texttt{Unit}). All unit classes are immutable. +\label{sec:org93aab3e} +Units are internally represented by the abstract class \texttt{Unit}. All units have an \hyperref[sec:org9c5f1fc]{ObjectProduct} (referred to as the base) that they are based on, a dimension (ObjectProduct), one or more names and a symbol (these last two bits of data are contained in the \texttt{NameSymbol} class). The dimension is calculated from the base unit when needed; the variable is just a cache. It has two constructors: a package-private one used to make \texttt{BaseUnit} instances, and a protected one used to make general units (for other subclasses of \texttt{Unit}). All unit classes are immutable. Units also have two conversion functions - one which converts from a value expressed in this unit to its base unit, and another which converts from a value expressed in the base unit to this unit. In \texttt{Unit}, they are defined as two abstract methods. This allows you to convert from any unit to any other (as long as they have the same base, i.e. you aren't converting metres to pounds). To convert from A to B, first convert from A to its base, then convert from the base to B. @@ -133,20 +133,20 @@ There are a few more classes which play small roles in the unit system: \item[{USCustomary}] A static utility class with instances of common units in the US Customary system (not to be confused with the British Imperial system; it has the same unit names but the values of a few units are different). \end{description} \subsection{Prefixes} -\label{sec:org1bd7050} +\label{sec:org40fa3a0} A \texttt{UnitPrefix} is a simple object that can multiply a \texttt{LinearUnit} by a value. It can calculate a new name for the unit by combining its name and the unit's name (symbols are done similarly). It can do multiplication, division and exponentation with a number, as well as multiplication and division with another prefix; all of these work by changing the prefix's multiplier. \subsection{The Unit Database} -\label{sec:orgc8dc0e4} +\label{sec:org0a33326} The \texttt{UnitDatabase} class stores all of the unit, prefix and dimension data used by this program. It is not a representation of an actual database, just a class that stores lots of data. Units are stored using a custom \texttt{Map} implementation (\texttt{PrefixedUnitMap}) which maps unit names to units. It is backed by two maps: one for units (without prefixes) and one for prefixes. It is programmed to include prefixes (so if units includes "metre" and prefixes includes "kilo", this map will include "kilometre", mapping it to a unit representing a kilometre). It is immutable, but you can modify the underlying maps, which is reflected in the \texttt{PrefixedUnitMap}. Other than that, it is a normal map implementation. Prefixes and dimensions are stored in normal maps. \subsubsection{Parsing Expressions} -\label{sec:orgb7ee1da} -Each \texttt{UnitDatabase} instance has four \hyperref[sec:orgd351c2f]{ExpressionParser} instances associated with it, for four types of expressions: unit, unit value, prefix and dimension. They are mostly similar, with operators corresponding to each operation of the corresponding class (\texttt{LinearUnit}, \texttt{LinearUnitValue}, \texttt{UnitPrefix}, \texttt{ObjectProduct}). Unit and unit value expressions use linear units; nonlinear units can be used with a special syntax (like "degC(20)") and are immediately converted to a linear unit representing their base (Kelvin in this case) before operating. +\label{sec:orgb3362c7} +Each \texttt{UnitDatabase} instance has four \hyperref[sec:org7f49fac]{ExpressionParser} instances associated with it, for four types of expressions: unit, unit value, prefix and dimension. They are mostly similar, with operators corresponding to each operation of the corresponding class (\texttt{LinearUnit}, \texttt{LinearUnitValue}, \texttt{UnitPrefix}, \texttt{ObjectProduct}). Unit and unit value expressions use linear units; nonlinear units can be used with a special syntax (like "degC(20)") and are immediately converted to a linear unit representing their base (Kelvin in this case) before operating. \subsubsection{Parsing Files} -\label{sec:org072dc65} +\label{sec:org5f50970} There are two types of data files: unit and dimension. Unit files contain data about units and prefixes. Each line contains the name of a unit or prefix (prefixes end in a dash, units don't) followed by an expression which defines it, separated by one or more space characters (this behaviour is defined by the static regular expression \texttt{NAME\_EXPRESSION}). Unit files are parsed line by line, each line being run through the \texttt{addUnitOrPrefixFromLine} method, which splits a line into name and expression, determines whether it's a unit or a prefix, and parses the expression. Because all units are defined by others, base units need to be defined with a special expression "!"; \textbf{these units should be added to the database before parsing the file}. @@ -154,10 +154,10 @@ Unit files contain data about units and prefixes. Each line contains the name o Dimension files are similar, only for dimensions instead of units and prefixes. \newpage \section{Front-End Design} -\label{sec:orgb716c37} +\label{sec:org431a987} The front-end of 7Units is based on an MVP model. There are two major frontend classes, the \textbf{View} and the \textbf{Presenter}. \subsection{The View} -\label{sec:orgf3ee40e} +\label{sec:org5460ab6} The \texttt{View} is the part of the frontend code that directly interacts with the user. It handles input and output, but does not do any processing. Processing is handled by the presenter and the backend code. The \texttt{View} is an interface, not a single class, so that I can easily create multiple views without having to rewrite any processing code. This allows me to easily prototype changes to the GUI without messing with existing code. @@ -171,10 +171,10 @@ There are currently two implementations of the \texttt{View}: \end{description} Both of these \texttt{View} implementations implement \texttt{UnitConversionView} and \texttt{ExpressionConversionView}. \subsection{The Presenter} -\label{sec:orgcf5cc70} +\label{sec:org4f735c1} The \texttt{Presenter} is an intermediary between the \texttt{View} and the backend code. It accepts the user's input and passes it to the backend, then accepts the backend's output and passes it to the frontend for user viewing. Its main functions do not have arguments or return values; instead it takes input from and provides output to the \texttt{View} via its public methods. \subsubsection{Rules} -\label{sec:org9542834} +\label{sec:org29011d5} The \texttt{Presenter} has a set of function-object rules that determine some of its behaviours. Each corresponds to a setting in the \texttt{View}, but they can be set to other values via the \texttt{Presenter}'s setters (although nonstandard rules cannot be saved and loaded): \begin{description} \item[{numberDisplayRule}] A function that determines how numbers are displayed. This controls the rounding rules. @@ -184,7 +184,7 @@ The \texttt{Presenter} has a set of function-object rules that determine some of These rules have been made this way to enable an incredible level of customization of these behaviours. Because any function object with the correct arguments and return type is accepted, these rules (especially the number display rule) can do much more than the default behaviours. \subsection{Utility Classes} -\label{sec:orgc49ece0} +\label{sec:orga401626} The frontend has many miscellaneous utility classes. Many of them are package-private. Here is a list of them, with a brief description of what they do and where they are used: \begin{description} \item[{DefaultPrefixRepetitionRule}] An enum containing the available rules determining when you can repeat prefixes. Used by the \texttt{TabbedView} for selecting the rule and by the \texttt{Presenter} for loading it from a file. @@ -197,15 +197,15 @@ The frontend has many miscellaneous utility classes. Many of them are package-p \end{description} \newpage \section{Utility Classes} -\label{sec:orgc90957f} +\label{sec:org9889589} 7Units has a few general "utility" classes. They aren't directly related to units, but are used in the units system. \subsection{ObjectProduct} -\label{sec:orgc3e831c} +\label{sec:org9c5f1fc} An \texttt{ObjectProduct} represents a "product" of elements of some type. The units system uses them to represent coherent units as a product of base units, and dimensions as a product of base dimensions. Internally, it is represented using a map mapping objects to their exponents in the product. For example, the unit "kg m\textsuperscript{2} / s\textsuperscript{2}" (i.e. a Joule) would be represented with a map like \texttt{[kg: 1, m: 2, s: -2]}. \subsection{ExpressionParser} -\label{sec:orgd351c2f} +\label{sec:org7f49fac} The \texttt{ExpressionParser} class is used to parse the unit, prefix and dimension expressions that are used throughout 7Units. An expression is something like "(2 m + 30 J / N) * 8 s)". Each instance represents a type of expression, containing a way to obtain values (such as numbers or units) from the text and operations that can be done on these values (such as addition, subtraction or multiplication). Each operation also has a priority, which controls the order of operations (i.e. multiplication gets a higher priority than addition). \texttt{ExpressionParser} has a parameterized type \texttt{T}, which represents the type of the value used in the expression. The expression parser currently only supports one type of value per expression; in the expressions used by 7Units numbers are treated as a kind of unit or prefix. Operators are represented by internal types; the system distinguishes between unary operators (those that take a single value, like negation) and binary operators (those that take 2 values, like +, -, * or /). @@ -222,13 +222,13 @@ Expressions are parsed in 2 steps: After evaluating the last token, there should be one value left in the stack - the answer. If there isn't, the original expression was malformed. \end{enumerate} \subsection{Math Classes} -\label{sec:orgdadbc0d} +\label{sec:org48c9af9} There are two simple math classes in 7Units: \begin{description} \item[{\texttt{UncertainDouble}}] Like a \texttt{double}, but with an uncertainty (e.g. \(2.0 \pm 0.4\)). The operations are like those of the regular Double, only they also calculate the uncertainty of the final value. They also have "exact" versions to help interoperation between \texttt{double} and \texttt{UncertainDouble}. It is used by the converter's Scientific Precision setting. \item[{\texttt{DecimalComparison}}] A static utility class that contains a few alternate equals() methods for \texttt{double} and \texttt{UncertainDouble}. These methods allow a slight (configurable) difference between values to still be considered equal, to fight roundoff error. \end{description} \subsection{Collection Classes} -\label{sec:org7421746} +\label{sec:org9065607} The \texttt{ConditionalExistenceCollections} class contains wrapper implementations of \texttt{Collection}, \texttt{Iterator}, \texttt{Map} and \texttt{Set}. These implementations ignore elements that do not pass a certain condition - if an element fails the condition, \texttt{contains} will return false, the iterator will skip past it, it won't be counted in \texttt{size}, etc. even if it exists in the original collection. Effectively, any element of the original collection that fails the test does not exist. \end{document} diff --git a/docs/manual.org b/docs/manual.org index 6de3b93..bcaaf6b 100644 --- a/docs/manual.org +++ b/docs/manual.org @@ -1,5 +1,5 @@ #+TITLE: 7Units User Manual -#+SUBTITLE: For Version 0.4.0-alpha.1 +#+SUBTITLE: For Version 0.4.0-beta.1 #+DATE: 2022 July 8 #+LaTeX_HEADER: \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} @@ -9,7 +9,7 @@ * System Requirements - Works on all major operating systems \\ *NOTE:* All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown. - - Java version 11-15 required + - Java version 11+ required # installation instructions go here - wait until git repository is fixed/set up #+LaTeX: \newpage * How to Use 7Units @@ -47,7 +47,7 @@ [[../screenshots/sample-conversion-results-expression-converter.png]] * 7Units Settings All settings can be accessed in the tab with the gear icon. - #+CAPTION: The settings menu, as of version 0.4.0-alpha.1 + #+CAPTION: The settings menu, as of version 0.4.0-beta.1 #+ATTR_LaTeX: :height 250px [[../screenshots/main-interface-settings.png]] ** Rounding Settings diff --git a/docs/manual.pdf b/docs/manual.pdf index 71cb886..430eb09 100644 Binary files a/docs/manual.pdf and b/docs/manual.pdf differ diff --git a/docs/manual.tex b/docs/manual.tex index 4b8e4ab..bc80a69 100644 --- a/docs/manual.tex +++ b/docs/manual.tex @@ -1,4 +1,4 @@ -% Created 2022-07-08 Fri 10:17 +% Created 2022-07-08 Fri 12:44 % Intended LaTeX compiler: pdflatex \documentclass[11pt]{article} \usepackage[utf8]{inputenc} @@ -17,7 +17,7 @@ \usepackage[a4paper, lmargin=25mm, rmargin=25mm, tmargin=25mm, bmargin=25mm]{geometry} \date{2022 July 8} \title{7Units User Manual\\\medskip -\large For Version 0.4.0-alpha.1} +\large For Version 0.4.0-beta.1} \hypersetup{ pdfauthor={}, pdftitle={7Units User Manual}, @@ -32,21 +32,21 @@ \newpage \section{Introduction and Purpose} -\label{sec:org26b964e} +\label{sec:org40df1fc} 7Units is a program that can be used to convert units. This document outlines how to use the program. \section{System Requirements} -\label{sec:orgfb95788} +\label{sec:org5bf24ac} \begin{itemize} \item Works on all major operating systems \\ \textbf{NOTE:} All screenshots in this document were taken on Windows 10. If you use a different operating system, the program will probably look different than what is shown. -\item Java version 11-15 required +\item Java version 11+ required \end{itemize} \newpage \section{How to Use 7Units} -\label{sec:orgc48d5d5} +\label{sec:org0303c2c} \subsection{Simple Unit Conversion} -\label{sec:orgd56d395} +\label{sec:orgfdea557} \begin{enumerate} \item Select the "Convert Units" tab if it is not already selected. You should see a screen like in figure \ref{main-interface-dimension}: \begin{figure}[htbp] @@ -71,7 +71,7 @@ \end{figure} \end{enumerate} \subsection{Complex Unit Conversion} -\label{sec:org79999e9} +\label{sec:orgaebd362} \begin{enumerate} \item Select the "Convert Unit Expressions" if it is not already selected. You should see a screen like in figure \ref{main-interface-expression}: \begin{figure}[htbp] @@ -79,7 +79,7 @@ \includegraphics[height=250px]{../screenshots/main-interface-expression-converter.png} \caption{\label{main-interface-expression}Taken in version 0.3.0} \end{figure} -\item Enter a \hyperref[sec:org28ae9bb]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}". +\item Enter a \hyperref[sec:org1bb92a1]{unit expression} in the From box. This can be something like "\texttt{7 km}" or "\texttt{6 ft - 2 in}" or "\texttt{3 kg m + 9 lb ft + (35 mm)\textasciicircum{}2 * (85 oz) / (20 in)}". \item Enter a unit name (or another unit expression) in the To box. \item Press the Convert button. This will calculate the value of the first expression, and convert it to a multiple of the second unit (or expression). \begin{figure}[htbp] @@ -89,15 +89,15 @@ \end{figure} \end{enumerate} \section{7Units Settings} -\label{sec:org6032cec} +\label{sec:orgcab9094} All settings can be accessed in the tab with the gear icon. \begin{figure}[htbp] \centering \includegraphics[height=250px]{../screenshots/main-interface-settings.png} -\caption{The settings menu, as of version 0.4.0-alpha.1} +\caption{The settings menu, as of version 0.4.0-beta.1} \end{figure} \subsection{Rounding Settings} -\label{sec:orgbe67478} +\label{sec:orgbb50019} These settings control how the output of a unit conversion is rounded. \begin{description} \item[{Fixed Precision}] Round to a fixed number of \href{https://en.wikipedia.org/wiki/Significant\_figures}{significant digits}. The number of significant digits is controlled by the precision slider below. @@ -105,7 +105,7 @@ These settings control how the output of a unit conversion is rounded. \item[{Scientific Precision}] Intelligent rounding which uses the precision of the input value(s) to determine the output precision. Not affected by the precision slider. \end{description} \subsection{Prefix Repetition Settings} -\label{sec:org3207fad} +\label{sec:org48d4cc7} These settings control when you are allowed to repeat unit prefixes (e.g. kilokilometre) \begin{description} \item[{No Repetition}] Units may only have one prefix. @@ -120,7 +120,7 @@ These settings control when you are allowed to repeat unit prefixes (e.g. kiloki \end{itemize} \end{description} \subsection{Search Settings} -\label{sec:orge76e8f6} +\label{sec:orgce39699} These settings control which prefixes are shown in the "Convert Units" tab. Only coherent SI units (e.g. metre, second, newton, joule) will get prefixes. Some prefixed units are created in the unitfile, and will stay regardless of this setting (though they can be removed from the unitfile). \begin{description} \item[{Never Include Prefixed Units}] Prefixed units will only be shown if they are explicitly added to the unitfile. @@ -128,15 +128,15 @@ These settings control which prefixes are shown in the "Convert Units" tab. Onl \item[{Include All Single Prefixes}] Every coherent unit will have every prefixed version of it included in the list. \end{description} \subsection{Miscellaneous Settings} -\label{sec:org5613324} +\label{sec:org2332067} \begin{description} \item[{Convert One Way Only}] In the simple conversion tab, only imperial/customary units will be shown on the left, and only metric units\footnote{7Units's definition of "metric" is stricter than the SI, but all of the common units that are commonly considered metric but not included in 7Units's definition are included in the exceptions file.} will be shown on the right. Units listed in the exceptions file (\texttt{src/main/resources/metric\_exceptions.txt}) will be shown on both sides. This is a way to reduce the number of options you must search through if you only convert one way. The expressions tab is unaffected. \item[{Show Duplicates in "Convert Units"}] If unchecked, any unit that has multiple names will only have one included in the Convert Units lists. The selected name will be the longest; if there are multiple longest names one is selected arbitrarily. You will still be able to use these alternate names in the expressions tab. \end{description} \section{Appendices} -\label{sec:org03406a3} +\label{sec:orgd294b53} \subsection{Unit Expressions} -\label{sec:org28ae9bb} +\label{sec:org1bb92a1} A unit expression is simply a math expression where the values being operated on are units or numbers. The operations that can be used are (in order of precedence): \begin{itemize} \item Exponentiation (\^{}); the exponent must be an integer. Both units and numbers can be raised to an exponent @@ -146,6 +146,6 @@ A unit expression is simply a math expression where the values being operated on Brackets can be used to manipulate the order of operations, and nonlinear units like Celsius and Fahrenheit cannot be used in expressions. You can use a value in a nonlinear unit by putting brackets after it - for example, degC(12) represents the value 12 \textdegree{} C \subsection{Other Expressions} -\label{sec:orgb03cf2c} +\label{sec:org2f36819} There are also a simplified version of expressions for prefixes and dimensions. Only multiplication, division and exponentation are supported. Currently, exponentation is not supported for dimensions, but that may be fixed in the future. \end{document} diff --git a/screenshots/main-interface-settings.png b/screenshots/main-interface-settings.png index c29f65c..39b95f4 100644 Binary files a/screenshots/main-interface-settings.png and b/screenshots/main-interface-settings.png differ diff --git a/src/main/java/sevenUnits/ProgramInfo.java b/src/main/java/sevenUnits/ProgramInfo.java index f32d2c7..6ebe66c 100644 --- a/src/main/java/sevenUnits/ProgramInfo.java +++ b/src/main/java/sevenUnits/ProgramInfo.java @@ -26,9 +26,9 @@ import sevenUnits.utils.SemanticVersionNumber; */ public final class ProgramInfo { - /** The version number (0.4.0-alpha+dev) */ + /** The version number (0.4.0-beta.1) */ public static final SemanticVersionNumber VERSION = SemanticVersionNumber - .preRelease(0, 4, 0, "alpha", 1); + .preRelease(0, 4, 0, "beta", 1); private ProgramInfo() { // this class is only for static variables, you shouldn't be able to diff --git a/src/main/java/sevenUnits/unit/BaseDimension.java b/src/main/java/sevenUnits/unit/BaseDimension.java index bcd57d9..820d48c 100644 --- a/src/main/java/sevenUnits/unit/BaseDimension.java +++ b/src/main/java/sevenUnits/unit/BaseDimension.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 Adrien Hopkins + * Copyright (C) 2019, 2022 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 @@ -63,6 +63,9 @@ public final class BaseDimension implements Nameable { this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); } + /** + * @since v0.4.0 + */ @Override public NameSymbol getNameSymbol() { return NameSymbol.of(this.name, this.symbol); diff --git a/src/main/java/sevenUnits/utils/NameSymbol.java b/src/main/java/sevenUnits/utils/NameSymbol.java index 7ef2967..9388f63 100644 --- a/src/main/java/sevenUnits/utils/NameSymbol.java +++ b/src/main/java/sevenUnits/utils/NameSymbol.java @@ -294,6 +294,7 @@ public final class NameSymbol { * extra name. If this {@code NameSymbol} has a primary name, the provided * name will become an other name, otherwise it will become the primary name. * + * @since v0.4.0 * @since 2022-04-19 */ public final NameSymbol withExtraName(String name) { diff --git a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java index a663a11..e80e16e 100644 --- a/src/main/java/sevenUnits/utils/SemanticVersionNumber.java +++ b/src/main/java/sevenUnits/utils/SemanticVersionNumber.java @@ -39,6 +39,7 @@ import java.util.regex.Pattern; * are made * * + * @since v0.4.0 * @since 2022-02-19 */ public final class SemanticVersionNumber @@ -51,6 +52,7 @@ public final class SemanticVersionNumber * throw NullPointerExceptions, everything else throws * IllegalArgumentException. * + * @since v0.4.0 * @since 2022-02-19 */ public static final class Builder { @@ -67,6 +69,7 @@ public final class SemanticVersionNumber * @param major major version number of final version * @param minor minor version number of final version * @param patch patch version number of final version + * @since v0.4.0 * @since 2022-02-19 */ private Builder(int major, int minor, int patch) { @@ -79,6 +82,7 @@ public final class SemanticVersionNumber /** * @return version number created by this builder + * @since v0.4.0 * @since 2022-02-19 */ public SemanticVersionNumber build() { @@ -91,6 +95,7 @@ public final class SemanticVersionNumber * * @param identifiers build metadata * @return this builder + * @since v0.4.0 * @since 2022-02-19 */ public Builder buildMetadata(List identifiers) { @@ -110,6 +115,7 @@ public final class SemanticVersionNumber * * @param identifiers build metadata * @return this builder + * @since v0.4.0 * @since 2022-02-19 */ public Builder buildMetadata(String... identifiers) { @@ -148,6 +154,7 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder + * @since v0.4.0 * @since 2022-02-19 */ public Builder preRelease(int... identifiers) { @@ -166,6 +173,7 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder + * @since v0.4.0 * @since 2022-02-19 */ public Builder preRelease(List identifiers) { @@ -185,6 +193,7 @@ public final class SemanticVersionNumber * * @param identifiers pre-release identifier(s) to add * @return this builder + * @since v0.4.0 * @since 2022-02-19 */ public Builder preRelease(String... identifiers) { @@ -205,6 +214,7 @@ public final class SemanticVersionNumber * @param identifier1 first identifier * @param identifier2 second identifier * @return this builder + * @since v0.4.0 * @since 2022-02-19 */ public Builder preRelease(String identifier1, int identifier2) { @@ -270,6 +280,7 @@ public final class SemanticVersionNumber * @param patch patch version number of final version * @return version number builder * @throws IllegalArgumentException if any argument is negative + * @since v0.4.0 * @since 2022-02-19 */ public static final SemanticVersionNumber.Builder builder(int major, @@ -293,6 +304,7 @@ public final class SemanticVersionNumber * @param b second list * @return result of comparison as in a comparator * @see Comparator + * @since v0.4.0 * @since 2022-02-20 */ private static final int compareIdentifiers(List a, List b) { @@ -353,6 +365,7 @@ public final class SemanticVersionNumber * * @param versionString string to parse * @return {@code SemanticVersionNumber} instance + * @since v0.4.0 * @since 2022-02-19 * @see {@link #toString} */ @@ -396,6 +409,7 @@ public final class SemanticVersionNumber * * @param versionString string to test * @return true iff string is valid + * @since v0.4.0 * @since 2022-02-19 */ public static final boolean isValidVersionString(String versionString) { @@ -415,6 +429,7 @@ public final class SemanticVersionNumber * @throws IllegalArgumentException if any argument is negative or if the * preReleaseType is null, empty or not * alphanumeric (0-9, A-Z, a-z, - only) + * @since v0.4.0 * @since 2022-02-19 */ public static final SemanticVersionNumber preRelease(int major, int minor, @@ -452,6 +467,7 @@ public final class SemanticVersionNumber * @param patch patch version number * @return {@code SemanticVersionNumber} instance * @throws IllegalArgumentException if any argument is negative + * @since v0.4.0 * @since 2022-02-19 */ public static final SemanticVersionNumber stableVersion(int major, int minor, @@ -484,6 +500,7 @@ public final class SemanticVersionNumber * @param patch patch version number * @param preReleaseIdentifiers pre-release version data * @param buildMetadata build metadata + * @since v0.4.0 * @since 2022-02-19 */ private SemanticVersionNumber(int major, int minor, int patch, @@ -497,6 +514,7 @@ public final class SemanticVersionNumber /** * @return build metadata (empty if there is none) + * @since v0.4.0 * @since 2022-02-19 */ public List buildMetadata() { @@ -567,6 +585,7 @@ public final class SemanticVersionNumber * @param other version to compare with * @return true if you can definitely upgrade to {@code other} without * changing code + * @since v0.4.0 * @since 2022-02-20 */ public boolean compatibleWith(SemanticVersionNumber other) { @@ -620,6 +639,7 @@ public final class SemanticVersionNumber /** * @return true iff this version is stable (major version > 0 and not a * pre-release) + * @since v0.4.0 * @since 2022-02-19 */ public boolean isStable() { @@ -629,6 +649,7 @@ public final class SemanticVersionNumber /** * @return the MAJOR version number, incremented when you make backwards * incompatible API changes + * @since v0.4.0 * @since 2022-02-19 */ public int majorVersion() { @@ -638,6 +659,7 @@ public final class SemanticVersionNumber /** * @return the MINOR version number, incremented when you add backwards * compatible functionality + * @since v0.4.0 * @since 2022-02-19 */ public int minorVersion() { @@ -647,6 +669,7 @@ public final class SemanticVersionNumber /** * @return the PATCH version number, incremented when you make backwards * compatible bug fixes + * @since v0.4.0 * @since 2022-02-19 */ public int patchVersion() { @@ -656,6 +679,7 @@ public final class SemanticVersionNumber /** * @return identifiers describing this pre-release (empty if not a * pre-release) + * @since v0.4.0 * @since 2022-02-19 */ public List preReleaseIdentifiers() { @@ -674,6 +698,7 @@ public final class SemanticVersionNumber * 1, pre-release identifiers "alpha" and "1" and build metadata "2022-02-19" * has a string representation "3.2.1-alpha.1+2022-02-19". * + * @since v0.4.0 * @see The official SemVer specification */ @Override diff --git a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java index 872ca10..5c39788 100644 --- a/src/main/java/sevenUnitsGUI/ExpressionConversionView.java +++ b/src/main/java/sevenUnitsGUI/ExpressionConversionView.java @@ -20,17 +20,20 @@ package sevenUnitsGUI; * A View that can convert unit expressions * * @author Adrien Hopkins + * @since v0.4.0 * @since 2021-12-15 */ public interface ExpressionConversionView extends View { /** * @return unit expression to convert from + * @since v0.4.0 * @since 2021-12-15 */ String getFromExpression(); /** * @return unit expression to convert to + * @since v0.4.0 * @since 2021-12-15 */ String getToExpression(); @@ -39,6 +42,7 @@ public interface ExpressionConversionView extends View { * Shows the output of an expression conversion to the user. * * @param uc unit conversion to show + * @since v0.4.0 * @since 2021-12-15 */ void showExpressionConversionOutput(UnitConversionRecord uc); diff --git a/src/main/java/sevenUnitsGUI/Main.java b/src/main/java/sevenUnitsGUI/Main.java index b5a896f..998b373 100644 --- a/src/main/java/sevenUnitsGUI/Main.java +++ b/src/main/java/sevenUnitsGUI/Main.java @@ -19,12 +19,16 @@ package sevenUnitsGUI; /** * The main code for the 7Units GUI * + * @since v0.4.0 * @since 2022-04-19 */ public final class Main { /** - * @param args + * The main method that starts 7Units + * + * @param args commandline arguments + * @since v0.4.0 * @since 2022-04-19 */ public static void main(String[] args) { diff --git a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java index 2928082..87f14a8 100644 --- a/src/main/java/sevenUnitsGUI/PrefixSearchRule.java +++ b/src/main/java/sevenUnitsGUI/PrefixSearchRule.java @@ -33,24 +33,31 @@ 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 */ public final class PrefixSearchRule implements Function, Map> { /** * A rule that does not add any prefixed versions of units. + * + * @since v0.4.0 */ public static final PrefixSearchRule NO_PREFIXES = getUniversalRule( Set.of()); /** * A rule that gives every unit a common set of prefixes. + * + * @since v0.4.0 */ public static final PrefixSearchRule COMMON_PREFIXES = getCoherentOnlyRule( Set.of(Metric.MILLI, Metric.KILO)); /** * A rule that gives every unit all metric prefixes. + * + * @since v0.4.0 */ public static final PrefixSearchRule ALL_METRIC_PREFIXES = getCoherentOnlyRule( Metric.ALL_PREFIXES); @@ -62,6 +69,7 @@ public final class PrefixSearchRule implements * * @param prefixes prefixes to apply * @return prefix rule + * @since v0.4.0 * @since 2022-07-06 */ public static final PrefixSearchRule getCoherentOnlyRule( @@ -75,6 +83,7 @@ public final class PrefixSearchRule implements * * @param prefixes prefixes to apply * @return prefix rule + * @since v0.4.0 * @since 2022-07-06 */ public static final PrefixSearchRule getUniversalRule( @@ -95,6 +104,7 @@ public final class PrefixSearchRule implements /** * @param prefixes * @param metricOnly + * @since v0.4.0 * @since 2022-07-06 */ public PrefixSearchRule(Set prefixes, @@ -140,6 +150,7 @@ public final class PrefixSearchRule implements /** * @return the allowUnit + * @since v0.4.0 * @since 2022-07-06 */ public Predicate getAllowUnit() { @@ -148,6 +159,7 @@ public final class PrefixSearchRule implements /** * @return the prefixes that are applied by this rule + * @since v0.4.0 * @since 2022-07-06 */ public Set getPrefixes() { diff --git a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java index 0c0ba8e..cc69d31 100644 --- a/src/main/java/sevenUnitsGUI/StandardDisplayRules.java +++ b/src/main/java/sevenUnitsGUI/StandardDisplayRules.java @@ -28,12 +28,14 @@ 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 */ public final class StandardDisplayRules { /** * A rule that rounds to a fixed number of decimal places. * + * @since v0.4.0 * @since 2022-04-18 */ public static final class FixedDecimals @@ -94,6 +96,7 @@ public final class StandardDisplayRules { /** * A rule that rounds to a fixed number of significant digits. * + * @since v0.4.0 * @since 2022-04-18 */ public static final class FixedPrecision @@ -162,6 +165,7 @@ 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 */ public static final class UncertaintyBased @@ -188,6 +192,7 @@ 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 */ public static final FixedDecimals fixedDecimals(int decimalPlaces) { @@ -198,6 +203,7 @@ 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 */ public static final FixedPrecision fixedPrecision(int significantFigures) { @@ -211,6 +217,7 @@ 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 */ public static final Function getStandardRule( @@ -236,6 +243,7 @@ public final class StandardDisplayRules { /** * @return an UncertainDouble-based rounding rule + * @since v0.4.0 * @since 2022-04-18 */ public static final UncertaintyBased uncertaintyBased() { diff --git a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java index f951f44..fa64ee9 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionRecord.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionRecord.java @@ -24,6 +24,7 @@ import sevenUnits.unit.UnitValue; /** * A record of a conversion between units or expressions * + * @since v0.4.0 * @since 2022-04-09 */ public final class UnitConversionRecord { @@ -33,6 +34,7 @@ public final class UnitConversionRecord { * @param input input unit & value * @param output output unit & value * @return unit conversion record + * @since v0.4.0 * @since 2022-04-09 */ public static UnitConversionRecord fromLinearValues(LinearUnitValue input, @@ -49,6 +51,7 @@ public final class UnitConversionRecord { * @param input input unit & value * @param output output unit & value * @return unit conversion record + * @since v0.4.0 * @since 2022-04-09 */ public static UnitConversionRecord fromValues(UnitValue input, @@ -67,6 +70,7 @@ 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 */ public static UnitConversionRecord valueOf(String fromName, String toName, @@ -143,6 +147,7 @@ public final class UnitConversionRecord { /** * @return name of unit or expression that was converted from + * @since v0.4.0 * @since 2022-04-09 */ public String fromName() { @@ -166,6 +171,7 @@ public final class UnitConversionRecord { /** * @return string representing input value + * @since v0.4.0 * @since 2022-04-09 */ public String inputValueString() { @@ -174,6 +180,7 @@ public final class UnitConversionRecord { /** * @return string representing output value + * @since v0.4.0 * @since 2022-04-09 */ public String outputValueString() { @@ -182,6 +189,7 @@ public final class UnitConversionRecord { /** * @return name of unit or expression that was converted to + * @since v0.4.0 * @since 2022-04-09 */ public String toName() { diff --git a/src/main/java/sevenUnitsGUI/UnitConversionView.java b/src/main/java/sevenUnitsGUI/UnitConversionView.java index 6a95aa5..0d07823 100644 --- a/src/main/java/sevenUnitsGUI/UnitConversionView.java +++ b/src/main/java/sevenUnitsGUI/UnitConversionView.java @@ -23,23 +23,27 @@ import java.util.Set; * A View that supports single unit-based conversion * * @author Adrien Hopkins + * @since v0.4.0 * @since 2021-12-15 */ public interface UnitConversionView extends View { /** * @return dimensions available for filtering + * @since v0.4.0 * @since 2022-01-29 */ Set getDimensionNames(); /** * @return name of unit to convert from + * @since v0.4.0 * @since 2021-12-15 */ Optional getFromSelection(); /** * @return list of names of units available to convert from + * @since v0.4.0 * @since 2022-03-30 */ Set getFromUnitNames(); @@ -47,24 +51,28 @@ public interface UnitConversionView extends View { /** * @return value to convert between the units (specifically, the numeric * string provided by the user) + * @since v0.4.0 * @since 2021-12-15 */ String getInputValue(); /** * @return selected dimension + * @since v0.4.0 * @since 2021-12-15 */ Optional getSelectedDimensionName(); /** * @return name of unit to convert to + * @since v0.4.0 * @since 2021-12-15 */ Optional getToSelection(); /** * @return list of names of units available to convert to + * @since v0.4.0 * @since 2022-03-30 */ Set getToUnitNames(); @@ -73,6 +81,7 @@ 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 */ void setDimensionNames(Set dimensionNames); @@ -83,6 +92,7 @@ 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 */ void setFromUnitNames(Set unitNames); @@ -93,6 +103,7 @@ 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 */ void setToUnitNames(Set unitNames); @@ -102,6 +113,7 @@ public interface UnitConversionView extends View { * * @param input input unit & value (obtained from this view) * @param output output unit & value + * @since v0.4.0 * @since 2021-12-24 */ void showUnitConversionOutput(UnitConversionRecord uc); diff --git a/src/main/java/sevenUnitsGUI/View.java b/src/main/java/sevenUnitsGUI/View.java index b2d2b94..bb810ec 100644 --- a/src/main/java/sevenUnitsGUI/View.java +++ b/src/main/java/sevenUnitsGUI/View.java @@ -26,11 +26,13 @@ import sevenUnits.utils.NameSymbol; * An object that controls user interaction with 7Units * * @author Adrien Hopkins + * @since v0.4.0 * @since 2021-12-15 */ public interface View { /** * @return a new tabbed view + * @since v0.4.0 * @since 2022-04-19 */ static View createTabbedView() { @@ -39,18 +41,21 @@ public interface View { /** * @return the presenter associated with this view + * @since v0.4.0 * @since 2022-04-19 */ Presenter getPresenter(); /** * @return name of prefix currently being viewed + * @since v0.4.0 * @since 2022-04-10 */ Optional getViewedPrefixName(); /** * @return name of unit currently being viewed + * @since v0.4.0 * @since 2022-04-10 */ Optional getViewedUnitName(); @@ -60,6 +65,7 @@ public interface View { * viewer * * @param prefixNames prefix names to view + * @since v0.4.0 * @since 2022-04-10 */ void setViewablePrefixNames(Set prefixNames); @@ -68,6 +74,7 @@ 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 */ void setViewableUnitNames(Set unitNames); @@ -78,6 +85,7 @@ 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 */ void showErrorMessage(String title, String message); @@ -87,6 +95,7 @@ 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 */ void showPrefix(NameSymbol name, String multiplierString); @@ -98,6 +107,7 @@ 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 */ void showUnit(NameSymbol name, String definition, String dimensionName, diff --git a/src/main/java/sevenUnitsGUI/ViewBot.java b/src/main/java/sevenUnitsGUI/ViewBot.java index a3ba7a2..e7304c4 100644 --- a/src/main/java/sevenUnitsGUI/ViewBot.java +++ b/src/main/java/sevenUnitsGUI/ViewBot.java @@ -32,6 +32,7 @@ import sevenUnits.utils.Nameable; * for testing. Getters and setters work as expected. * * @author Adrien Hopkins + * @since v0.4.0 * @since 2022-01-29 */ public final class ViewBot -- cgit v1.2.3