diff options
Diffstat (limited to 'src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java')
-rw-r--r-- | src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java | 1505 |
1 files changed, 0 insertions, 1505 deletions
diff --git a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java b/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java deleted file mode 100644 index bfd5974..0000000 --- a/src/main/java/sevenUnits/converterGUI/SevenUnitsGUI.java +++ /dev/null @@ -1,1505 +0,0 @@ -/** - * 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 sevenUnits.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.io.InputStream; -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.Scanner; -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 sevenUnits.ProgramInfo; -import sevenUnits.unit.BaseDimension; -import sevenUnits.unit.BritishImperial; -import sevenUnits.unit.LinearUnit; -import sevenUnits.unit.LinearUnitValue; -import sevenUnits.unit.NameSymbol; -import sevenUnits.unit.Metric; -import sevenUnits.unit.Unit; -import sevenUnits.unit.UnitDatabase; -import sevenUnits.unit.UnitPrefix; -import sevenUnits.unit.UnitValue; -import sevenUnits.utils.ConditionalExistenceCollections; -import sevenUnits.utils.ObjectProduct; - -/** - * @author Adrien Hopkins - * @since 2018-12-27 - * @since v0.1.0 - */ -final class SevenUnitsGUI { - /** - * 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 String DEFAULT_UNITS_FILEPATH = "/unitsfile.txt"; - /** The default place where dimensions are stored. */ - private static final String DEFAULT_DIMENSIONS_FILEPATH = "/dimensionfile.txt"; - /** The default place where exceptions are stored. */ - private static final String DEFAULT_EXCEPTIONS_FILEPATH = "/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", Metric.METRE); - database.addUnit("kilogram", Metric.KILOGRAM); - database.addUnit("gram", Metric.KILOGRAM.dividedBy(1000)); - database.addUnit("second", Metric.SECOND); - database.addUnit("ampere", Metric.AMPERE); - database.addUnit("kelvin", Metric.KELVIN); - database.addUnit("mole", Metric.MOLE); - database.addUnit("candela", Metric.CANDELA); - database.addUnit("bit", Metric.BIT); - database.addUnit("unit", Metric.ONE); - // nonlinear units - must be loaded manually - database.addUnit("tempCelsius", Metric.CELSIUS); - database.addUnit("tempFahrenheit", BritishImperial.FAHRENHEIT); - - // load initial dimensions - database.addDimension("LENGTH", Metric.Dimensions.LENGTH); - database.addDimension("MASS", Metric.Dimensions.MASS); - database.addDimension("TIME", Metric.Dimensions.TIME); - database.addDimension("TEMPERATURE", Metric.Dimensions.TEMPERATURE); - } - - /** - * Gets the text of a resource file as a set of strings (each one is one - * line of the text). - * - * @param filename filename to get resource from - * @return contents of file - * @since 2021-03-27 - */ - public static final List<String> getLinesFromResource(String filename) { - final List<String> lines = new ArrayList<>(); - - try (InputStream stream = inputStream(filename); - Scanner scanner = new Scanner(stream)) { - while (scanner.hasNextLine()) { - lines.add(scanner.nextLine()); - } - } catch (final IOException e) { - throw new AssertionError( - "Error occurred while loading file " + filename, e); - } - - return lines; - } - - /** - * Gets an input stream for a resource file. - * - * @param filepath file to use as resource - * @return obtained Path - * @since 2021-03-27 - */ - private static final InputStream inputStream(String filepath) { - return SevenUnitsGUI.class.getResourceAsStream(filepath); - } - - /** - * @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; - - // The "include duplicate units" setting - private boolean includeDuplicateUnits = true; - - /** - * 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); - - // load units and prefixes - try (final InputStream units = inputStream(DEFAULT_UNITS_FILEPATH)) { - this.database.loadUnitsFromStream(units); - } catch (final IOException e) { - throw new AssertionError("Loading of unitsfile.txt failed.", e); - } - - // load dimensions - try (final InputStream dimensions = inputStream( - DEFAULT_DIMENSIONS_FILEPATH)) { - this.database.loadDimensionsFromStream(dimensions); - } catch (final IOException e) { - throw new AssertionError("Loading of dimensionfile.txt failed.", e); - } - - // load metric exceptions - try { - this.metricExceptions = new HashSet<>(); - try (InputStream exceptions = inputStream( - DEFAULT_EXCEPTIONS_FILEPATH); - Scanner scanner = new Scanner(exceptions)) { - while (scanner.hasNextLine()) { - final String line = Presenter - .withoutComments(scanner.nextLine()); - if (!line.isBlank()) { - this.metricExceptions.add(line); - } - } - } - } catch (final IOException e) { - throw new AssertionError("Loading of metric_exceptions.txt failed.", - e); - } - - // load settings - requires database to exist - if (Files.exists(this.getSettingsFile())) { - 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(true).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", - this.database.unitMapPrefixless(false).size(), - this.database.unitMapPrefixless(true).size(), - this.database.unitMapPrefixless(false).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; - case "include_duplicates": - this.includeDuplicateUnits = Boolean.valueOf(value); - if (this.view.presenter != null) { - this.view.update(); - } - 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)); - writer.write(String.format("include_duplicates=%s\n", - this.includeDuplicateUnits)); - } catch (final IOException e) { - e.printStackTrace(); - this.view.showErrorDialog("I/O Error", - "Error occurred while saving settings: " - + e.getLocalizedMessage()); - } - } - - public final void setIncludeDuplicateUnits( - boolean includeDuplicateUnits) { - this.includeDuplicateUnits = includeDuplicateUnits; - - this.view.update(); - this.saveSettings(); - } - - /** - * 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 - */ - public final Set<String> unitNameSet() { - return this.database.unitMapPrefixless(this.includeDuplicateUnits) - .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("7Units"); - 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 = Presenter - .getLinesFromResource("/about.txt").stream() - .map(Presenter::withoutComments) - .collect(Collectors.joining("\n")) - .replaceAll("\\[VERSION\\]", ProgramInfo.VERSION); - 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 Duplicates in \"Convert Units\""); - showAllVariations - .setSelected(this.presenter.includeDuplicateUnits); - showAllVariations.addItemListener(e -> this.presenter - .setIncludeDuplicateUnits(e.getStateChange() == 1)); - 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() { - this.unitNameList.setItems(this.presenter.unitNameSet()); - this.fromSearch.setItems(this.presenter.fromEntries()); - this.toSearch.setItems(this.presenter.toEntries()); - - 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(); - } -} |