summaryrefslogtreecommitdiff
path: root/src/main/java/org/unitConverter/converterGUI/UnitConverterGUI.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/unitConverter/converterGUI/UnitConverterGUI.java')
-rw-r--r--src/main/java/org/unitConverter/converterGUI/UnitConverterGUI.java1426
1 files changed, 1426 insertions, 0 deletions
diff --git a/src/main/java/org/unitConverter/converterGUI/UnitConverterGUI.java b/src/main/java/org/unitConverter/converterGUI/UnitConverterGUI.java
new file mode 100644
index 0000000..c046846
--- /dev/null
+++ b/src/main/java/org/unitConverter/converterGUI/UnitConverterGUI.java
@@ -0,0 +1,1426 @@
+/**
+ * Copyright (C) 2018-2021 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 org.unitConverter.converterGUI;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.KeyEvent;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFormattedTextField;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.WindowConstants;
+import javax.swing.border.TitledBorder;
+
+import org.unitConverter.math.ConditionalExistenceCollections;
+import org.unitConverter.math.ObjectProduct;
+import org.unitConverter.unit.BaseDimension;
+import org.unitConverter.unit.BritishImperial;
+import org.unitConverter.unit.LinearUnit;
+import org.unitConverter.unit.LinearUnitValue;
+import org.unitConverter.unit.NameSymbol;
+import org.unitConverter.unit.SI;
+import org.unitConverter.unit.Unit;
+import org.unitConverter.unit.UnitDatabase;
+import org.unitConverter.unit.UnitPrefix;
+import org.unitConverter.unit.UnitValue;
+
+/**
+ * @author Adrien Hopkins
+ * @since 2018-12-27
+ * @since v0.1.0
+ */
+final class UnitConverterGUI {
+ /**
+ * A tab in the View.
+ */
+ private enum Pane {
+ UNIT_CONVERTER, EXPRESSION_CONVERTER, UNIT_VIEWER, PREFIX_VIEWER, ABOUT,
+ SETTINGS;
+ }
+
+ private static class Presenter {
+ /** The default place where settings are stored. */
+ private static final String DEFAULT_SETTINGS_FILEPATH = "settings.txt";
+ /** The default place where units are stored. */
+ private static final Path DEFAULT_UNITS_FILE = Path.of("unitsfile.txt");
+ /** The default place where dimensions are stored. */
+ private static final Path DEFAULT_DIMENSION_FILE = Path
+ .of("dimensionfile.txt");
+ /** The default place where exceptions are stored. */
+ private static final Path DEFAULT_EXCEPTIONS_FILE = Path
+ .of("metric_exceptions.txt");
+
+ /**
+ * Adds default units and dimensions to a database.
+ *
+ * @param database database to add to
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ private static void addDefaults(final UnitDatabase database) {
+ database.addUnit("metre", SI.METRE);
+ database.addUnit("kilogram", SI.KILOGRAM);
+ database.addUnit("gram", SI.KILOGRAM.dividedBy(1000));
+ database.addUnit("second", SI.SECOND);
+ database.addUnit("ampere", SI.AMPERE);
+ database.addUnit("kelvin", SI.KELVIN);
+ database.addUnit("mole", SI.MOLE);
+ database.addUnit("candela", SI.CANDELA);
+ database.addUnit("bit", SI.BIT);
+ database.addUnit("unit", SI.ONE);
+ // nonlinear units - must be loaded manually
+ database.addUnit("tempCelsius", SI.CELSIUS);
+ database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT);
+
+ // load initial dimensions
+ database.addDimension("LENGTH", SI.Dimensions.LENGTH);
+ database.addDimension("MASS", SI.Dimensions.MASS);
+ database.addDimension("TIME", SI.Dimensions.TIME);
+ database.addDimension("TEMPERATURE", SI.Dimensions.TEMPERATURE);
+ }
+
+ /**
+ * @return {@code line} with any comments removed.
+ * @since 2021-03-13
+ */
+ private static final String withoutComments(String line) {
+ final int index = line.indexOf('#');
+ return index == -1 ? line : line.substring(index);
+ }
+
+ /** The presenter's associated view. */
+ private final View view;
+
+ /** The units known by the program. */
+ private final UnitDatabase database;
+
+ /** The names of all of the units */
+ private final List<String> unitNames;
+
+ /** The names of all of the prefixes */
+ private final List<String> prefixNames;
+
+ /** The names of all of the dimensions */
+ private final List<String> dimensionNames;
+
+ /** Unit names that are ignored by the metric-only/imperial-only filter */
+ private final Set<String> metricExceptions;
+
+ private final Comparator<String> prefixNameComparator;
+
+ /** A boolean remembering whether or not one-way conversion is on */
+ private boolean oneWay = true;
+
+ /** The prefix rule */
+ private DefaultPrefixRepetitionRule prefixRule = null;
+ // conditions for existence of From and To entries
+ // used for one-way conversion
+ private final MutablePredicate<String> fromExistenceCondition = new MutablePredicate<>(
+ s -> true);
+
+ private final MutablePredicate<String> toExistenceCondition = new MutablePredicate<>(
+ s -> true);
+
+ /*
+ * Rounding-related settings. I am using my own system, and not
+ * MathContext, because MathContext does not support decimal place based
+ * or scientific rounding, only significant digit based rounding.
+ */
+ private int precision = 6;
+
+ private RoundingType roundingType = RoundingType.SIGNIFICANT_DIGITS;
+
+ /**
+ * 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.database = new UnitDatabase(
+ DefaultPrefixRepetitionRule.NO_RESTRICTION);
+ Presenter.addDefaults(this.database);
+
+ this.database.loadUnitsFile(DEFAULT_UNITS_FILE);
+ this.database.loadDimensionFile(DEFAULT_DIMENSION_FILE);
+
+ // load metric exceptions
+ try {
+ this.metricExceptions = Files.readAllLines(DEFAULT_EXCEPTIONS_FILE)
+ .stream().map(Presenter::withoutComments)
+ .filter(s -> !s.isBlank()).collect(Collectors.toSet());
+ } catch (final IOException e) {
+ throw new AssertionError("Loading of metric_exceptions.txt failed.",
+ e);
+ }
+
+ // load settings - requires database to exist
+ this.loadSettings();
+
+ // 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.database.containsPrefixName(o1))
+ return -1;
+ else if (!Presenter.this.database.containsPrefixName(o2))
+ return 1;
+
+ final UnitPrefix p1 = Presenter.this.database.getPrefix(o1);
+ final UnitPrefix p2 = Presenter.this.database.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.database.unitMapPrefixless().keySet());
+ this.unitNames.sort(null); // sorts it using Comparable
+
+ this.prefixNames = new ArrayList<>(this.database.prefixMap().keySet());
+ this.prefixNames.sort(this.prefixNameComparator); // sorts it using my
+ // comparator
+
+ this.dimensionNames = new DelegateListModel<>(
+ new ArrayList<>(this.database.dimensionMap().keySet()));
+ this.dimensionNames.sort(null); // sorts it using Comparable
+
+ // a Predicate that returns true iff the argument is a full base unit
+ final Predicate<Unit> 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)
+ .withName(NameSymbol.ofName(toSelection));
+
+ final UnitValue beforeValue;
+ try {
+ beforeValue = UnitValue.of(from,
+ this.view.getDimensionConverterInput());
+ } catch (final ParseException e) {
+ this.view.showErrorDialog("Error",
+ "Error in parsing: " + e.getMessage());
+ return;
+ }
+ final UnitValue value = beforeValue.convertTo(to);
+
+ final String output = this.getRoundedString(value);
+
+ this.view.setDimensionConverterOutputText(
+ String.format("%s = %s", beforeValue, output));
+ }
+
+ /**
+ * 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 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;
+ }
+
+ final LinearUnitValue from;
+ final Unit to;
+ try {
+ from = this.database.evaluateUnitExpression(fromUnitString);
+ } catch (final IllegalArgumentException | NoSuchElementException e) {
+ this.view.showErrorDialog("Parse Error",
+ "Could not recognize text in From entry: " + e.getMessage());
+ return;
+ }
+ try {
+ to = this.database.getUnitFromExpression(toUnitString);
+ } catch (final IllegalArgumentException | NoSuchElementException e) {
+ this.view.showErrorDialog("Parse Error",
+ "Could not recognize text in To entry: " + e.getMessage());
+ return;
+ }
+
+ if (to instanceof LinearUnit) {
+ // convert to LinearUnitValue
+ final LinearUnitValue from2;
+ final LinearUnit to2 = ((LinearUnit) to)
+ .withName(NameSymbol.ofName(toUnitString));
+ final boolean useSlash;
+
+ if (from.canConvertTo(to2)) {
+ from2 = from;
+ useSlash = false;
+ } else if (LinearUnitValue.ONE.dividedBy(from).canConvertTo(to2)) {
+ from2 = LinearUnitValue.ONE.dividedBy(from);
+ useSlash = true;
+ } else {
+ // if I can't convert, leave
+ this.view.showErrorDialog("Conversion Error",
+ String.format("Cannot convert between %s and %s",
+ fromUnitString, toUnitString));
+ return;
+ }
+
+ final LinearUnitValue converted = from2.convertTo(to2);
+ this.view.setExpressionConverterOutputText((useSlash ? "1 / " : "")
+ + String.format("%s = %s", fromUnitString,
+ this.getRoundedString(converted, false)));
+ return;
+ } else {
+ // convert to UnitValue
+ final UnitValue from2 = from.asUnitValue();
+ if (from2.canConvertTo(to)) {
+ final UnitValue converted = from2.convertTo(to);
+
+ this.view
+ .setExpressionConverterOutputText(String.format("%s = %s",
+ fromUnitString, this.getRoundedString(converted)));
+ } 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<String> dimensionNameList() {
+ return this.dimensionNames;
+ }
+
+ /**
+ * @return a list of all the entries in the dimension-based converter's
+ * From box
+ * @since 2020-08-27
+ */
+ public final Set<String> fromEntries() {
+ return ConditionalExistenceCollections.conditionalExistenceSet(
+ this.unitNameSet(), this.fromExistenceCondition);
+ }
+
+ /**
+ * @return a comparator to compare prefix names
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ public final Comparator<String> getPrefixNameComparator() {
+ return this.prefixNameComparator;
+ }
+
+ /**
+ * Like {@link LinearUnitValue#toString(boolean)}, but obeys this unit
+ * converter's rounding settings.
+ *
+ * @since 2020-08-04
+ */
+ private final String getRoundedString(final LinearUnitValue value,
+ boolean showUncertainty) {
+ switch (this.roundingType) {
+ case DECIMAL_PLACES:
+ case SIGNIFICANT_DIGITS:
+ return this.getRoundedString(value.asUnitValue());
+ case SCIENTIFIC:
+ return value.toString(showUncertainty);
+ default:
+ throw new AssertionError("Invalid switch condition.");
+ }
+ }
+
+ /**
+ * Like {@link UnitValue#toString()}, but obeys this unit converter's
+ * rounding settings.
+ *
+ * @since 2020-08-04
+ */
+ private final String getRoundedString(final UnitValue value) {
+ final BigDecimal unrounded = new BigDecimal(value.getValue());
+ final BigDecimal rounded;
+ int precision = this.precision;
+
+ switch (this.roundingType) {
+ case DECIMAL_PLACES:
+ rounded = unrounded.setScale(precision, RoundingMode.HALF_EVEN);
+ break;
+ case SCIENTIFIC:
+ precision = 12;
+ //$FALL-THROUGH$
+ case SIGNIFICANT_DIGITS:
+ rounded = unrounded
+ .round(new MathContext(precision, RoundingMode.HALF_EVEN));
+ break;
+ default:
+ throw new AssertionError("Invalid switch condition.");
+ }
+
+ String output = rounded.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 + " " + value.getUnit().getPrimaryName().get();
+ }
+
+ /**
+ * @return The file where settings are stored;
+ * @since 2020-12-11
+ */
+ private final Path getSettingsFile() {
+ return Path.of(DEFAULT_SETTINGS_FILEPATH);
+ }
+
+ /**
+ * Loads settings from the settings file.
+ *
+ * @since 2021-02-17
+ */
+ public final void loadSettings() {
+ try {
+ // read file line by line
+ final int lineNum = 0;
+ for (final String line : Files
+ .readAllLines(this.getSettingsFile())) {
+ final int equalsIndex = line.indexOf('=');
+ if (equalsIndex == -1)
+ throw new IllegalStateException(
+ "Settings file is malformed at line " + lineNum);
+
+ final String param = line.substring(0, equalsIndex);
+ final String value = line.substring(equalsIndex + 1);
+
+ switch (param) {
+ // set manually to avoid the unnecessary saving of the non-manual
+ // methods
+ case "precision":
+ this.precision = Integer.valueOf(value);
+ break;
+ case "rounding_type":
+ this.roundingType = RoundingType.valueOf(value);
+ break;
+ case "prefix_rule":
+ this.prefixRule = DefaultPrefixRepetitionRule.valueOf(value);
+ this.database.setPrefixRepetitionRule(this.prefixRule);
+ break;
+ case "one_way":
+ this.oneWay = Boolean.valueOf(value);
+ if (this.oneWay) {
+ this.fromExistenceCondition.setPredicate(
+ unitName -> this.metricExceptions.contains(unitName)
+ || !this.database.getUnit(unitName)
+ .isMetric());
+ this.toExistenceCondition.setPredicate(
+ unitName -> this.metricExceptions.contains(unitName)
+ || this.database.getUnit(unitName).isMetric());
+ } else {
+ this.fromExistenceCondition.setPredicate(unitName -> true);
+ this.toExistenceCondition.setPredicate(unitName -> true);
+ }
+ break;
+ default:
+ System.err.printf("Warning: unrecognized setting \"%s\".",
+ param);
+ break;
+ }
+ }
+ } catch (final IOException e) {}
+ }
+
+ /**
+ * @return a set of all prefix names in the database
+ * @since 2019-04-14
+ * @since v0.2.0
+ */
+ public final Set<String> prefixNameSet() {
+ return this.database.prefixMap().keySet();
+ }
+
+ /**
+ * 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 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()));
+ }
+ }
+
+ /**
+ * Saves the settings to the settings file.
+ *
+ * @since 2021-02-17
+ */
+ public final void saveSettings() {
+ try (BufferedWriter writer = Files
+ .newBufferedWriter(this.getSettingsFile())) {
+ writer.write(String.format("precision=%d\n", this.precision));
+ writer.write(
+ String.format("rounding_type=%s\n", this.roundingType));
+ writer.write(String.format("prefix_rule=%s\n", this.prefixRule));
+ writer.write(String.format("one_way=%s\n", this.oneWay));
+ } catch (final IOException e) {
+ e.printStackTrace();
+ this.view.showErrorDialog("I/O Error",
+ "Error occurred while saving settings: "
+ + e.getLocalizedMessage());
+ }
+ }
+
+ /**
+ * Enables or disables one-way conversion.
+ *
+ * @param oneWay whether one-way conversion should be on (true) or off
+ * (false)
+ * @since 2020-08-27
+ */
+ public final void setOneWay(boolean oneWay) {
+ this.oneWay = oneWay;
+ if (oneWay) {
+ this.fromExistenceCondition.setPredicate(
+ unitName -> this.metricExceptions.contains(unitName)
+ || !this.database.getUnit(unitName).isMetric());
+ this.toExistenceCondition.setPredicate(
+ unitName -> this.metricExceptions.contains(unitName)
+ || this.database.getUnit(unitName).isMetric());
+ } else {
+ this.fromExistenceCondition.setPredicate(unitName -> true);
+ this.toExistenceCondition.setPredicate(unitName -> true);
+ }
+
+ this.saveSettings();
+ }
+
+ /**
+ * @param precision new value of precision
+ * @since 2019-01-15
+ * @since v0.1.0
+ */
+ public final void setPrecision(final int precision) {
+ this.precision = precision;
+
+ this.saveSettings();
+ }
+
+ /**
+ * @param prefixRepetitionRule the prefixRepetitionRule to set
+ * @since 2020-08-26
+ */
+ public void setPrefixRepetitionRule(
+ Predicate<List<UnitPrefix>> prefixRepetitionRule) {
+ if (prefixRepetitionRule instanceof DefaultPrefixRepetitionRule) {
+ this.prefixRule = (DefaultPrefixRepetitionRule) prefixRepetitionRule;
+ } else {
+ this.prefixRule = null;
+ }
+ this.database.setPrefixRepetitionRule(prefixRepetitionRule);
+
+ this.saveSettings();
+ }
+
+ /**
+ * @param roundingType the roundingType to set
+ * @since 2020-07-16
+ */
+ public final void setRoundingType(RoundingType roundingType) {
+ this.roundingType = roundingType;
+
+ this.saveSettings();
+ }
+
+ /**
+ * @return a list of all the entries in the dimension-based converter's To
+ * box
+ * @since 2020-08-27
+ */
+ public final Set<String> toEntries() {
+ return ConditionalExistenceCollections.conditionalExistenceSet(
+ this.unitNameSet(), this.toExistenceCondition);
+ }
+
+ /**
+ * 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<BaseDimension> dimension = this.database
+ .getDimension(dimensionName);
+ return unit.getDimension().equals(dimension);
+ }
+
+ /**
+ * 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 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
+ */
+ private final Set<String> unitNameSet() {
+ return this.database.unitMapPrefixless().keySet();
+ }
+ }
+
+ /**
+ * Different types of rounding.
+ *
+ * Significant digits: Rounds to a number of digits. i.e. with precision 5,
+ * 12345.6789 rounds to 12346. Decimal places: Rounds to a number of digits
+ * after the decimal point, i.e. with precision 5, 12345.6789 rounds to
+ * 12345.67890. Scientific: Rounds based on the number of digits and
+ * operations, following standard scientific rounding.
+ */
+ private static enum RoundingType {
+ SIGNIFICANT_DIGITS, DECIMAL_PLACES, SCIENTIFIC;
+ }
+
+ private static class View {
+ private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat();
+
+ /** 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);
+
+ // enable system look and feel
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (ClassNotFoundException | InstantiationException
+ | IllegalAccessException | UnsupportedLookAndFeelException e) {
+ // oh well, just use default theme
+ System.err.println("Failed to enable system look-and-feel.");
+ e.printStackTrace();
+ }
+
+ // create the components
+ this.masterPane = new JTabbedPane();
+ 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.fromEntries());
+ this.toSearch = new SearchBoxList(this.presenter.toEntries());
+ this.valueInput = new JFormattedTextField(NUMBER_FORMATTER);
+ 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;
+ case 4:
+ return Pane.ABOUT;
+ case 5:
+ return Pane.SETTINGS;
+ default:
+ throw new AssertionError("No selected pane, or invalid pane.");
+ }
+ }
+
+ /**
+ * @return value in dimension-based converter
+ * @throws ParseException
+ * @since 2020-07-07
+ */
+ public double getDimensionConverterInput() throws ParseException {
+ final Number value = NUMBER_FORMATTER.parse(this.valueInput.getText());
+ if (value instanceof Double)
+ return (double) value;
+ else if (value instanceof Long)
+ return ((Long) value).longValue();
+ else
+ throw new AssertionError();
+ }
+
+ /**
+ * @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);
+
+ // update stuff
+ this.masterPane.addChangeListener(e -> this.update());
+
+ { // 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<String> dimensionSelector = new JComboBox<>(
+ this.presenter.dimensionNameList()
+ .toArray(new String[0]));
+ dimensionSelector.setSelectedItem("LENGTH");
+
+ // handle dimension filter
+ final MutablePredicate<String> 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);
+ }
+ }
+
+ { // Info panel
+ final JPanel infoPanel = new JPanel();
+ this.masterPane.addTab("\uD83D\uDEC8", // info (i) character
+ new JScrollPane(infoPanel));
+
+ final JTextArea infoTextArea = new JTextArea();
+ infoTextArea.setEditable(false);
+ infoTextArea.setOpaque(false);
+ infoPanel.add(infoTextArea);
+
+ // get info text
+ final String infoText;
+ try {
+ final Path aboutFile = Path.of("src", "main", "resources",
+ "about.txt");
+ infoText = Files.readAllLines(aboutFile).stream()
+ .map(Presenter::withoutComments)
+ .collect(Collectors.joining("\n"));
+ } catch (final IOException e) {
+ throw new AssertionError("I/O exception loading about.txt",
+ e);
+ }
+ infoTextArea.setText(infoText);
+ }
+
+ { // Settings panel
+ final JPanel settingsPanel = new JPanel();
+ this.masterPane.addTab("\u2699", new JScrollPane(settingsPanel));
+ this.masterPane.setMnemonicAt(5, 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");
+ if (this.presenter.roundingType == RoundingType.SIGNIFICANT_DIGITS) {
+ fixedPrecision.setSelected(true);
+ }
+ fixedPrecision.addActionListener(e -> this.presenter
+ .setRoundingType(RoundingType.SIGNIFICANT_DIGITS));
+ roundingRuleButtons.add(fixedPrecision);
+ roundingPanel.add(fixedPrecision, new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JRadioButton fixedDecimals = new JRadioButton(
+ "Fixed Decimal Places");
+ if (this.presenter.roundingType == RoundingType.DECIMAL_PLACES) {
+ fixedDecimals.setSelected(true);
+ }
+ fixedDecimals.addActionListener(e -> this.presenter
+ .setRoundingType(RoundingType.DECIMAL_PLACES));
+ roundingRuleButtons.add(fixedDecimals);
+ roundingPanel.add(fixedDecimals, new GridBagBuilder(0, 2)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JRadioButton relativePrecision = new JRadioButton(
+ "Scientific Precision");
+ if (this.presenter.roundingType == RoundingType.SCIENTIFIC) {
+ relativePrecision.setSelected(true);
+ }
+ relativePrecision.addActionListener(e -> this.presenter
+ .setRoundingType(RoundingType.SCIENTIFIC));
+ 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.setValue(this.presenter.precision);
+
+ sigDigSlider.addChangeListener(e -> this.presenter
+ .setPrecision(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");
+ if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.NO_REPETITION) {
+ noRepetition.setSelected(true);
+ }
+ noRepetition.addActionListener(
+ e -> this.presenter.setPrefixRepetitionRule(
+ DefaultPrefixRepetitionRule.NO_REPETITION));
+ prefixRuleButtons.add(noRepetition);
+ prefixRepetitionPanel.add(noRepetition,
+ new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START)
+ .build());
+
+ final JRadioButton noRestriction = new JRadioButton(
+ "No Restriction");
+ if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.NO_RESTRICTION) {
+ noRestriction.setSelected(true);
+ }
+ noRestriction.addActionListener(
+ e -> this.presenter.setPrefixRepetitionRule(
+ DefaultPrefixRepetitionRule.NO_RESTRICTION));
+ prefixRuleButtons.add(noRestriction);
+ prefixRepetitionPanel.add(noRestriction,
+ new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START)
+ .build());
+
+ final JRadioButton customRepetition = new JRadioButton(
+ "Complex Repetition");
+ if (this.presenter.prefixRule == DefaultPrefixRepetitionRule.COMPLEX_REPETITION) {
+ customRepetition.setSelected(true);
+ }
+ customRepetition.addActionListener(
+ e -> this.presenter.setPrefixRepetitionRule(
+ DefaultPrefixRepetitionRule.COMPLEX_REPETITION));
+ 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.setEnabled(false);
+ searchRuleButtons.add(noPrefixes);
+ searchingPanel.add(noPrefixes, new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JRadioButton fixedPrefixes = new JRadioButton(
+ "Include Some Prefixes");
+ fixedPrefixes.setEnabled(false);
+ searchRuleButtons.add(fixedPrefixes);
+ searchingPanel.add(fixedPrefixes, new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JRadioButton explicitPrefixes = new JRadioButton(
+ "Include Explicit Prefixes");
+ explicitPrefixes.setEnabled(false);
+ searchRuleButtons.add(explicitPrefixes);
+ searchingPanel.add(explicitPrefixes, new GridBagBuilder(0, 2)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JRadioButton alwaysInclude = new JRadioButton(
+ "Include All Single Prefixes");
+ alwaysInclude.setEnabled(false);
+ 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 oneWay = new JCheckBox(
+ "Convert One Way Only");
+ oneWay.setSelected(this.presenter.oneWay);
+ oneWay.addItemListener(
+ e -> this.presenter.setOneWay(e.getStateChange() == 1));
+ miscPanel.add(oneWay, new GridBagBuilder(0, 0)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JCheckBox showAllVariations = new JCheckBox(
+ "Show Symbols in \"Convert Units\"");
+ showAllVariations.setSelected(true);
+ showAllVariations.setEnabled(false);
+ miscPanel.add(showAllVariations, new GridBagBuilder(0, 1)
+ .setAnchor(GridBagConstraints.LINE_START).build());
+
+ final JButton unitFileButton = new JButton(
+ "Manage Unit Data Files");
+ unitFileButton.setEnabled(false);
+ miscPanel.add(unitFileButton, new GridBagBuilder(0, 2)
+ .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 void update() {
+ switch (this.getActivePane()) {
+ case UNIT_CONVERTER:
+ this.fromSearch.updateList();
+ this.toSearch.updateList();
+ break;
+ default:
+ // do nothing, for now
+ break;
+ }
+ }
+ }
+
+ public static void main(final String[] args) {
+ new View().init();
+ }
+}