diff options
-rw-r--r-- | dimensionfile.txt | 10 | ||||
-rwxr-xr-x | src/org/unitConverter/UnitsDatabase.java | 2 | ||||
-rwxr-xr-x | src/org/unitConverter/converterGUI/DelegateListModel.java | 10 | ||||
-rw-r--r-- | src/org/unitConverter/converterGUI/MutablePredicate.java | 60 | ||||
-rw-r--r-- | src/org/unitConverter/converterGUI/SearchBoxList.java | 205 | ||||
-rwxr-xr-x | src/org/unitConverter/converterGUI/UnitConverterGUI.java | 180 | ||||
-rwxr-xr-x | unitsfile.txt | 20 |
7 files changed, 444 insertions, 43 deletions
diff --git a/dimensionfile.txt b/dimensionfile.txt index d3c068c..7a1da10 100644 --- a/dimensionfile.txt +++ b/dimensionfile.txt @@ -4,10 +4,14 @@ # ! means "look for an existing dimension which I will load at the start" # This is necessary because every dimension must be defined by others, and I need somewhere to start. +# I have excluded electric current and quantity since their units are exclusively SI. + LENGTH ! MASS ! TIME ! -ELECTRIC_CURRENT ! TEMPERATURE ! -QUANTITY ! -LUMINOUS_INTENSITY !
\ No newline at end of file +LUMINOUS_INTENSITY ! + +# Derived Dimensions +VELOCITY LENGTH / TIME +ENERGY MASS * VELOCITY^2
\ No newline at end of file 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 diff --git a/unitsfile.txt b/unitsfile.txt index 2455c8a..14fb6fb 100755 --- a/unitsfile.txt +++ b/unitsfile.txt @@ -171,7 +171,7 @@ arcsecond 1 / 60 arcminute arcsec arcsecond # constants -waterdensity 1 kilogram / litre +waterdensity kilogram / litre # Imperial length units foot 0.3048 m @@ -183,6 +183,10 @@ yd yard mile 1760 yard mi mile +# Compressed notation +kph km / hour +mph mile / hour + # Imperial weight units pound 0.45359237 kg lb pound @@ -231,4 +235,16 @@ metricpint 2 metriccup pint metricpint metricquart 2 metricpint quart metricquart -metricgallon 4 metricquart
\ No newline at end of file +metricgallon 4 metricquart + +# Energy units +calorie 4.18 J +cal calorie +Calorie kilocalorie +Cal Calorie + +# Extra units to only include in the dimension-based converter +m/s m / s +km/h km / h +ft/s foot / s +mi/h mile / hour
\ No newline at end of file |