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 */ 028package edu.wisc.ssec.mcidasv; 029 030import static edu.wisc.ssec.mcidasv.McIdasPreferenceManager.PROP_HIQ_FONT_RENDERING; 031import static ucar.unidata.util.GuiUtils.makeMenu; 032import static ucar.unidata.util.MenuUtil.MENU_SEPARATOR; 033 034import java.io.BufferedReader; 035import java.io.FileReader; 036import java.io.IOException; 037import java.util.ArrayList; 038import java.util.List; 039import java.util.Map; 040import java.util.logging.Logger; 041import java.util.Comparator; 042 043import edu.wisc.ssec.mcidasv.startupmanager.options.BooleanOption; 044import org.python.util.PythonInterpreter; 045 046import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster; 047import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 048 049import ucar.unidata.data.DataSource; 050import ucar.unidata.data.DescriptorDataSource; 051import ucar.unidata.idv.IntegratedDataViewer; 052import ucar.unidata.idv.ui.ImageGenerator; 053import ucar.unidata.idv.ui.JythonShell; 054import ucar.unidata.util.FileManager; 055import ucar.unidata.util.Misc; 056 057import javax.swing.*; 058import javax.swing.filechooser.FileNameExtensionFilter; 059 060/** 061 * Overrides the IDV's {@link ucar.unidata.idv.JythonManager JythonManager} to 062 * associate a {@link JythonShell} with a given {@code JythonManager}. 063 */ 064public class JythonManager extends ucar.unidata.idv.JythonManager { 065 066// /** Trusty logging object. */ 067// private static final Logger logger = LoggerFactory.getLogger(JythonManager.class); 068 069 /** Associated Jython Shell. May be {@code null}. */ 070 private JythonShell jythonShell; 071 072 private static final Logger logger = 073 Logger.getLogger(JythonManager.class.getName()); 074 075 /** 076 * Create the manager and call initPython. 077 * 078 * @param idv The IDV. 079 */ 080 public JythonManager(IntegratedDataViewer idv) { 081 super(idv); 082 } 083 084 /** 085 * Create a Jython shell, if one doesn't already exist. This will also 086 * bring the window {@literal "to the front"} of the rest of the McIDAS-V 087 * session. 088 * 089 * @return JythonShell object for interactive Jython usage. 090 */ 091 public JythonShell createShell() { 092 if (jythonShell == null) { 093 jythonShell = new JythonShell(getIdv()); 094 095 } 096 jythonShell.toFront(); 097 return jythonShell; 098 } 099 100 /** 101 * Returns the Jython Shell associated with this {@code JythonManager}. 102 * 103 * @return Jython Shell being used by this manager. May be {@code null}. 104 */ 105 public JythonShell getShell() { 106 return jythonShell; 107 } 108 109 /** 110 * Create and initialize a Jython interpreter. 111 * 112 * @return Newly created Jython interpreter. 113 */ 114 @Override public PythonInterpreter createInterpreter() { 115 PythonInterpreter interpreter = super.createInterpreter(); 116 return interpreter; 117 } 118 119 /** 120 * Removes the given interpreter from the list of active interpreters. 121 * 122 * <p>Also attempts to close any Jython Shell associated with the 123 * interpreter.</p> 124 * 125 * @param interpreter Interpreter to remove. Should not be {@code null}. 126 */ 127 @Override public void removeInterpreter(PythonInterpreter interpreter) { 128 super.removeInterpreter(interpreter); 129 if ((jythonShell != null) && !jythonShell.isShellResetting() && jythonShell.getInterpreter().equals(interpreter)) { 130 jythonShell.close(); 131 jythonShell = null; 132 } 133 } 134 135 /** 136 * Overridden so that McIDAS-V can add an {@code islInterpreter} object 137 * to the interpreter's locals (before executing the contents of {@code}. 138 * 139 * @param code Jython code to evaluate. {@code null} is probably a bad idea. 140 * @param properties {@code String->Object} pairs to insert into the 141 * locals. Parameter may be {@code null}. 142 */ 143 @SuppressWarnings("unchecked") // dealing with idv code that predates generics. 144 @Override public void evaluateTrusted(String code, Map<String, Object> properties) { 145 if (properties == null) { 146 properties = CollectionHelpers.newMap(); 147 } 148 properties.putIfAbsent("islInterpreter", new ImageGenerator(getIdv())); 149 properties.putIfAbsent("_idv", getIdv()); 150 properties.putIfAbsent("idv", getIdv()); 151 super.evaluateTrusted(code, properties); 152 } 153 154 /** 155 * Return the list of menu items to use when the user has clicked on a 156 * formula {@link DataSource}. 157 * 158 * @param dataSource The data source clicked on. 159 * 160 * @return {@link List} of menu items. 161 */ 162 @SuppressWarnings("unchecked") // dealing with idv code that predates generics. 163 @Override public List doMakeFormulaDataSourceMenuItems(DataSource dataSource) { 164 List menuItems = new ArrayList(100); 165 JMenuItem menuItem; 166 167 menuItem = new JMenuItem("Create Formula..."); 168 menuItem.setToolTipText("Open Formula Editor window"); 169 menuItem.addActionListener(e -> showFormulaDialog()); 170 menuItems.add(menuItem); 171 172 List editItems; 173 if (dataSource instanceof DescriptorDataSource) { 174 editItems = doMakeEditMenuItems((DescriptorDataSource)dataSource); 175 } else { 176 editItems = doMakeEditMenuItems(); 177 } 178 // Remove any accidental top-level "Edit Formulas" header 179 editItems.removeIf(item -> (item instanceof String) && ((String)item).equalsIgnoreCase("Edit Formulas")); 180 181 // Sort within each group 182 List<Object> sortedGroupedItems = new ArrayList<>(); 183 Object currentGroupHeader = null; 184 List<JMenuItem> currentGroupItems = new ArrayList<>(); 185 186 for (Object item : editItems) { 187 if (item instanceof String) { 188 if (!currentGroupItems.isEmpty()) { 189 currentGroupItems.sort(Comparator.comparing(JMenuItem::getText, String.CASE_INSENSITIVE_ORDER)); 190 if (currentGroupHeader != null) sortedGroupedItems.add(currentGroupHeader); 191 sortedGroupedItems.addAll(currentGroupItems); 192 currentGroupItems.clear(); 193 } 194 currentGroupHeader = item; 195 } else if (item instanceof JMenuItem) { 196 currentGroupItems.add((JMenuItem)item); 197 } else { 198 if (!currentGroupItems.isEmpty()) { 199 currentGroupItems.sort(Comparator.comparing(JMenuItem::getText, String.CASE_INSENSITIVE_ORDER)); 200 if (currentGroupHeader != null) sortedGroupedItems.add(currentGroupHeader); 201 sortedGroupedItems.addAll(currentGroupItems); 202 currentGroupItems.clear(); 203 } 204 currentGroupHeader = null; 205 sortedGroupedItems.add(item); 206 } 207 } 208 209 if (!currentGroupItems.isEmpty()) { 210 currentGroupItems.sort(Comparator.comparing(JMenuItem::getText, String.CASE_INSENSITIVE_ORDER)); 211 if (currentGroupHeader != null) sortedGroupedItems.add(currentGroupHeader); 212 sortedGroupedItems.addAll(currentGroupItems); 213 } 214 215 sortMenuItems(sortedGroupedItems); 216 menuItems.add(makeMenu("Edit Formulas...", sortedGroupedItems)); 217 218 menuItems.add(MENU_SEPARATOR); 219 220 menuItem = new JMenuItem("Jython Library..."); 221 menuItem.setToolTipText("Open Jython Library window"); 222 menuItem.addActionListener(e -> showJythonEditor()); 223 menuItems.add(menuItem); 224 225 menuItem = new JMenuItem("Jython Shell..."); 226 menuItem.setToolTipText("Open Jython Shell window"); 227 menuItem.addActionListener(e -> createShell()); 228 menuItems.add(menuItem); 229 230 // McIDAS Inquiry #2701-3141 231 menuItem = new JMenuItem("Load Jython Script..."); 232 menuItem.setToolTipText("Select a Jython script to load"); 233 menuItem.addActionListener(e -> { 234 createShell(); 235 jythonShell.loadScript(); 236 }); 237 menuItems.add(menuItem); 238 239 menuItem = new JMenuItem("Run Jython Script..."); 240 menuItem.setToolTipText("Select a Jython script to run"); 241 menuItem.addActionListener(e -> { 242 createShell(); 243 jythonShell.loadAndRunScript(); 244 }); 245 menuItems.add(menuItem); 246 247 menuItems.add(MENU_SEPARATOR); 248 249 menuItem = new JMenuItem("Import..."); 250 menuItem.setToolTipText("Import formulas"); 251 menuItem.addActionListener(e -> importFormulas()); 252 menuItems.add(menuItem); 253 254 menuItem = new JMenuItem("Export..."); 255 menuItem.setToolTipText("Export Formulas"); 256 menuItem.addActionListener(e -> exportFormulas()); 257 menuItems.add(menuItem); 258 259 return menuItems; 260 } 261 262 // Recursively sort nested JMenu children 263 private void sortMenuItems(List<?> items) { 264 items.sort((o1, o2) -> { 265 if (o1 instanceof JMenuItem && o2 instanceof JMenuItem) { 266 return ((JMenuItem) o1).getText().compareToIgnoreCase(((JMenuItem) o2).getText()); 267 } 268 return 0; 269 }); 270 271 for (Object item : items) { 272 if (item instanceof JMenu) { 273 JMenu submenu = (JMenu) item; 274 List<JMenuItem> subItems = new ArrayList<>(); 275 for (int i = 0; i < submenu.getItemCount(); i++) { 276 JMenuItem child = submenu.getItem(i); 277 if (child != null) subItems.add(child); 278 } 279 sortMenuItems(subItems); 280 submenu.removeAll(); 281 for (JMenuItem sortedChild : subItems) { 282 submenu.add(sortedChild); 283 } 284 } 285 } 286 } 287 288 /** 289 * Determine if the user should be warned about a potential bug that we've been unable to resolve. 290 * 291 * <p>The conditions for the bug to appear are: 292 * <ul> 293 * <li>In background mode (i.e. running a script).</li> 294 * <li>Geometry by reference is <b>disabled</b>.</li> 295 * <li>New font rendering is <b>enabled</b>.</li> 296 * </ul> 297 * 298 * @return {@code true} if the user's configuration has made it possible for bug to manifest. 299 */ 300 public static boolean shouldWarnImageCapturing() { 301 boolean backgroundMode = McIDASV.getStaticMcv().getArgsManager().isScriptingMode(); 302 boolean shouldWarn = false; 303 OptionMaster optMaster = OptionMaster.getInstance(); 304 BooleanOption useGeometryByRef = optMaster.getBooleanOption("USE_GEOBYREF"); 305 if (useGeometryByRef != null) { 306 shouldWarn = backgroundMode 307 && !Boolean.parseBoolean(System.getProperty("visad.java3d.geometryByRef")) 308 && Boolean.parseBoolean(System.getProperty(PROP_HIQ_FONT_RENDERING, "false")); 309 } 310// System.out.println("background mode: "+backgroundMode); 311// System.out.println("geo by ref: "+Boolean.parseBoolean(System.getProperty("visad.java3d.geometryByRef"))); 312// System.out.println("new fonts: "+Boolean.parseBoolean(System.getProperty(PROP_HIQ_FONT_RENDERING, "false"))); 313// System.out.println("shouldWarn: "+shouldWarn); 314// System.out.println("props:\n"+System.getProperties()); 315// return shouldWarn; 316 return false; 317 } 318 319}