summaryrefslogtreecommitdiff
path: root/src/unitConverter/converterGUI/UnitConverterGUI.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/unitConverter/converterGUI/UnitConverterGUI.java')
-rwxr-xr-xsrc/unitConverter/converterGUI/UnitConverterGUI.java671
1 files changed, 671 insertions, 0 deletions
diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java
new file mode 100755
index 0000000..b54e0da
--- /dev/null
+++ b/src/unitConverter/converterGUI/UnitConverterGUI.java
@@ -0,0 +1,671 @@
+/**
+ * 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
+ * @since v0.1.0
+ */
+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
+ * @since v0.1.0
+ */
+ 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());
+ }
+
+ /**
+ * Runs whenever the convert button is pressed.
+ *
+ * <p>
+ * Reads and parses a unit expression from the from and to boxes, then converts {@code from} to {@code to}. Any
+ * errors are shown in JOptionPanes.
+ * </p>
+ *
+ * @since 2019-01-26
+ * @since v0.1.0
+ */
+ 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
+ * @since v0.1.0
+ */
+ 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
+ * @since v0.1.0
+ */
+ 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
+ * @since v0.1.0
+ */
+ public final ListModel<String> keyListModel() {
+ return this.unitNamesFiltered;
+ }
+
+ /**
+ * Runs whenever the prefix filter is changed.
+ * <p>
+ * Filters the prefix list then sorts it using a {@code FilterComparator}.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ 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 of the prefix names
+ * @since 2019-01-15
+ */
+ public final ListModel<String> prefixNameListModel() {
+ return this.prefixNamesFiltered;
+ }
+
+ /**
+ * Runs whenever a prefix is selected in the viewer.
+ * <p>
+ * Shows its information in the text box to the right.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ 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;
+ }
+
+ /**
+ * Runs whenever the unit filter is changed.
+ * <p>
+ * Filters the unit list then sorts it using a {@code FilterComparator}.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ 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));
+ }
+
+ /**
+ * Runs whenever a unit is selected in the viewer.
+ * <p>
+ * Shows its information in the text box to the right.
+ * </p>
+ *
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ 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;
+
+ /** The list of unit names in the unit viewer */
+ private final JList<String> unitNameList;
+ /** The list of prefix names in the prefix viewer */
+ private final JList<String> prefixNameList;
+ /** The unit search box in the unit viewer */
+ private final JTextField unitFilterEntry;
+ /** The text box for unit data in the unit viewer */
+ private final JTextArea unitTextBox;
+ /** The prefix search box in the prefix viewer */
+ private final JTextField prefixFilterEntry;
+ /** The text box for prefix data in the prefix viewer */
+ private final JTextArea prefixTextBox;
+ /** 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 output;
+
+ /**
+ * Creates the {@code View}.
+ *
+ * @since 2019-01-14
+ * @since v0.1.0
+ */
+ public View() {
+ this.presenter = new Presenter(this);
+ this.frame = new JFrame("Unit Converter");
+ this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ // create the components
+ 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);
+
+ // create more components
+ this.initComponents();
+
+ this.frame.pack();
+ }
+
+ /**
+ * @return text in "From" box in converter panel
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public String getFromText() {
+ return this.fromEntry.getText();
+ }
+
+ /**
+ * @return text in prefix filter
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public String getPrefixFilterText() {
+ return this.prefixFilterEntry.getText();
+ }
+
+ /**
+ * @return index of selected prefix
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public int getPrefixListSelection() {
+ return this.prefixNameList.getSelectedIndex();
+ }
+
+ /**
+ * @return text in "To" box in converter panel
+ * @since 2019-01-26
+ * @since v0.1.0
+ */
+ 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
+ * @since v0.1.0
+ */
+ public int getUnitListSelection() {
+ return this.unitNameList.getSelectedIndex();
+ }
+
+ /**
+ * Starts up the application.
+ *
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ public final void init() {
+ this.frame.setVisible(true);
+ }
+
+ /**
+ * Initializes the view's components.
+ *
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+ 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);
+ }
+ }
+
+ { // panel for specifying precision
+ 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());
+
+ { // unit search box
+ 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));
+
+ { // panel for listing and seaching
+ final JPanel prefixListPanel = new JPanel();
+ prefixLookupPanel.add(prefixListPanel);
+
+ prefixListPanel.setLayout(new BorderLayout());
+
+ { // prefix search box
+ 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);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the text in the output of the conversion panel.
+ *
+ * @param text
+ * text to set
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ 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
+ * @since v0.1.0
+ */
+ 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
+ * @since v0.1.0
+ */
+ 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();
+ }
+}