001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.startupmanager.options;
030
031import java.io.File;
032
033import java.nio.file.Path;
034import java.nio.file.Paths;
035import java.util.regex.Pattern;
036
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039
040import javax.swing.JButton;
041import javax.swing.JCheckBox;
042import javax.swing.JComponent;
043import javax.swing.JFileChooser;
044import javax.swing.JPanel;
045import javax.swing.JTextField;
046import javax.swing.SwingUtilities;
047
048import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
049import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
050
051/**
052 * Represents a file selection.
053 */
054public final class FileOption extends AbstractOption {
055
056    /** Label for {@link #browseButton}. */
057    private static final String BUTTON_LABEL = "Browse...";
058
059    /** Label for {@link #enableCheckBox}. */
060    private static final String CHECKBOX_LABEL = "Specify default bundle:";
061
062    /** System property that points to the McIDAS-V user path. */
063    private static final String USERPATH = "mcv.userpath";
064
065    /** Name of the {@literal "bundle"} subdirectory of the user path. */
066    private static final String BUNDLE_DIR = "bundles";
067
068    /**
069     * Regular expression pattern for ensuring that no quote marks are present
070     * in {@link #value}.
071     */
072    private static final Pattern CLEAN_VALUE_REGEX = Pattern.compile("\"");
073
074    /** Formatting string used by {@link #toString()}. */
075    private static final String FORMAT =
076        "[FileOption@%x: optionId=%s, value=%s]";
077
078    /** Tool tip used by {@link #bundleField}. */
079    public static final String BUNDLE_FIELD_TIP =
080        "Path to default bundle. An empty path signifies that there is no"
081        + " default bundle in use.";
082
083    /** Default option value. See {@link OptionMaster#blahblah}. */
084    private final String defaultValue;
085
086    /** Default state of {@link #enableCheckBox}. */
087    private final boolean defaultCheckBox;
088
089    /** Default path for {@link #bundleField}. */
090    private final String defaultBundle;
091
092    /**
093     * Shows current default bundle. Empty means there isn't one. May be
094     * {@code null}!
095     */
096    private JTextField bundleField;
097
098    /** Used to pop up a {@link JFileChooser}. May be {@code null}! */
099    private JButton browseButton;
100
101    /**
102     * Whether or not the default bundle should be used. May be {@code null}!
103     */
104    private JCheckBox enableCheckBox;
105
106    /** Current state of {@link #enableCheckBox}. */
107    private boolean checkbox;
108
109    /** Current contents of {@link #bundleField}. Value may be {@code null}! */
110    private String path;
111
112    /**
113     * Create a new {@literal "file option"} that allows the user to select
114     * a file.
115     *
116     * @param id Option ID.
117     * @param label Option label (used in GUI).
118     * @param defaultValue Default option value.
119     * @param optionPlatform Platform restrictions for the option.
120     * @param optionVisibility Visibility restrictions for the option.
121     */
122    public FileOption(
123        final String id,
124        final String label,
125        final String defaultValue,
126        final OptionMaster.OptionPlatform optionPlatform,
127        final OptionMaster.Visibility optionVisibility)
128    {
129        super(id, label, OptionMaster.Type.DIRTREE, optionPlatform, optionVisibility);
130        this.defaultValue = defaultValue;
131        String[] defaults = parseFormat(defaultValue);
132        this.defaultCheckBox = booleanFromFormat(defaults[0]);
133        this.defaultBundle = defaults[1];
134        setValue(defaultValue);
135    }
136
137    /**
138     * Handles the user clicking on the {@link #browseButton}.
139     *
140     * @param event Currently ignored.
141     */
142    private void browseButtonActionPerformed(ActionEvent event) {
143        String defaultPath =
144            StartupManager.getInstance().getPlatform().getUserBundles();
145        String userPath = System.getProperty(USERPATH, defaultPath);
146        String bundlePath = Paths.get(userPath, BUNDLE_DIR).toString();
147        setValue(selectBundle(bundlePath));
148    }
149
150    /**
151     * Show a {@code JFileChooser} dialog that allows the user to select a
152     * bundle.
153     *
154     * @param bundleDirectory Initial directory for the {@code JFileChooser}.
155     *
156     * @return Either the path to the user's chosen bundle, or
157     * {@link #defaultValue} if the user cancelled.
158     */
159    private String selectBundle(final String bundleDirectory) {
160        JFileChooser fileChooser = new JFileChooser();
161        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
162        if ((path != null) && !path.isEmpty()) {
163            fileChooser.setSelectedFile(new File(path));
164        } else {
165            fileChooser.setCurrentDirectory(new File(bundleDirectory));
166        }
167        String result = defaultValue;
168        switch (fileChooser.showOpenDialog(null)) {
169            case JFileChooser.APPROVE_OPTION:
170                result = fileChooser.getSelectedFile().getAbsolutePath();
171                break;
172            default:
173                result = path;
174                break;
175        }
176        return '"' + getCheckBoxValue() + ';' + result + '"';
177    }
178
179    /**
180     * Returns the GUI component that represents the option.
181     *
182     * @return GUI representation of this option.
183     */
184    @Override public JComponent getComponent() {
185        bundleField = new JTextField(path);
186        bundleField.setColumns(30);
187        bundleField.setToolTipText(BUNDLE_FIELD_TIP);
188        bundleField.setEnabled(checkbox);
189
190        browseButton = new JButton(BUTTON_LABEL);
191        browseButton.setEnabled(checkbox);
192        browseButton.addActionListener(new ActionListener() {
193            @Override public void actionPerformed(ActionEvent e) {
194                browseButtonActionPerformed(e);
195            }
196        });
197
198        enableCheckBox = new JCheckBox(CHECKBOX_LABEL, checkbox);
199        enableCheckBox.addActionListener(new ActionListener() {
200            @Override
201            public void actionPerformed(ActionEvent e) {
202                boolean status = enableCheckBox.isSelected();
203                bundleField.setEnabled(status);
204                browseButton.setEnabled(status);
205            }
206        });
207        JPanel bottom = McVGuiUtils.sideBySide(bundleField, browseButton);
208        return McVGuiUtils.topBottom(enableCheckBox, bottom, McVGuiUtils.Prefer.NEITHER);
209    }
210
211    /**
212     *
213     * @return The current value of the option.
214     */
215    @Override public String getValue() {
216        return '"' + getCheckBoxValue() + ';' + getBundlePath() + '"';
217    }
218
219    public String getCheckBoxValue() {
220        boolean status = defaultCheckBox;
221        if (enableCheckBox != null) {
222            status = enableCheckBox.isSelected();
223        }
224        return status ? "1" : "0";
225    }
226
227    public String getBundlePath() {
228        String result = defaultBundle;
229        if (bundleField != null) {
230            result = bundleField.getText();
231        }
232        return result;
233    }
234
235    /**
236     * Forces the value of the option to the data specified.
237     *
238     * @param newValue New value to use.
239     */
240    @Override public void setValue(final String newValue) {
241        String[] results = parseFormat(newValue);
242        checkbox = booleanFromFormat(results[0]);
243        path = results[1];
244        SwingUtilities.invokeLater(new Runnable() {
245            @Override public void run() {
246                String[] results = parseFormat(newValue);
247                checkbox = booleanFromFormat(results[0]);
248                path = results[1];
249                if (enableCheckBox != null) {
250                    enableCheckBox.setSelected(checkbox);
251                }
252
253                // defaultValue check is to avoid blanking out the field
254                // when the user hits cancel
255                if ((bundleField != null) && !defaultBundle.equals(path)) {
256                    bundleField.setEnabled(checkbox);
257                    bundleField.setText(path);
258                }
259
260                if (browseButton != null) {
261                    browseButton.setEnabled(checkbox);
262                }
263            }
264        });
265    }
266
267    /**
268     * Friendly string representation of the option.
269     *
270     * @return {@code String} containing relevant info about the option.
271     */
272    @Override public String toString() {
273        return String.format(FORMAT, hashCode(), getOptionId(), getValue());
274    }
275
276    public static String[] parseFormat(String format) {
277        format = CLEAN_VALUE_REGEX.matcher(format).replaceAll("");
278        String checkBox = "1";
279        String path;
280        int splitAt = format.indexOf(';');
281        if (splitAt == -1) {
282            // string was something like "/path/goes/here.mcv"
283            path = format;
284        } else if (splitAt == 0) {
285            // string was something like ";/path/goes/here.mcv"
286            path = format.substring(1);
287        } else {
288            // string was something like "1;/path/goes/here.mcv"
289            checkBox = format.substring(0, splitAt);
290            path = format.substring(splitAt + 1);
291        }
292        if (path.isEmpty()) {
293            checkBox = "0";
294        }
295        return new String[] { checkBox, path };
296    }
297
298    public static boolean booleanFromFormat(String value) {
299        boolean result = false;
300        if ("1".equals(value)) {
301            result = true;
302        }
303        return result;
304    }
305}