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