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