/** * Copyright (C) 2018 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package 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 unitNames; /** The names of all of the units, but filtered */ private final DelegateListModel unitNamesFiltered; /** The names of all of the prefixes */ private final List prefixNames; /** The names of all of the prefixes */ private final DelegateListModel prefixNamesFiltered; private final Comparator 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 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 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 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 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 unitNameList; private final JList 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(); } }