001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 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.awt.BorderLayout; 032import java.awt.Dimension; 033import java.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.io.File; 036import java.util.regex.Pattern; 037 038import javax.swing.JCheckBox; 039import javax.swing.JComponent; 040import javax.swing.JLabel; 041import javax.swing.JPanel; 042import javax.swing.JScrollPane; 043import javax.swing.JTree; 044import javax.swing.ToolTipManager; 045import javax.swing.event.AncestorEvent; 046import javax.swing.event.AncestorListener; 047import javax.swing.event.TreeSelectionEvent; 048import javax.swing.event.TreeSelectionListener; 049import javax.swing.tree.DefaultMutableTreeNode; 050import javax.swing.tree.DefaultTreeModel; 051import javax.swing.tree.TreePath; 052import javax.swing.tree.TreeSelectionModel; 053 054import edu.wisc.ssec.mcidasv.ArgumentManager; 055import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 056import edu.wisc.ssec.mcidasv.startupmanager.StartupManager.TreeCellRenderer; 057import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.OptionPlatform; 058import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.Type; 059import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.Visibility; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063/** 064 * Represents a startup option that should be selected from the contents of a 065 * given directory. The visual representation of this class is a tree. 066 */ 067public final class DirectoryOption extends AbstractOption { 068 069 private static final Logger logger = LoggerFactory.getLogger(DirectoryOption.class); 070 071 /** Regular expression pattern for ensuring that no quote marks are present in {@link #value}. */ 072 private static final Pattern CLEAN_VALUE_REGEX = Pattern.compile("\""); 073 074 /** Selected tree node. Value may be {@code null}. */ 075 private DefaultMutableTreeNode selected = null; 076 077 /** Current option value. Empty {@code String} signifies no selection. */ 078 private String value = ""; 079 080 /** Default value of this option. */ 081 private final String defaultValue; 082 083 public DirectoryOption(final String id, final String label, final String defaultValue, final OptionPlatform optionPlatform, final Visibility optionVisibility) { 084 super(id, label, Type.DIRTREE, optionPlatform, optionVisibility); 085 this.defaultValue = defaultValue; 086 setValue(defaultValue); 087 } 088 089 private void exploreDirectory(final String directory, final DefaultMutableTreeNode parent) { 090 assert directory != null : "Cannot traverse a null directory"; 091 System.err.println("scanning bundle directory: '"+directory+'\''); 092 File dir = new File(directory); 093 assert dir.exists() : "Cannot traverse a directory that does not exist"; 094 095 File[] files = dir.listFiles(); 096 assert files != null; 097 for (File f : files) { 098 DefaultMutableTreeNode current = new DefaultMutableTreeNode(f); 099 if (f.isDirectory()) { 100 System.err.println(f+": directory!"); 101 parent.add(current); 102 exploreDirectory(f.getPath(), current); 103 } else if (ArgumentManager.isBundle(f.getPath())) { 104 System.err.println(f+": bundle!"); 105 parent.add(current); 106 if (f.getPath().equals(getUnquotedValue())) { 107 selected = current; 108 } 109 } else { 110 System.err.println(f+": neither! :("); 111 } 112 } 113 } 114 115 private DefaultMutableTreeNode getRootNode(final String path) { 116 File bundleDir = new File(path); 117 if (bundleDir.isDirectory()) { 118 DefaultMutableTreeNode root = new DefaultMutableTreeNode(bundleDir); 119 return root; 120 } 121 return null; 122 } 123 124 private void useSelectedTreeValue(final JTree tree) { 125 assert tree != null : "cannot use a null JTree"; 126 DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent(); 127 128 if (node != null) { 129 File f = (File) node.getUserObject(); 130 if (f.isDirectory()) { 131 setValue(defaultValue); 132 } else { 133 setValue(node.toString()); 134 } 135 136 TreePath nodePath = new TreePath(node.getPath()); 137 tree.setSelectionPath(nodePath); 138 tree.scrollPathToVisible(nodePath); 139 } 140 } 141 142 @Override public JPanel getComponent() { 143 JPanel panel = new JPanel(new BorderLayout()); 144 145 final String path = StartupManager.getInstance().getPlatform().getUserBundles(); 146 final DefaultMutableTreeNode root = getRootNode(path); 147 if (root == null) { 148 return panel; 149 } 150 151 final JTree tree = new JTree(root); 152 tree.addTreeSelectionListener(e -> useSelectedTreeValue(tree)); 153 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 154 JScrollPane scroller = new JScrollPane(tree); 155 156 ToolTipManager.sharedInstance().registerComponent(tree); 157 tree.setCellRenderer(new TreeCellRenderer()); 158 scroller.setPreferredSize(new Dimension(140, 130)); 159 tree.expandRow(0); 160 161 if (selected != null) { 162 TreePath nodePath = new TreePath(selected.getPath()); 163 tree.setSelectionPath(nodePath); 164 tree.scrollPathToVisible(nodePath); 165 } 166 167 final JCheckBox enabled = new JCheckBox("Specify default bundle:", true); 168 enabled.addActionListener(e -> { 169 tree.setEnabled(enabled.isSelected()); 170 if (tree.isEnabled()) { 171 useSelectedTreeValue(tree); 172 } else { 173 setValue(defaultValue); 174 } 175 }); 176 177 // this listener is what creates (and destroys) the bundle tree. 178 // ancestorAdded is triggered when "tree" becomes visible, and 179 // ancestorRemoved is triggered when "tree" is no longer visible. 180 tree.addAncestorListener(new AncestorListener() { 181 @Override public void ancestorAdded(AncestorEvent event) { 182 System.err.println("tree visible! calling exploreDirectory: path='"+path+"' root='"+root+'\''); 183 exploreDirectory(path, root); 184 System.err.println("exploreDirectory finished"); 185 DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); 186 model.reload(); 187 tree.revalidate(); 188 } 189 190 @Override public void ancestorRemoved(AncestorEvent event) { 191 System.err.println("tree hidden!"); 192 root.removeAllChildren(); 193 DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); 194 model.reload(); 195 } 196 197 @Override public void ancestorMoved(AncestorEvent event) { } 198 }); 199 200 panel.add(enabled, BorderLayout.PAGE_START); 201 panel.add(scroller, BorderLayout.PAGE_END); 202 return panel; 203 } 204 205 @Override public String getValue() { 206 return '"' + value + '"'; 207 } 208 209 public String getUnquotedValue() { 210 return value; 211 } 212 213 @Override public void setValue(final String newValue) { 214 value = CLEAN_VALUE_REGEX.matcher(newValue).replaceAll(""); 215 } 216 217 public String toString() { 218 return String.format("[DirectoryOption@%x: optionId=%s, value=%s]", hashCode(), getOptionId(), getValue()); 219 } 220}