/**
* 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();
}
}