001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2017 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 static edu.wisc.ssec.mcidasv.util.McVGuiUtils.sideBySide; 032import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.topBottom; 033 034import java.io.File; 035 036import java.nio.file.Paths; 037import java.util.regex.Pattern; 038 039import java.awt.event.ActionEvent; 040 041import javax.swing.JButton; 042import javax.swing.JCheckBox; 043import javax.swing.JComponent; 044import javax.swing.JFileChooser; 045import javax.swing.JPanel; 046import javax.swing.JTextField; 047import javax.swing.SwingUtilities; 048 049import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 050import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 051 052/** 053 * Represents a file selection. 054 */ 055public final class FileOption extends AbstractOption { 056 057 /** Label for {@link #browseButton}. */ 058 private static final String BUTTON_LABEL = "Browse..."; 059 060 /** Label for {@link #enableCheckBox}. */ 061 private static final String CHECKBOX_LABEL = "Specify default bundle:"; 062 063 /** System property that points to the McIDAS-V user path. */ 064 private static final String USERPATH = "mcv.userpath"; 065 066 /** Name of the {@literal "bundle"} subdirectory of the user path. */ 067 private static final String BUNDLE_DIR = "bundles"; 068 069 /** Constant that represents string version of the {@code 1} boolean. */ 070 private static final String TRUE_STRING = "1"; 071 072 /** Constant that represents string version of the {@code 0} boolean. */ 073 private static final String FALSE_STRING = "0"; 074 075 /** 076 * Regular expression pattern for ensuring that no quote marks are present. 077 */ 078 private static final Pattern CLEAN_VALUE_REGEX = Pattern.compile("\""); 079 080 /** Formatting string used by {@link #toString()}. */ 081 private static final String FORMAT = 082 "[FileOption@%x: optionId=%s, value=%s]"; 083 084 /** Tool tip used by {@link #bundleField}. */ 085 public static final String BUNDLE_FIELD_TIP = 086 "Path to default bundle. An empty path signifies that there is no" 087 + " default bundle in use."; 088 089 /** Default option value. See {@link OptionMaster#blahblah}. */ 090 private final String defaultValue; 091 092 /** Default state of {@link #enableCheckBox}. */ 093 private final boolean defaultCheckBox; 094 095 /** Default path for {@link #bundleField}. */ 096 private final String defaultBundle; 097 098 /** 099 * Shows current default bundle. Empty means there isn't one. May be 100 * {@code null}! 101 */ 102 private JTextField bundleField; 103 104 /** Used to pop up a {@link JFileChooser}. May be {@code null}! */ 105 private JButton browseButton; 106 107 /** 108 * Whether or not the default bundle should be used. May be {@code null}! 109 */ 110 private JCheckBox enableCheckBox; 111 112 /** Current state of {@link #enableCheckBox}. */ 113 private boolean checkbox; 114 115 /** Current contents of {@link #bundleField}. Value may be {@code null}! */ 116 private String path; 117 118 /** 119 * Create a new {@literal "file option"} that allows the user to select 120 * a file. 121 * 122 * @param id Option ID. 123 * @param label Option label (used in GUI). 124 * @param defaultValue Default option value. 125 * @param platform Platform restrictions for the option. 126 * @param visibility Visibility restrictions for the option. 127 */ 128 public FileOption( 129 final String id, 130 final String label, 131 final String defaultValue, 132 final OptionMaster.OptionPlatform platform, 133 final OptionMaster.Visibility visibility) 134 { 135 super(id, label, OptionMaster.Type.DIRTREE, platform, visibility); 136 this.defaultValue = defaultValue; 137 String[] defaults = parseFormat(defaultValue); 138 this.defaultCheckBox = booleanFromFormat(defaults[0]); 139 this.defaultBundle = defaults[1]; 140 setValue(defaultValue); 141 } 142 143 /** 144 * Handles the user clicking on the {@link #browseButton}. 145 * 146 * @param event Currently ignored. 147 */ 148 private void browseButtonActionPerformed(ActionEvent event) { 149 String defaultPath = 150 StartupManager.getInstance().getPlatform().getUserBundles(); 151 String userPath = System.getProperty(USERPATH, defaultPath); 152 String bundlePath = Paths.get(userPath, BUNDLE_DIR).toString(); 153 setValue(selectBundle(bundlePath)); 154 } 155 156 /** 157 * Show a {@code JFileChooser} dialog that allows the user to select a 158 * bundle. 159 * 160 * @param bundleDirectory Initial directory for the {@code JFileChooser}. 161 * 162 * @return Either the path to the user's chosen bundle, or 163 * {@link #defaultValue} if the user cancelled. 164 */ 165 private String selectBundle(final String bundleDirectory) { 166 JFileChooser fileChooser = new JFileChooser(); 167 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 168 if ((path != null) && !path.isEmpty()) { 169 fileChooser.setSelectedFile(new File(path)); 170 } else { 171 fileChooser.setCurrentDirectory(new File(bundleDirectory)); 172 } 173 String result; 174 switch (fileChooser.showOpenDialog(null)) { 175 case JFileChooser.APPROVE_OPTION: 176 result = fileChooser.getSelectedFile().getAbsolutePath(); 177 break; 178 default: 179 result = path; 180 break; 181 } 182 return '"' + getCheckBoxValue() + ';' + result + '"'; 183 } 184 185 /** 186 * Returns the GUI component that represents the option. 187 * 188 * @return GUI representation of this option. 189 */ 190 @Override public JComponent getComponent() { 191 bundleField = new JTextField(path); 192 bundleField.setColumns(30); 193 bundleField.setToolTipText(BUNDLE_FIELD_TIP); 194 bundleField.setEnabled(checkbox); 195 196 browseButton = new JButton(BUTTON_LABEL); 197 browseButton.setEnabled(checkbox); 198 browseButton.addActionListener(this::browseButtonActionPerformed); 199 200 enableCheckBox = new JCheckBox(CHECKBOX_LABEL, checkbox); 201 enableCheckBox.addActionListener(e -> { 202 boolean status = enableCheckBox.isSelected(); 203 bundleField.setEnabled(status); 204 browseButton.setEnabled(status); 205 }); 206 JPanel bottom = sideBySide(bundleField, browseButton); 207 return topBottom(enableCheckBox, bottom, McVGuiUtils.Prefer.NEITHER); 208 } 209 210 /** 211 * Returns a string containing the state of {@link #enableCheckBox} and 212 * {@link #bundleField}. 213 * 214 * <p>Results should look like {@code 0;/path/to/bundle.mcv}.</p> 215 * 216 * @return Current value of the option. 217 */ 218 @Override public String getValue() { 219 return '"' + getCheckBoxValue() + ';' + getBundlePath() + '"'; 220 } 221 222 /** 223 * Returns a string representation of {@link #enableCheckBox}. 224 * 225 * @return Either {@code 1} or {@code 0} depending upon the state of 226 * {@link #enableCheckBox}. 227 */ 228 public String getCheckBoxValue() { 229 boolean status = defaultCheckBox; 230 if (enableCheckBox != null) { 231 status = enableCheckBox.isSelected(); 232 } 233 return status ? TRUE_STRING : FALSE_STRING; 234 } 235 236 /** 237 * Returns a string representating the path to the startup bundle. 238 * 239 * @return If {@link #bundleField} is {@code null}, {@link #defaultBundle} 240 * is returned. Otherwise the contents of the text field are returned. 241 */ 242 public String getBundlePath() { 243 String result = defaultBundle; 244 if (bundleField != null) { 245 result = bundleField.getText(); 246 } 247 return result; 248 } 249 250 /** 251 * Forces the value of the option to the data specified. 252 * 253 * @param newValue New value to use. 254 */ 255 @Override public void setValue(final String newValue) { 256 String[] results = parseFormat(newValue); 257 checkbox = booleanFromFormat(results[0]); 258 path = results[1]; 259 SwingUtilities.invokeLater(() -> { 260 String[] results1 = parseFormat(newValue); 261 checkbox = booleanFromFormat(results1[0]); 262 path = results1[1]; 263 if (enableCheckBox != null) { 264 enableCheckBox.setSelected(checkbox); 265 } 266 267 // defaultValue check is to avoid blanking out the field 268 // when the user hits cancel 269 if ((bundleField != null) && !defaultBundle.equals(path)) { 270 bundleField.setEnabled(checkbox); 271 bundleField.setText(path); 272 } 273 274 if (browseButton != null) { 275 browseButton.setEnabled(checkbox); 276 } 277 }); 278 } 279 280 /** 281 * Friendly string representation of the option. 282 * 283 * @return {@code String} containing relevant info about the option. 284 */ 285 @Override public String toString() { 286 return String.format(FORMAT, hashCode(), getOptionId(), getValue()); 287 } 288 289 /** 290 * Attempt to extract something sensible from the value given in 291 * {@literal "runMcV-Prefs"}. 292 * 293 * <p>Expected format is something like {@code "0;/path/to/bundle.mcv"} or 294 * {@code "1;"}. The first example would signify that 295 * {@link #enableCheckBox} is not selected, and the contents of 296 * {@link #bundleField} are {@code /path/to/bundle.mcv}. The second 297 * example would signify that {@link #enableCheckBox} is selected, and the 298 * contents of {@link #bundleField} should be an empty string.</p> 299 * 300 * @param format See method description for details. {@code null} not 301 * allowed. 302 * 303 * @return Two element array where the first element is the state of 304 * {@link #enableCheckBox} and the second is the bundle path. Note that 305 * the bundle path may be empty. 306 * 307 * @see #booleanFromFormat(String) 308 */ 309 public static String[] parseFormat(String format) { 310 format = CLEAN_VALUE_REGEX.matcher(format).replaceAll(""); 311 String checkBox = TRUE_STRING; 312 String path; 313 int splitAt = format.indexOf(';'); 314 if (splitAt == -1) { 315 // string was something like "/path/goes/here.mcv" 316 path = format; 317 } else if (splitAt == 0) { 318 // string was something like ";/path/goes/here.mcv" 319 path = format.substring(1); 320 } else { 321 // string was something like "1;/path/goes/here.mcv" 322 checkBox = format.substring(0, splitAt); 323 path = format.substring(splitAt + 1); 324 } 325 if (path.isEmpty()) { 326 checkBox = FALSE_STRING; 327 } 328 return new String[] { checkBox, path }; 329 } 330 331 /** 332 * Convert the strings {@code 1} and {@code 0} to their corresponding 333 * boolean values. 334 * 335 * @param value String to convert. {@code null} or empty strings accepted. 336 * 337 * @return Returns {@code true} if {@code value} is {@code 1}. Otherwise 338 * returns {@code false}. 339 */ 340 public static boolean booleanFromFormat(String value) { 341 boolean result = false; 342 if (TRUE_STRING.equals(value)) { 343 result = true; 344 } 345 return result; 346 } 347}