summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdrien Hopkins <adrien.p.hopkins@gmail.com>2019-04-13 12:17:14 -0400
committerAdrien Hopkins <adrien.p.hopkins@gmail.com>2019-04-13 12:17:14 -0400
commite0c5021a9ba85debf0c0722d78f75a0dbcc8376b (patch)
treefff7da15995839b099f1e5b66111e94da9b9b3b2 /src
parent8e613844ae19a4dea2089ac34c1f0ae650eaeae7 (diff)
Implemented the dimension-based converter.
Also added a search box list, and fixed a bug with dimension exponentiation.
Diffstat (limited to 'src')
-rwxr-xr-xsrc/org/unitConverter/UnitsDatabase.java2
-rwxr-xr-xsrc/org/unitConverter/converterGUI/DelegateListModel.java10
-rw-r--r--src/org/unitConverter/converterGUI/MutablePredicate.java60
-rw-r--r--src/org/unitConverter/converterGUI/SearchBoxList.java205
-rwxr-xr-xsrc/org/unitConverter/converterGUI/UnitConverterGUI.java180
5 files changed, 419 insertions, 38 deletions
diff --git a/src/org/unitConverter/UnitsDatabase.java b/src/org/unitConverter/UnitsDatabase.java
index 626f145..c3d3131 100755
--- a/src/org/unitConverter/UnitsDatabase.java
+++ b/src/org/unitConverter/UnitsDatabase.java
@@ -65,7 +65,7 @@ public final class UnitsDatabase {
final double exponent = exponentUnit.getConversionFactor();
if (DecimalComparison.equals(exponent % 1, 0))
// then exponentiate
- return base.toExponent((int) (exponent % 1 + 0.5));
+ return base.toExponent((int) (exponent + 0.5));
else
// not an integer
throw new UnsupportedOperationException("Decimal exponents are currently not supported.");
diff --git a/src/org/unitConverter/converterGUI/DelegateListModel.java b/src/org/unitConverter/converterGUI/DelegateListModel.java
index e375126..b80f63d 100755
--- a/src/org/unitConverter/converterGUI/DelegateListModel.java
+++ b/src/org/unitConverter/converterGUI/DelegateListModel.java
@@ -16,6 +16,7 @@
*/
package org.unitConverter.converterGUI;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -50,6 +51,15 @@ final class DelegateListModel<E> extends AbstractListModel<E> implements List<E>
private final List<E> delegate;
/**
+ * Creates an empty {@code DelegateListModel}.
+ *
+ * @since 2019-04-13
+ */
+ public DelegateListModel() {
+ this(new ArrayList<>());
+ }
+
+ /**
* Creates the {@code DelegateListModel}.
*
* @param delegate
diff --git a/src/org/unitConverter/converterGUI/MutablePredicate.java b/src/org/unitConverter/converterGUI/MutablePredicate.java
new file mode 100644
index 0000000..157903c
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/MutablePredicate.java
@@ -0,0 +1,60 @@
+/**
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package org.unitConverter.converterGUI;
+
+import java.util.function.Predicate;
+
+/**
+ * A container for a predicate, which can be changed later.
+ *
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+final class MutablePredicate<T> implements Predicate<T> {
+ private Predicate<T> predicate;
+
+ /**
+ * Creates the {@code MutablePredicate}.
+ *
+ * @since 2019-04-13
+ */
+ public MutablePredicate(final Predicate<T> predicate) {
+ this.predicate = predicate;
+ }
+
+ /**
+ * @return predicate
+ * @since 2019-04-13
+ */
+ public final Predicate<T> getPredicate() {
+ return this.predicate;
+ }
+
+ /**
+ * @param predicate
+ * new value of predicate
+ * @since 2019-04-13
+ */
+ public final void setPredicate(final Predicate<T> predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public boolean test(final T t) {
+ return this.predicate.test(t);
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/SearchBoxList.java b/src/org/unitConverter/converterGUI/SearchBoxList.java
new file mode 100644
index 0000000..7d3b748
--- /dev/null
+++ b/src/org/unitConverter/converterGUI/SearchBoxList.java
@@ -0,0 +1,205 @@
+/**
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package org.unitConverter.converterGUI;
+
+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.function.Predicate;
+
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-04-13
+ */
+final class SearchBoxList extends JPanel {
+
+ /**
+ * @since 2019-04-13
+ */
+ private static final long serialVersionUID = 6226930279415983433L;
+
+ /**
+ * The text to place in an empty search box.
+ */
+ private static final String EMPTY_TEXT = "Search...";
+ /**
+ * The color to use for an empty foreground.
+ */
+ private static final Color EMPTY_FOREGROUND = new Color(192, 192, 192);
+
+ // the components
+ private final Collection<String> itemsToFilter;
+ private final DelegateListModel<String> listModel;
+ private final JTextField searchBox;
+ private final JList<String> 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<String> searchFilter = o -> true;
+
+ /**
+ * Creates the {@code SearchBoxList}.
+ *
+ * @since 2019-04-13
+ */
+ public SearchBoxList(final Collection<String> itemsToFilter) {
+ super(new BorderLayout(), true);
+ this.itemsToFilter = itemsToFilter;
+
+ // 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
+ */
+ public void addSearchFilter(final Predicate<String> filter) {
+ this.searchFilter = this.searchFilter.and(filter);
+ }
+
+ /**
+ * Resets the search filter.
+ *
+ * @since 2019-04-13
+ */
+ public void clearSearchFilters() {
+ this.searchFilter = o -> true;
+ }
+
+ /**
+ * @return value selected in item list
+ * @since 2019-04-13
+ */
+ public String getSelectedValue() {
+ return this.searchItems.getSelectedValue();
+ }
+
+ /**
+ * Re-applies the filters.
+ *
+ * @since 2019-04-13
+ */
+ public void reapplyFilter() {
+ final String searchText = this.searchBoxEmpty ? "" : this.searchBox.getText();
+ final FilterComparator comparator = new FilterComparator(searchText);
+
+ this.listModel.clear();
+ this.itemsToFilter.forEach(string -> {
+ if (string.toLowerCase().contains(searchText.toLowerCase())) {
+ this.listModel.add(string);
+ }
+ });
+
+ // applies the custom filters
+ this.listModel.removeIf(this.searchFilter.negate());
+
+ // sorts the remaining items
+ this.listModel.sort(comparator);
+ }
+
+ /**
+ * Runs whenever the search box gains focus.
+ *
+ * @param e
+ * focus event
+ * @since 2019-04-13
+ */
+ 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
+ */
+ private void searchBoxFocusLost(final FocusEvent e) {
+ this.searchBoxFocused = false;
+ if (this.searchBoxEmpty) {
+ this.searchBox.setText(EMPTY_TEXT);
+ this.searchBox.setForeground(EMPTY_FOREGROUND);
+ }
+ }
+
+ 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);
+
+ // initialize list with items that match the filter then sort
+ this.listModel.clear();
+ this.itemsToFilter.forEach(string -> {
+ if (string.toLowerCase().contains(searchText.toLowerCase())) {
+ this.listModel.add(string);
+ }
+ });
+
+ // applies the custom filters
+ this.listModel.removeIf(this.searchFilter.negate());
+
+ // sorts the remaining items
+ this.listModel.sort(comparator);
+ }
+}
diff --git a/src/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
index 4f5ebeb..9314510 100755
--- a/src/org/unitConverter/converterGUI/UnitConverterGUI.java
+++ b/src/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -25,6 +25,7 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
import java.util.function.Predicate;
import javax.swing.BorderFactory;
@@ -116,9 +117,7 @@ final class UnitConverterGUI {
this.units.addDimension("LENGTH", StandardDimensions.LENGTH);
this.units.addDimension("MASS", StandardDimensions.MASS);
this.units.addDimension("TIME", StandardDimensions.TIME);
- this.units.addDimension("ELECTRIC_CURRENT", StandardDimensions.ELECTRIC_CURRENT);
this.units.addDimension("TEMPERATURE", StandardDimensions.TEMPERATURE);
- this.units.addDimension("QUANTITY", StandardDimensions.QUANTITY);
this.units.addDimension("LUMINOUS_INTENSITY", StandardDimensions.LUMINOUS_INTENSITY);
this.units.loadUnitsFile(new File("unitsfile.txt"));
@@ -239,6 +238,52 @@ final class UnitConverterGUI {
}
/**
+ * Converts in the dimension-based converter
+ *
+ * @since 2019-04-13
+ */
+ public final void convertDimensionBased() {
+ final String fromSelection = this.view.getFromSelection();
+ if (fromSelection == null) {
+ this.view.showErrorDialog("Error", "No unit selected in From field");
+ return;
+ }
+ final String toSelection = this.view.getToSelection();
+ if (toSelection == null) {
+ this.view.showErrorDialog("Error", "No unit selected in To field");
+ return;
+ }
+
+ final Unit from = this.units.getUnit(fromSelection);
+ final Unit to = this.units.getUnit(toSelection);
+
+ final String input = this.view.getDimensionBasedInput();
+ if (input.equals("")) {
+ this.view.showErrorDialog("Error", "No value to convert entered.");
+ return;
+ }
+ final double beforeValue = Double.parseDouble(input);
+ final double value = to.convertFromBase(from.convertToBase(beforeValue));
+
+ // round value
+ final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures));
+ String output = bigValue.toString();
+
+ // remove trailing zeroes
+ if (output.contains(".")) {
+ while (output.endsWith("0")) {
+ output = output.substring(0, output.length() - 1);
+ }
+ if (output.endsWith(".")) {
+ output = output.substring(0, output.length() - 1);
+ }
+ }
+
+ this.view.setDimensionBasedOutputText(
+ String.format("%s %s = %s %s", input, fromSelection, output, toSelection));
+ }
+
+ /**
* @return a list of all of the unit dimensions
* @since 2019-04-13
*/
@@ -366,6 +411,23 @@ final class UnitConverterGUI {
}
/**
+ * 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
+ */
+ public boolean unitMatchesDimension(final String unitName, final String dimensionName) {
+ final Unit unit = this.units.getUnit(unitName);
+ final UnitDimension dimension = this.units.getDimension(dimensionName);
+ return unit.getDimension().equals(dimension);
+ }
+
+ /**
* Runs whenever a unit is selected in the viewer.
* <p>
* Shows its information in the text box to the right.
@@ -385,6 +447,10 @@ final class UnitConverterGUI {
this.view.setUnitTextBoxText(unit.toString());
}
}
+
+ public final Set<String> unitNameSet() {
+ return this.units.prefixlessUnitNameSet();
+ }
}
private static class View {
@@ -405,6 +471,14 @@ final class UnitConverterGUI {
private final JTextField prefixFilterEntry;
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
+ /** 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 panel for inputting values in the dimension-based converter */
+ private final JTextField valueInput;
+ /** The output area in the dimension-based converter */
+ private final JTextArea dimensionBasedOutput;
/** The "From" entry in the conversion panel */
private final JTextField fromEntry;
/** The "To" entry in the conversion panel */
@@ -430,6 +504,10 @@ final class UnitConverterGUI {
this.unitTextBox = new JTextArea();
this.prefixFilterEntry = new JTextField();
this.prefixTextBox = new JTextArea();
+ this.fromSearch = new SearchBoxList(this.presenter.unitNameSet());
+ this.toSearch = new SearchBoxList(this.presenter.unitNameSet());
+ this.valueInput = new JFormattedTextField(new DecimalFormat("###############0.################"));
+ this.dimensionBasedOutput = new JTextArea(2, 32);
this.fromEntry = new JTextField();
this.toEntry = new JTextField();
this.output = new JTextArea(2, 32);
@@ -441,6 +519,22 @@ final class UnitConverterGUI {
}
/**
+ * @return value in dimension-based converter
+ * @since 2019-04-13
+ */
+ public String getDimensionBasedInput() {
+ return this.valueInput.getText();
+ }
+
+ /**
+ * @return selection in "From" selector in dimension-based converter
+ * @since 2019-04-13
+ */
+ public String getFromSelection() {
+ return this.fromSearch.getSelectedValue();
+ }
+
+ /**
* @return text in "From" box in converter panel
* @since 2019-01-15
* @since v0.1.0
@@ -468,6 +562,14 @@ final class UnitConverterGUI {
}
/**
+ * @return selection in "To" selector in dimension-based converter
+ * @since 2019-04-13
+ */
+ public String getToSelection() {
+ return this.toSearch.getSelectedValue();
+ }
+
+ /**
* @return text in "To" box in converter panel
* @since 2019-01-26
* @since v0.1.0
@@ -531,22 +633,17 @@ final class UnitConverterGUI {
inputPanel.setLayout(new GridLayout(1, 3));
- { // panel for From things
- final JPanel fromPanel = new JPanel();
- inputPanel.add(fromPanel);
+ final JComboBox<String> dimensionSelector = new JComboBox<>(
+ this.presenter.dimensionNameList().toArray(new String[0]));
+ dimensionSelector.setSelectedItem("LENGTH");
- fromPanel.setLayout(new BorderLayout());
+ // handle dimension filter
+ final MutablePredicate<String> dimensionFilter = new MutablePredicate<>(s -> true);
- { // search box for from
- final JTextField fromSearch = new JTextField("Search...");
- fromPanel.add(fromSearch, BorderLayout.PAGE_START);
- }
+ // panel for From things
+ inputPanel.add(this.fromSearch);
- { // list for From units
- final JList<String> fromList = new JList<>();
- fromPanel.add(fromList, BorderLayout.CENTER);
- }
- }
+ this.fromSearch.addSearchFilter(dimensionFilter);
{ // for dimension selector and arrow that represents conversion
final JPanel inBetweenPanel = new JPanel();
@@ -555,10 +652,6 @@ final class UnitConverterGUI {
inBetweenPanel.setLayout(new BorderLayout());
{ // dimension selector
- final List<String> dimensionNameList = this.presenter.dimensionNameList();
- dimensionNameList.add(0, "Select a dimension...");
- final JComboBox<String> dimensionSelector = new JComboBox<>(
- dimensionNameList.toArray(new String[0]));
inBetweenPanel.add(dimensionSelector, BorderLayout.PAGE_START);
}
@@ -568,23 +661,25 @@ final class UnitConverterGUI {
}
}
- { // panel for To things
- final JPanel toPanel = new JPanel();
- inputPanel.add(toPanel);
+ // panel for To things
- toPanel.setLayout(new BorderLayout());
+ inputPanel.add(this.toSearch);
- { // search box for to
- final JTextField toSearch = new JTextField("Search...");
- toPanel.add(toSearch, BorderLayout.PAGE_START);
- }
+ this.toSearch.addSearchFilter(dimensionFilter);
- { // list for To units
- final JList<String> toList = new JList<>();
- toPanel.add(toList, BorderLayout.CENTER);
- }
- }
+ // code for dimension filter
+ dimensionSelector.addItemListener(e -> {
+ dimensionFilter.setPredicate(string -> View.this.presenter.unitMatchesDimension(string,
+ (String) dimensionSelector.getSelectedItem()));
+ this.fromSearch.reapplyFilter();
+ this.toSearch.reapplyFilter();
+ });
+ // apply the item listener once because I have a default selection
+ dimensionFilter.setPredicate(string -> View.this.presenter.unitMatchesDimension(string,
+ (String) dimensionSelector.getSelectedItem()));
+ this.fromSearch.reapplyFilter();
+ this.toSearch.reapplyFilter();
}
{ // panel for submit and output, and also value entry
@@ -605,20 +700,20 @@ final class UnitConverterGUI {
}
{ // value to convert
- final JTextField valueInput = new JFormattedTextField(
- new DecimalFormat("###############0.################"));
- valueInputPanel.add(valueInput, BorderLayout.CENTER);
+ valueInputPanel.add(this.valueInput, BorderLayout.CENTER);
}
}
{ // button to convert
final JButton convertButton = new JButton("Convert");
outputPanel.add(convertButton);
+
+ convertButton.addActionListener(e -> this.presenter.convertDimensionBased());
}
{ // output of conversion
- final JLabel outputLabel = new JLabel();
- outputPanel.add(outputLabel);
+ outputPanel.add(this.dimensionBasedOutput);
+ this.dimensionBasedOutput.setEditable(false);
}
}
}
@@ -763,6 +858,17 @@ final class UnitConverterGUI {
}
/**
+ * Sets the text in the output of the dimension-based converter.
+ *
+ * @param text
+ * text to set
+ * @since 2019-04-13
+ */
+ public void setDimensionBasedOutputText(final String text) {
+ this.dimensionBasedOutput.setText(text);
+ }
+
+ /**
* Sets the text in the output of the conversion panel.
*
* @param text