diff options
Diffstat (limited to 'src/unitConverter/converterGUI')
-rwxr-xr-x | src/unitConverter/converterGUI/DelegateListModel.java | 228 | ||||
-rwxr-xr-x | src/unitConverter/converterGUI/FilterComparator.java | 72 | ||||
-rwxr-xr-x | src/unitConverter/converterGUI/GridBagBuilder.java | 432 | ||||
-rwxr-xr-x | src/unitConverter/converterGUI/UnitConverterGUI.java | 586 | ||||
-rw-r--r-- | src/unitConverter/converterGUI/package-info.java | 23 |
5 files changed, 1341 insertions, 0 deletions
diff --git a/src/unitConverter/converterGUI/DelegateListModel.java b/src/unitConverter/converterGUI/DelegateListModel.java new file mode 100755 index 0000000..42bc0dc --- /dev/null +++ b/src/unitConverter/converterGUI/DelegateListModel.java @@ -0,0 +1,228 @@ +/** + * 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 <https://www.gnu.org/licenses/>. + */ +package unitConverter.converterGUI; + +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. + * <p> + * 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. + * </p> + * + * @author Adrien Hopkins + * @since 2019-01-14 + */ +final class DelegateListModel<E> extends AbstractListModel<E> implements List<E> { + /** + * @since 2019-01-14 + */ + private static final long serialVersionUID = 8985494428224810045L; + + /** + * The list that this model is a delegate to. + * + * @since 2019-01-14 + */ + private final List<E> delegate; + + /** + * Creates the {@code DelegateListModel}. + * + * @param delegate + * list to delegate + * @since 2019-01-14 + */ + public DelegateListModel(final List<E> 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<? extends E> 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<? extends E> 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<E> iterator() { + return this.delegate.iterator(); + } + + @Override + public int lastIndexOf(final Object elem) { + return this.delegate.lastIndexOf(elem); + } + + @Override + public ListIterator<E> listIterator() { + return this.delegate.listIterator(); + } + + @Override + public ListIterator<E> 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<E> subList(final int fromIndex, final int toIndex) { + return this.delegate.subList(fromIndex, toIndex); + } + + @Override + public Object[] toArray() { + return this.delegate.toArray(); + } + + @Override + public <T> T[] toArray(final T[] a) { + return this.delegate.toArray(a); + } + + @Override + public String toString() { + return this.delegate.toString(); + } +} diff --git a/src/unitConverter/converterGUI/FilterComparator.java b/src/unitConverter/converterGUI/FilterComparator.java new file mode 100755 index 0000000..ab25f2f --- /dev/null +++ b/src/unitConverter/converterGUI/FilterComparator.java @@ -0,0 +1,72 @@ +/**
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package unitConverter.converterGUI;
+
+import java.util.Comparator;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2019-01-15
+ */
+public final class FilterComparator implements Comparator<String> {
+
+ private final String filter;
+ private final Comparator<String> comparator;
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * @since 2019-01-15
+ */
+ public FilterComparator(final String filter) {
+ this(filter, null);
+ }
+
+ /**
+ * Creates the {@code FilterComparator}.
+ *
+ * @param filter
+ * @param comparator
+ * @since 2019-01-15
+ */
+ public FilterComparator(final String filter, final Comparator<String> comparator) {
+ this.filter = filter;
+ this.comparator = comparator;
+ }
+
+ @Override
+ public int compare(final String arg0, final String arg1) {
+ // elements that start with the filter always go first
+ if (arg0.startsWith(this.filter) && !arg1.startsWith(this.filter))
+ return -1;
+ else if (!arg0.startsWith(this.filter) && arg1.startsWith(this.filter))
+ return 1;
+
+ // elements that contain the filter but don't start with them go next
+ if (arg0.contains(this.filter) && !arg1.contains(this.filter))
+ return -1;
+ else if (!arg0.contains(this.filter) && !arg1.contains(this.filter))
+ return 1;
+
+ // other elements go last
+ if (this.comparator == null)
+ return arg0.compareTo(arg1);
+ else
+ return this.comparator.compare(arg0, arg1);
+ }
+}
diff --git a/src/unitConverter/converterGUI/GridBagBuilder.java b/src/unitConverter/converterGUI/GridBagBuilder.java new file mode 100755 index 0000000..7a0615a --- /dev/null +++ b/src/unitConverter/converterGUI/GridBagBuilder.java @@ -0,0 +1,432 @@ +/** + * 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 <https://www.gnu.org/licenses/>. + */ +package unitConverter.converterGUI; + +import java.awt.GridBagConstraints; +import java.awt.Insets; + +/** + * @author Adrien Hopkins + * @since 2018-11-30 + */ +final class GridBagBuilder { + /** + * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has + * <code>gridx=0</code>. 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 + * <code>RELATIVE</code> specifies that the component be placed immediately following the component that was added + * to the container just before this component was added. + * <p> + * The default value is <code>RELATIVE</code>. <code>gridx</code> should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridy + * @see java.awt.ComponentOrientation + */ + private final int gridx; + + /** + * Specifies the cell at the top of the component's display area, where the topmost cell has <code>gridy=0</code>. + * The value <code>RELATIVE</code> specifies that the component be placed just below the component that was added to + * the container just before this component was added. + * <p> + * The default value is <code>RELATIVE</code>. <code>gridy</code> should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridx + */ + private final int gridy; + + /** + * Specifies the number of cells in a row for the component's display area. + * <p> + * Use <code>REMAINDER</code> to specify that the component's display area will be from <code>gridx</code> to the + * last cell in the row. Use <code>RELATIVE</code> to specify that the component's display area will be from + * <code>gridx</code> to the next to the last one in its row. + * <p> + * <code>gridwidth</code> should be non-negative and the default value is 1. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridheight + */ + private final int gridwidth; + + /** + * Specifies the number of cells in a column for the component's display area. + * <p> + * Use <code>REMAINDER</code> to specify that the component's display area will be from <code>gridy</code> to the + * last cell in the column. Use <code>RELATIVE</code> to specify that the component's display area will be from + * <code>gridy</code> to the next to the last one in its column. + * <p> + * <code>gridheight</code> should be a non-negative value and the default value is 1. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridwidth + */ + private final int gridheight; + + /** + * Specifies how to distribute extra horizontal space. + * <p> + * The grid bag layout manager calculates the weight of a column to be the maximum <code>weightx</code> 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. + * <p> + * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right + * edges. + * <p> + * The default value of this field is <code>0</code>. <code>weightx</code> should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#weighty + */ + private double weightx; + + /** + * Specifies how to distribute extra vertical space. + * <p> + * The grid bag layout manager calculates the weight of a row to be the maximum <code>weighty</code> 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. + * <p> + * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom + * edges. + * <p> + * The default value of this field is <code>0</code>. <code>weighty</code> should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#weightx + */ + private double weighty; + + /** + * This field is used when the component is smaller than its display area. It determines where, within the display + * area, to place the component. + * <p> + * 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: + * <code>CENTER</code>, <code>NORTH</code>, <code>NORTHEAST</code>, <code>EAST</code>, <code>SOUTHEAST</code>, + * <code>SOUTH</code>, <code>SOUTHWEST</code>, <code>WEST</code>, and <code>NORTHWEST</code>. The orientation + * relative values are: <code>PAGE_START</code>, <code>PAGE_END</code>, <code>LINE_START</code>, + * <code>LINE_END</code>, <code>FIRST_LINE_START</code>, <code>FIRST_LINE_END</code>, <code>LAST_LINE_START</code> + * and <code>LAST_LINE_END</code>. The baseline relative values are: <code>BASELINE</code>, + * <code>BASELINE_LEADING</code>, <code>BASELINE_TRAILING</code>, <code>ABOVE_BASELINE</code>, + * <code>ABOVE_BASELINE_LEADING</code>, <code>ABOVE_BASELINE_TRAILING</code>, <code>BELOW_BASELINE</code>, + * <code>BELOW_BASELINE_LEADING</code>, and <code>BELOW_BASELINE_TRAILING</code>. The default value is + * <code>CENTER</code>. + * + * @serial + * @see #clone() + * @see java.awt.ComponentOrientation + */ + private int anchor; + + /** + * 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. + * <p> + * The following values are valid for <code>fill</code>: + * + * <ul> + * <li><code>NONE</code>: Do not resize the component. + * <li><code>HORIZONTAL</code>: Make the component wide enough to fill its display area horizontally, but do not + * change its height. + * <li><code>VERTICAL</code>: Make the component tall enough to fill its display area vertically, but do not change + * its width. + * <li><code>BOTH</code>: Make the component fill its display area entirely. + * </ul> + * <p> + * The default value is <code>NONE</code>. + * + * @serial + * @see #clone() + */ + private int fill; + + /** + * This field specifies the external padding of the component, the minimum amount of space between the component and + * the edges of its display area. + * <p> + * The default value is <code>new Insets(0, 0, 0, 0)</code>. + * + * @serial + * @see #clone() + */ + private Insets insets; + + /** + * 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 <code>ipadx</code> pixels. + * <p> + * The default value is <code>0</code>. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#ipady + */ + private int ipadx; + + /** + * 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 <code>ipady</code> pixels. + * <p> + * 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + public int getAnchor() { + return this.anchor; + } + + /** + * @return fill + * @since 2018-11-30 + */ + public int getFill() { + return this.fill; + } + + /** + * @return gridheight + * @since 2018-11-30 + */ + public int getGridheight() { + return this.gridheight; + } + + /** + * @return gridwidth + * @since 2018-11-30 + */ + public int getGridwidth() { + return this.gridwidth; + } + + /** + * @return gridx + * @since 2018-11-30 + */ + public int getGridx() { + return this.gridx; + } + + /** + * @return gridy + * @since 2018-11-30 + */ + public int getGridy() { + return this.gridy; + } + + /** + * @return insets + * @since 2018-11-30 + */ + public Insets getInsets() { + return this.insets; + } + + /** + * @return ipadx + * @since 2018-11-30 + */ + public int getIpadx() { + return this.ipadx; + } + + /** + * @return ipady + * @since 2018-11-30 + */ + public int getIpady() { + return this.ipady; + } + + /** + * @return weightx + * @since 2018-11-30 + */ + public double getWeightx() { + return this.weightx; + } + + /** + * @return weighty + * @since 2018-11-30 + */ + public double getWeighty() { + return this.weighty; + } + + /** + * @param anchor + * anchor to set + * @since 2018-11-30 + */ + public GridBagBuilder setAnchor(final int anchor) { + this.anchor = anchor; + return this; + } + + /** + * @param fill + * fill to set + * @since 2018-11-30 + */ + public GridBagBuilder setFill(final int fill) { + this.fill = fill; + return this; + } + + /** + * @param insets + * insets to set + * @since 2018-11-30 + */ + public GridBagBuilder setInsets(final Insets insets) { + this.insets = insets; + return this; + } + + /** + * @param ipadx + * ipadx to set + * @since 2018-11-30 + */ + public GridBagBuilder setIpadx(final int ipadx) { + this.ipadx = ipadx; + return this; + } + + /** + * @param ipady + * ipady to set + * @since 2018-11-30 + */ + public GridBagBuilder setIpady(final int ipady) { + this.ipady = ipady; + return this; + } + + /** + * @param weightx + * weightx to set + * @since 2018-11-30 + */ + public GridBagBuilder setWeightx(final double weightx) { + this.weightx = weightx; + return this; + } + + /** + * @param weighty + * weighty to set + * @since 2018-11-30 + */ + public GridBagBuilder setWeighty(final double weighty) { + this.weighty = weighty; + return this; + } +} diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java new file mode 100755 index 0000000..0068312 --- /dev/null +++ b/src/unitConverter/converterGUI/UnitConverterGUI.java @@ -0,0 +1,586 @@ +/** + * 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 <https://www.gnu.org/licenses/>. + */ +package unitConverter.converterGUI; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.File; +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; + +import unitConverter.UnitsDatabase; +import unitConverter.dimension.StandardDimensions; +import unitConverter.dimension.UnitDimension; +import unitConverter.unit.AbstractUnit; +import unitConverter.unit.NonlinearUnits; +import unitConverter.unit.SI; +import unitConverter.unit.Unit; +import unitConverter.unit.UnitPrefix; + +/** + * @author Adrien Hopkins + * @since 2018-12-27 + */ +final class UnitConverterGUI { + private static class Presenter { + /** The presenter's associated view. */ + private final View view; + + /** The units known by the program. */ + private final UnitsDatabase units; + + /** The names of all of the units */ + private final List<String> unitNames; + + /** The names of all of the units, but filtered */ + private final DelegateListModel<String> unitNamesFiltered; + + /** The names of all of the prefixes */ + private final List<String> prefixNames; + + /** The names of all of the prefixes */ + private final DelegateListModel<String> prefixNamesFiltered; + + private final Comparator<String> prefixNameComparator; + + private int significantFigures = 6; + + /** + * Creates the presenter. + * + * @param view + * presenter's associated view + * @since 2018-12-27 + */ + Presenter(final View view) { + this.view = view; + + // load initial units + this.units = new UnitsDatabase(); + this.units.addUnit("metre", SI.METRE); + this.units.addUnit("kilogram", SI.KILOGRAM); + this.units.addUnit("gram", SI.KILOGRAM.dividedBy(1000)); + this.units.addUnit("second", SI.SECOND); + this.units.addUnit("ampere", SI.AMPERE); + this.units.addUnit("kelvin", SI.KELVIN); + this.units.addUnit("mole", SI.MOLE); + this.units.addUnit("candela", SI.CANDELA); + this.units.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION)); + this.units.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY)); + // nonlinear units - must be loaded manually + this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS); + this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT); + + this.units.addAllFromFile(new File("unitsfile.txt")); + + // a comparator that can be used to compare prefix names + // any name that does not exist is less than a name that does. + // otherwise, they are compared by value + this.prefixNameComparator = (o1, o2) -> { + if (!Presenter.this.units.containsPrefixName(o1)) + return -1; + else if (!Presenter.this.units.containsPrefixName(o2)) + return 1; + + final UnitPrefix p1 = Presenter.this.units.getPrefix(o1); + final UnitPrefix p2 = Presenter.this.units.getPrefix(o2); + + if (p1.getMultiplier() < p2.getMultiplier()) + return -1; + else if (p1.getMultiplier() > p2.getMultiplier()) + return 1; + + return o1.compareTo(o2); + }; + + this.unitNames = new ArrayList<>(this.units.prefixlessUnitNameSet()); + this.unitNames.sort(null); // sorts it using Comparable + + this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixlessUnitNameSet())); + this.unitNamesFiltered.sort(null); // sorts it using Comparable + + this.prefixNames = new ArrayList<>(this.units.prefixNameSet()); + this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator + + this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet())); + this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator + + System.out.printf("Successfully loaded %d units (%d base units)", AbstractUnit.getUnitCount(), + AbstractUnit.getBaseUnitCount()); + } + + public final void convert() { + final String fromUnitString = this.view.getFromText(); + final String toUnitString = this.view.getToText(); + + // try to parse from + final Unit from; + try { + from = this.units.getUnitFromExpression(fromUnitString); + } catch (final IllegalArgumentException e) { + this.view.showErrorDialog("Parse Error", "Could not recognize text in From entry: " + e.getMessage()); + return; + } + + final double value; + // try to parse to + final Unit to; + try { + to = this.units.getUnitFromExpression(toUnitString); + } catch (final IllegalArgumentException e) { + this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage()); + return; + } + + // if I can't convert, leave + if (!from.canConvertTo(to)) { + this.view.showErrorDialog("Conversion Error", + String.format("Cannot convert between %s and %s", fromUnitString, toUnitString)); + return; + } + + value = to.convertFromBase(from.convertToBase(1)); + + // 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.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString)); + } + + /** + * Filters the filtered model for units + * + * @param filter + * filter to use + * @since 2019-01-15 + */ + private final void filterFilteredPrefixModel(final Predicate<String> filter) { + this.prefixNamesFiltered.clear(); + for (final String prefixName : this.prefixNames) { + if (filter.test(prefixName)) { + this.prefixNamesFiltered.add(prefixName); + } + } + } + + /** + * Filters the filtered model for units + * + * @param filter + * filter to use + * @since 2019-01-15 + */ + private final void filterFilteredUnitModel(final Predicate<String> filter) { + this.unitNamesFiltered.clear(); + for (final String unitName : this.unitNames) { + if (filter.test(unitName)) { + this.unitNamesFiltered.add(unitName); + } + } + } + + /** + * @return a list model of all of the unit keys + * @since 2019-01-14 + */ + public final ListModel<String> keyListModel() { + return this.unitNamesFiltered; + } + + public final void prefixFilterUpdated() { + final String filter = this.view.getPrefixFilterText(); + if (filter.equals("")) { + this.filterFilteredPrefixModel(t -> true); + } else { + this.filterFilteredPrefixModel(t -> t.contains(filter)); + } + this.prefixNamesFiltered.sort(new FilterComparator(filter)); + } + + /** + * @return a list model of all fo the prefix names + * @since 2019-01-15 + */ + public final ListModel<String> prefixNameListModel() { + return this.prefixNamesFiltered; + } + + public final void prefixSelected() { + final int index = this.view.getPrefixListSelection(); + if (index == -1) + return; + else { + final String prefixName = this.prefixNamesFiltered.get(index); + final UnitPrefix prefix = this.units.getPrefix(prefixName); + + this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier())); + } + } + + /** + * @param significantFigures + * new value of significantFigures + * @since 2019-01-15 + */ + public final void setSignificantFigures(final int significantFigures) { + this.significantFigures = significantFigures; + } + + public final void unitFilterUpdated() { + final String filter = this.view.getUnitFilterText(); + if (filter.equals("")) { + this.filterFilteredUnitModel(t -> true); + } else { + this.filterFilteredUnitModel(t -> t.contains(filter)); + } + this.unitNamesFiltered.sort(new FilterComparator(filter)); + } + + /** + * + * @since 2019-01-15 + */ + public void unitNameSelected() { + final int index = this.view.getUnitListSelection(); + if (index == -1) + return; + else { + final String unitName = this.unitNamesFiltered.get(index); + final Unit unit = this.units.getUnit(unitName); + + this.view.setUnitTextBoxText(unit.toString()); + } + } + } + + private static class View { + /** The view's frame. */ + private final JFrame frame; + /** The view's associated presenter. */ + private final Presenter presenter; + + private final JList<String> unitNameList; + private final JList<String> prefixNameList; + private final JTextField unitFilterEntry; + private final JTextArea unitTextBox; + private final JTextField prefixFilterEntry; + private final JTextArea prefixTextBox; + private final JTextField fromEntry; + private final JTextField toEntry; + private final JTextArea output; + + /** + * Creates the {@code View}. + * + * @since 2019-01-14 + */ + public View() { + this.presenter = new Presenter(this); + this.frame = new JFrame("Unit Converter"); + this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + this.unitNameList = new JList<>(this.presenter.keyListModel()); + this.prefixNameList = new JList<>(this.presenter.prefixNameListModel()); + this.unitFilterEntry = new JTextField(); + this.unitTextBox = new JTextArea(); + this.prefixFilterEntry = new JTextField(); + this.prefixTextBox = new JTextArea(); + this.fromEntry = new JTextField(); + this.toEntry = new JTextField(); + this.output = new JTextArea(2, 32); + + this.initComponents(); + + this.frame.pack(); + } + + public String getFromText() { + return this.fromEntry.getText(); + } + + /** + * @return text in prefix filter + * @since 2019-01-15 + */ + public String getPrefixFilterText() { + return this.prefixFilterEntry.getText(); + } + + /** + * @return index of selected prefix + * @since 2019-01-15 + */ + public int getPrefixListSelection() { + return this.prefixNameList.getSelectedIndex(); + } + + public String getToText() { + return this.toEntry.getText(); + } + + /** + * @return text in unit filter + * @see javax.swing.text.JTextComponent#getText() + */ + public String getUnitFilterText() { + return this.unitFilterEntry.getText(); + } + + /** + * @return index of selected unit + * @since 2019-01-15 + */ + public int getUnitListSelection() { + return this.unitNameList.getSelectedIndex(); + } + + /** + * Starts up the application. + * + * @since 2018-12-27 + */ + public final void init() { + this.frame.setVisible(true); + } + + /** + * Initializes the view's components. + * + * @since 2018-12-27 + */ + private final void initComponents() { + final JPanel masterPanel = new JPanel(); + this.frame.add(masterPanel); + + masterPanel.setLayout(new BorderLayout()); + + { // pane with all of the tabs + final JTabbedPane masterPane = new JTabbedPane(); + masterPanel.add(masterPane, BorderLayout.CENTER); + + { // panel for unit conversion + final JPanel convertPanel = new JPanel(); + masterPane.addTab("Convert Units", convertPanel); + + convertPanel.setLayout(new GridLayout(5, 1)); + + { // panel for units to convert from + final JPanel fromPanel = new JPanel(); + convertPanel.add(fromPanel); + + fromPanel.setBorder(BorderFactory.createTitledBorder("From")); + fromPanel.setLayout(new GridLayout(1, 1)); + + { // entry for units + fromPanel.add(this.fromEntry); + } + } + + { // panel for units to convert to + final JPanel toPanel = new JPanel(); + convertPanel.add(toPanel); + + toPanel.setBorder(BorderFactory.createTitledBorder("To")); + toPanel.setLayout(new GridLayout(1, 1)); + + { // entry for units + toPanel.add(this.toEntry); + } + } + + { // button to convert + final JButton convertButton = new JButton("Convert!"); + convertPanel.add(convertButton); + + convertButton.addActionListener(e -> this.presenter.convert()); + } + + { // output of conversion + final JPanel outputPanel = new JPanel(); + convertPanel.add(outputPanel); + + outputPanel.setBorder(BorderFactory.createTitledBorder("Output")); + outputPanel.setLayout(new GridLayout(1, 1)); + + { // output + outputPanel.add(this.output); + this.output.setEditable(false); + } + } + + { + final JPanel sigDigPanel = new JPanel(); + convertPanel.add(sigDigPanel); + + sigDigPanel.setBorder(BorderFactory.createTitledBorder("Significant Digits")); + + { // slider + final JSlider sigDigSlider = new JSlider(0, 12); + sigDigPanel.add(sigDigSlider); + + sigDigSlider.setMajorTickSpacing(4); + sigDigSlider.setMinorTickSpacing(1); + sigDigSlider.setSnapToTicks(true); + sigDigSlider.setPaintTicks(true); + sigDigSlider.setPaintLabels(true); + + sigDigSlider.addChangeListener( + e -> this.presenter.setSignificantFigures(sigDigSlider.getValue())); + } + } + } + + { // panel to look up units + final JPanel unitLookupPanel = new JPanel(); + masterPane.addTab("Unit Viewer", unitLookupPanel); + + unitLookupPanel.setLayout(new GridLayout()); + + { // panel for listing and searching + final JPanel listPanel = new JPanel(); + unitLookupPanel.add(listPanel); + + listPanel.setLayout(new BorderLayout()); + + { + listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START); + this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated()); + } + + { // a list of units + listPanel.add(new JScrollPane(this.unitNameList), BorderLayout.CENTER); + this.unitNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp + this.unitNameList.addListSelectionListener(e -> { + this.presenter.unitNameSelected(); + }); + } + } + + { // the text box for unit's toString + unitLookupPanel.add(this.unitTextBox); + this.unitTextBox.setEditable(false); + this.unitTextBox.setLineWrap(true); + } + } + + { // panel to look up prefixes + final JPanel prefixLookupPanel = new JPanel(); + masterPane.addTab("Prefix Viewer", prefixLookupPanel); + + prefixLookupPanel.setLayout(new GridLayout(1, 2)); + + { + final JPanel prefixListPanel = new JPanel(); + prefixLookupPanel.add(prefixListPanel); + + prefixListPanel.setLayout(new BorderLayout()); + + { + prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START); + this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated()); + } + + { // a list of prefixes + prefixListPanel.add(new JScrollPane(this.prefixNameList), BorderLayout.CENTER); + this.prefixNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp + this.prefixNameList.addListSelectionListener(e -> { + this.presenter.prefixSelected(); + }); + } + } + + { // the text box for prefix's toString + prefixLookupPanel.add(this.prefixTextBox); + this.unitTextBox.setEditable(false); + } + } + } + } + + public void setOutputText(final String text) { + this.output.setText(text); + } + + /** + * Sets the text of the prefix text box. + * + * @param text + * text to set + * @since 2019-01-15 + */ + public void setPrefixTextBoxText(final String text) { + this.prefixTextBox.setText(text); + } + + /** + * Sets the text of the unit text box. + * + * @param t + * text to set + * @see javax.swing.text.JTextComponent#setText(java.lang.String) + */ + public void setUnitTextBoxText(final String t) { + this.unitTextBox.setText(t); + } + + /** + * Shows an error dialog. + * + * @param title + * title of dialog + * @param message + * message in dialog + * @since 2019-01-14 + */ + public void showErrorDialog(final String title, final String message) { + JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE); + } + } + + public static void main(final String[] args) { + new View().init(); + } +} diff --git a/src/unitConverter/converterGUI/package-info.java b/src/unitConverter/converterGUI/package-info.java new file mode 100644 index 0000000..9f7fa57 --- /dev/null +++ b/src/unitConverter/converterGUI/package-info.java @@ -0,0 +1,23 @@ +/** + * 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/>. + */ +/** + * All classes that work to convert units. + * + * @author Adrien Hopkins + * @since 2019-01-25 + */ +package unitConverter.converterGUI;
\ No newline at end of file |