isFullBase = unit -> unit instanceof LinearUnit && ((LinearUnit) unit).isBase();
// print out unit counts
System.out.printf("Successfully loaded %d units with %d unit names (%d base units).%n",
new HashSet<>(this.database.unitMapPrefixless().values()).size(),
this.database.unitMapPrefixless().size(),
new HashSet<>(this.database.unitMapPrefixless().values()).stream().filter(isFullBase).count());
}
/**
* Converts in the dimension-based converter
*
* @since 2019-04-13
* @since v0.2.0
*/
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.database.getUnit(fromSelection);
final Unit to = this.database.getUnit(toSelection);
final String input = this.view.getDimensionConverterInput();
if (input.equals("")) {
this.view.showErrorDialog("Error", "No value to convert entered.");
return;
}
final double beforeValue = Double.parseDouble(input);
final double value = from.convertTo(to, beforeValue);
final String output = this.getRoundedString(value);
this.view.setDimensionConverterOutputText(
String.format("%s %s = %s %s", input, fromSelection, output, toSelection));
}
/**
* Runs whenever the convert button is pressed.
*
*
* 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.
*
*
* @since 2019-01-26
* @since v0.1.0
*/
public final void convertExpressions() {
final String fromUnitString = this.view.getFromText();
final String toUnitString = this.view.getToText();
if (fromUnitString.isEmpty()) {
this.view.showErrorDialog("Parse Error", "Please enter a unit expression in the From: box.");
return;
}
if (toUnitString.isEmpty()) {
this.view.showErrorDialog("Parse Error", "Please enter a unit expression in the To: box.");
return;
}
// try to parse from
final Unit from;
try {
from = this.database.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 {
if (this.database.containsUnitName(toUnitString)) {
// if it's a unit, convert to that
to = this.database.getUnit(toUnitString);
} else {
to = this.database.getUnitFromExpression(toUnitString);
}
} catch (final IllegalArgumentException e) {
this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage());
return;
}
if (from.canConvertTo(to)) {
value = from.convertTo(to, 1);
// round value
final String output = this.getRoundedString(value);
this.view.setExpressionConverterOutputText(
String.format("%s = %s %s", fromUnitString, output, toUnitString));
} else if (from instanceof LinearUnit && SI.ONE.dividedBy((LinearUnit) from).canConvertTo(to)) {
// reciprocal conversion (like seconds to hertz)
value = SI.ONE.dividedBy((LinearUnit) from).convertTo(to, 1);
// round value
final String output = this.getRoundedString(value);
this.view.setExpressionConverterOutputText(
String.format("1 / %s = %s %s", fromUnitString, output, toUnitString));
} else {
// if I can't convert, leave
this.view.showErrorDialog("Conversion Error",
String.format("Cannot convert between %s and %s", fromUnitString, toUnitString));
}
}
/**
* @return a list of all of the unit dimensions
* @since 2019-04-13
* @since v0.2.0
*/
public final List dimensionNameList() {
return this.dimensionNames;
}
/**
* @return a comparator to compare prefix names
* @since 2019-04-14
* @since v0.2.0
*/
public final Comparator getPrefixNameComparator() {
return this.prefixNameComparator;
}
/**
* @param value value to round
* @return string of that value rounded to {@code significantDigits} significant
* digits.
* @since 2019-04-14
* @since v0.2.0
*/
private final String getRoundedString(final double value) {
// 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);
}
}
return output;
}
/**
* @return a set of all prefix names in the database
* @since 2019-04-14
* @since v0.2.0
*/
public final Set prefixNameSet() {
return this.database.prefixMap().keySet();
}
/**
* Runs whenever a prefix is selected in the viewer.
*
* Shows its information in the text box to the right.
*
*
* @since 2019-01-15
* @since v0.1.0
*/
public final void prefixSelected() {
final String prefixName = this.view.getPrefixViewerSelection();
if (prefixName == null)
return;
else {
final UnitPrefix prefix = this.database.getPrefix(prefixName);
this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier()));
}
}
/**
* @param significantFigures new value of significantFigures
* @since 2019-01-15
* @since v0.1.0
*/
public final void setSignificantFigures(final int significantFigures) {
this.significantFigures = significantFigures;
}
/**
* 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
* @since v0.2.0
*/
public final boolean unitMatchesDimension(final String unitName, final String dimensionName) {
final Unit unit = this.database.getUnit(unitName);
final ObjectProduct dimension = this.database.getDimension(dimensionName);
return unit.getDimension().equals(dimension);
}
/**
* Runs whenever a unit is selected in the viewer.
*
* Shows its information in the text box to the right.
*
*
* @since 2019-01-15
* @since v0.1.0
*/
public final void unitNameSelected() {
final String unitName = this.view.getUnitViewerSelection();
if (unitName == null)
return;
else {
final Unit unit = this.database.getUnit(unitName);
this.view.setUnitTextBoxText(unit.toString());
}
}
/**
* @return a set of all of the unit names
* @since 2019-04-14
* @since v0.2.0
*/
public final Set unitNameSet() {
return this.database.unitMapPrefixless().keySet();
}
}
private static class View {
/** The view's frame. */
private final JFrame frame;
/** The view's associated presenter. */
private final Presenter presenter;
/** The master pane containing all of the tabs. */
private final JTabbedPane masterPane;
// DIMENSION-BASED CONVERTER
/** The panel for inputting values in the dimension-based converter */
private final JTextField valueInput;
/** 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 output area in the dimension-based converter */
private final JTextArea dimensionBasedOutput;
// EXPRESSION-BASED CONVERTER
/** 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;
// UNIT AND PREFIX VIEWERS
/** The searchable list of unit names in the unit viewer */
private final SearchBoxList unitNameList;
/** The searchable list of prefix names in the prefix viewer */
private final SearchBoxList prefixNameList;
/** The text box for unit data in the unit viewer */
private final JTextArea unitTextBox;
/** The text box for prefix data in the prefix viewer */
private final JTextArea prefixTextBox;
/**
* 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(WindowConstants.EXIT_ON_CLOSE);
this.masterPane = new JTabbedPane();
// create the components
this.unitNameList = new SearchBoxList(this.presenter.unitNameSet());
this.prefixNameList = new SearchBoxList(this.presenter.prefixNameSet(),
this.presenter.getPrefixNameComparator(), true);
this.unitTextBox = new JTextArea();
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);
// create more components
this.initComponents();
this.frame.pack();
}
/**
* @return the currently selected pane.
* @throws AssertionError if no pane (or an invalid pane) is selected
*/
public Pane getActivePane() {
switch (this.masterPane.getSelectedIndex()) {
case 0:
return Pane.UNIT_CONVERTER;
case 1:
return Pane.EXPRESSION_CONVERTER;
case 2:
return Pane.UNIT_VIEWER;
case 3:
return Pane.PREFIX_VIEWER;
default:
throw new AssertionError("No selected pane, or invalid pane.");
}
}
/**
* @return value in dimension-based converter
* @since 2019-04-13
* @since v0.2.0
*/
public String getDimensionConverterInput() {
return this.valueInput.getText();
}
/**
* @return selection in "From" selector in dimension-based converter
* @since 2019-04-13
* @since v0.2.0
*/
public String getFromSelection() {
return this.fromSearch.getSelectedValue();
}
/**
* @return text in "From" box in converter panel
* @since 2019-01-15
* @since v0.1.0
*/
public String getFromText() {
return this.fromEntry.getText();
}
/**
* @return index of selected prefix in prefix viewer
* @since 2019-01-15
* @since v0.1.0
*/
public String getPrefixViewerSelection() {
return this.prefixNameList.getSelectedValue();
}
/**
* @return selection in "To" selector in dimension-based converter
* @since 2019-04-13
* @since v0.2.0
*/
public String getToSelection() {
return this.toSearch.getSelectedValue();
}
/**
* @return text in "To" box in converter panel
* @since 2019-01-26
* @since v0.1.0
*/
public String getToText() {
return this.toEntry.getText();
}
/**
* @return index of selected unit in unit viewer
* @since 2019-01-15
* @since v0.1.0
*/
public String getUnitViewerSelection() {
return this.unitNameList.getSelectedValue();
}
/**
* 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
masterPanel.add(this.masterPane, BorderLayout.CENTER);
{ // a panel for unit conversion using a selector
final JPanel convertUnitPanel = new JPanel();
this.masterPane.addTab("Convert Units", convertUnitPanel);
this.masterPane.setMnemonicAt(0, KeyEvent.VK_U);
convertUnitPanel.setLayout(new BorderLayout());
{ // panel for input part
final JPanel inputPanel = new JPanel();
convertUnitPanel.add(inputPanel, BorderLayout.CENTER);
inputPanel.setLayout(new GridLayout(1, 3));
final JComboBox dimensionSelector = new JComboBox<>(
this.presenter.dimensionNameList().toArray(new String[0]));
dimensionSelector.setSelectedItem("LENGTH");
// handle dimension filter
final MutablePredicate dimensionFilter = new MutablePredicate<>(s -> true);
// panel for From things
inputPanel.add(this.fromSearch);
this.fromSearch.addSearchFilter(dimensionFilter);
{ // for dimension selector and arrow that represents conversion
final JPanel inBetweenPanel = new JPanel();
inputPanel.add(inBetweenPanel);
inBetweenPanel.setLayout(new BorderLayout());
{ // dimension selector
inBetweenPanel.add(dimensionSelector, BorderLayout.PAGE_START);
}
{ // the arrow in the middle
final JLabel arrowLabel = new JLabel("->");
inBetweenPanel.add(arrowLabel, BorderLayout.CENTER);
}
}
// panel for To things
inputPanel.add(this.toSearch);
this.toSearch.addSearchFilter(dimensionFilter);
// 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
final JPanel outputPanel = new JPanel();
convertUnitPanel.add(outputPanel, BorderLayout.PAGE_END);
outputPanel.setLayout(new GridLayout(3, 1));
{ // unit input
final JPanel valueInputPanel = new JPanel();
outputPanel.add(valueInputPanel);
valueInputPanel.setLayout(new BorderLayout());
{ // prompt
final JLabel valuePrompt = new JLabel("Value to convert: ");
valueInputPanel.add(valuePrompt, BorderLayout.LINE_START);
}
{ // value to convert
valueInputPanel.add(this.valueInput, BorderLayout.CENTER);
}
}
{ // button to convert
final JButton convertButton = new JButton("Convert");
outputPanel.add(convertButton);
convertButton.addActionListener(e -> this.presenter.convertDimensionBased());
convertButton.setMnemonic(KeyEvent.VK_ENTER);
}
{ // output of conversion
outputPanel.add(this.dimensionBasedOutput);
this.dimensionBasedOutput.setEditable(false);
}
}
}
{ // panel for unit conversion using expressions
final JPanel convertExpressionPanel = new JPanel();
this.masterPane.addTab("Convert Unit Expressions", convertExpressionPanel);
this.masterPane.setMnemonicAt(1, KeyEvent.VK_E);
convertExpressionPanel.setLayout(new GridLayout(4, 1));
{ // panel for units to convert from
final JPanel fromPanel = new JPanel();
convertExpressionPanel.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();
convertExpressionPanel.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");
convertExpressionPanel.add(convertButton);
convertButton.addActionListener(e -> this.presenter.convertExpressions());
convertButton.setMnemonic(KeyEvent.VK_ENTER);
}
{ // output of conversion
final JPanel outputPanel = new JPanel();
convertExpressionPanel.add(outputPanel);
outputPanel.setBorder(BorderFactory.createTitledBorder("Output"));
outputPanel.setLayout(new GridLayout(1, 1));
{ // output
outputPanel.add(this.output);
this.output.setEditable(false);
}
}
}
{ // panel to look up units
final JPanel unitLookupPanel = new JPanel();
this.masterPane.addTab("Unit Viewer", unitLookupPanel);
this.masterPane.setMnemonicAt(2, KeyEvent.VK_V);
unitLookupPanel.setLayout(new GridLayout());
{ // search panel
unitLookupPanel.add(this.unitNameList);
this.unitNameList.getSearchList()
.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();
this.masterPane.addTab("Prefix Viewer", prefixLookupPanel);
this.masterPane.setMnemonicAt(3, KeyEvent.VK_P);
prefixLookupPanel.setLayout(new GridLayout(1, 2));
{ // panel for listing and seaching
prefixLookupPanel.add(this.prefixNameList);
this.prefixNameList.getSearchList()
.addListSelectionListener(e -> this.presenter.prefixSelected());
}
{ // the text box for prefix's toString
prefixLookupPanel.add(this.prefixTextBox);
this.prefixTextBox.setEditable(false);
this.prefixTextBox.setLineWrap(true);
}
}
{ // Settings panel
final JPanel settingsPanel = new JPanel();
this.masterPane.addTab("\u2699", new JScrollPane(settingsPanel));
this.masterPane.setMnemonicAt(4, KeyEvent.VK_S);
settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.PAGE_AXIS));
{ // rounding settings
final JPanel roundingPanel = new JPanel();
settingsPanel.add(roundingPanel);
roundingPanel.setBorder(new TitledBorder("Rounding Settings"));
roundingPanel.setLayout(new GridBagLayout());
// rounding rule selection
final ButtonGroup roundingRuleButtons = new ButtonGroup();
final JLabel roundingRuleLabel = new JLabel("Rounding Rule:");
roundingPanel.add(roundingRuleLabel, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton fixedPrecision = new JRadioButton("Fixed Precision");
fixedPrecision.setSelected(true);
roundingRuleButtons.add(fixedPrecision);
roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton fixedDecimals = new JRadioButton("Fixed Decimal Places");
roundingRuleButtons.add(fixedDecimals);
roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton relativePrecision = new JRadioButton("Scientific Precision");
roundingRuleButtons.add(relativePrecision);
roundingPanel.add(relativePrecision, new GridBagBuilder(0, 3).setAnchor(GridBagConstraints.LINE_START).build());
final JLabel sliderLabel = new JLabel("Precision:");
roundingPanel.add(sliderLabel, new GridBagBuilder(0, 4).setAnchor(GridBagConstraints.LINE_START).build());
final JSlider sigDigSlider = new JSlider(0, 12);
roundingPanel.add(sigDigSlider, new GridBagBuilder(0, 5).setAnchor(GridBagConstraints.LINE_START).build());
sigDigSlider.setMajorTickSpacing(4);
sigDigSlider.setMinorTickSpacing(1);
sigDigSlider.setSnapToTicks(true);
sigDigSlider.setPaintTicks(true);
sigDigSlider.setPaintLabels(true);
sigDigSlider.addChangeListener(
e -> this.presenter.setSignificantFigures(sigDigSlider.getValue()));
}
{ // prefix repetition settings
final JPanel prefixRepetitionPanel = new JPanel();
settingsPanel.add(prefixRepetitionPanel);
prefixRepetitionPanel.setBorder(new TitledBorder("Prefix Repetition Settings"));
prefixRepetitionPanel.setLayout(new GridBagLayout());
// prefix rules
final ButtonGroup prefixRuleButtons = new ButtonGroup();
final JRadioButton noRepetition = new JRadioButton("No Repetition");
noRepetition.setSelected(true);
prefixRuleButtons.add(noRepetition);
prefixRepetitionPanel.add(noRepetition, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton noRestriction = new JRadioButton("No Restriction");
prefixRuleButtons.add(noRestriction);
prefixRepetitionPanel.add(noRestriction, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton customRepetition = new JRadioButton("Custom Repetition Rule");
prefixRuleButtons.add(customRepetition);
prefixRepetitionPanel.add(customRepetition, new GridBagBuilder(0, 2).setAnchor(GridBagConstraints.LINE_START).build());
}
{ // search settings
final JPanel searchingPanel = new JPanel();
settingsPanel.add(searchingPanel);
searchingPanel.setBorder(new TitledBorder("Search Settings"));
searchingPanel.setLayout(new GridBagLayout());
// searching rules
final ButtonGroup searchRuleButtons = new ButtonGroup();
final JRadioButton noPrefixes = new JRadioButton("Never Include Prefixed Units");
noPrefixes.setSelected(true);
searchRuleButtons.add(noPrefixes);
searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton fixedPrefixes = new JRadioButton("Include Some Prefixes");
searchRuleButtons.add(fixedPrefixes);
searchingPanel.add(fixedPrefixes, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton explicitPrefixes = new JRadioButton("Include Explicit Prefixes");
searchRuleButtons.add(explicitPrefixes);
searchingPanel.add(explicitPrefixes, new GridBagBuilder(0, 2).setAnchor(GridBagConstraints.LINE_START).build());
final JRadioButton alwaysInclude = new JRadioButton("Include All Single Prefixes");
searchRuleButtons.add(alwaysInclude);
searchingPanel.add(alwaysInclude, new GridBagBuilder(0, 3).setAnchor(GridBagConstraints.LINE_START).build());
}
{ // miscellaneous settings
final JPanel miscPanel = new JPanel();
settingsPanel.add(miscPanel);
miscPanel.setBorder(new TitledBorder("Miscellaneous Settings"));
miscPanel.setLayout(new GridBagLayout());
final JCheckBox showAllVariations = new JCheckBox("Show Symbols in \"Convert Units\"");
showAllVariations.setSelected(true);
miscPanel.add(showAllVariations, new GridBagBuilder(0, 0).setAnchor(GridBagConstraints.LINE_START).build());
final JButton unitFileButton = new JButton("Manage Unit Data Files");
miscPanel.add(unitFileButton, new GridBagBuilder(0, 1).setAnchor(GridBagConstraints.LINE_START).build());
}
}
}
}
/**
* Sets the text in the output of the dimension-based converter.
*
* @param text text to set
* @since 2019-04-13
* @since v0.2.0
*/
public void setDimensionConverterOutputText(final String text) {
this.dimensionBasedOutput.setText(text);
}
/**
* 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 setExpressionConverterOutputText(final String text) {
this.output.setText(text);
}
/**
* Sets the text of the prefix text box in the prefix viewer.
*
* @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 in the unit viewer.
*
* @param text text to set
* @since 2019-01-15
* @since v0.1.0
*/
public void setUnitTextBoxText(final String text) {
this.unitTextBox.setText(text);
}
/**
* 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();
}
}