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;
041
042import edu.wisc.ssec.mcidasv.startupmanager.options.BooleanOption;
043import org.python.util.PythonInterpreter;
044
045import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster;
046import edu.wisc.ssec.mcidasv.util.CollectionHelpers;
047
048import ucar.unidata.data.DataSource;
049import ucar.unidata.data.DescriptorDataSource;
050import ucar.unidata.idv.IntegratedDataViewer;
051import ucar.unidata.idv.ui.ImageGenerator;
052import ucar.unidata.idv.ui.JythonShell;
053import ucar.unidata.util.FileManager;
054import ucar.unidata.util.Misc;
055
056import javax.swing.*;
057import javax.swing.filechooser.FileNameExtensionFilter;
058
059/**
060 * Overrides the IDV's {@link ucar.unidata.idv.JythonManager JythonManager} to 
061 * associate a {@link JythonShell} with a given {@code JythonManager}.
062 */
063public class JythonManager extends ucar.unidata.idv.JythonManager {
064    
065//    /** Trusty logging object. */
066//    private static final Logger logger = LoggerFactory.getLogger(JythonManager.class);
067    
068    /** Associated Jython Shell. May be {@code null}. */
069    private JythonShell jythonShell;
070
071    private static final Logger logger =
072            Logger.getLogger(JythonManager.class.getName());
073    
074    /**
075     * Create the manager and call initPython.
076     *
077     * @param idv The IDV.
078     */
079    public JythonManager(IntegratedDataViewer idv) {
080        super(idv);
081    }
082    
083    /**
084     * Create a Jython shell, if one doesn't already exist. This will also 
085     * bring the window {@literal "to the front"} of the rest of the McIDAS-V
086     * session.
087     * 
088     * @return JythonShell object for interactive Jython usage.
089     */
090    public JythonShell createShell() {
091        if (jythonShell == null) {
092            jythonShell = new JythonShell(getIdv());
093            
094        }
095        jythonShell.toFront();
096        return jythonShell;
097    }
098
099    public JythonShell createShellWithScript(String file) {
100        if (jythonShell == null) {
101            jythonShell = new JythonShell(getIdv());
102
103        }
104        jythonShell.toFront();
105        String cmd = "";
106        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
107            String line;
108            while ((line = reader.readLine()) != null) {
109                cmd += line + "\n";
110            }
111        } catch (IOException e) {
112            logger.info("Error reading scripting file: " + e.getMessage());
113        }
114
115        jythonShell.eval(cmd);
116        return jythonShell;
117    }
118    
119    /** 
120     * Returns the Jython Shell associated with this {@code JythonManager}.
121     * 
122     * @return Jython Shell being used by this manager. May be {@code null}.
123     */
124    public JythonShell getShell() {
125        return jythonShell;
126    }
127    
128    /**
129     * Create and initialize a Jython interpreter.
130     * 
131     * @return Newly created Jython interpreter.
132     */
133    @Override public PythonInterpreter createInterpreter() {
134        PythonInterpreter interpreter = super.createInterpreter();
135        return interpreter;
136    }
137    
138    /**
139     * Removes the given interpreter from the list of active interpreters. 
140     * 
141     * <p>Also attempts to close any Jython Shell associated with the 
142     * interpreter.</p>
143     * 
144     * @param interpreter Interpreter to remove. Should not be {@code null}. 
145     */
146    @Override public void removeInterpreter(PythonInterpreter interpreter) {
147        super.removeInterpreter(interpreter);
148        if ((jythonShell != null) && !jythonShell.isShellResetting() && jythonShell.getInterpreter().equals(interpreter)) {
149            jythonShell.close();
150            jythonShell = null;
151        }
152    }
153
154    /**
155     * Overridden so that McIDAS-V can add an {@code islInterpreter} object
156     * to the interpreter's locals (before executing the contents of {@code}.
157     * 
158     * @param code Jython code to evaluate. {@code null} is probably a bad idea.
159     * @param properties {@code String->Object} pairs to insert into the 
160     * locals. Parameter may be {@code null}.
161     */
162    @SuppressWarnings("unchecked") // dealing with idv code that predates generics.
163    @Override public void evaluateTrusted(String code, Map<String, Object> properties) {
164        if (properties == null) {
165            properties = CollectionHelpers.newMap();
166        }
167        properties.putIfAbsent("islInterpreter", new ImageGenerator(getIdv()));
168        properties.putIfAbsent("_idv", getIdv());
169        properties.putIfAbsent("idv", getIdv());
170        super.evaluateTrusted(code, properties);
171    }
172    
173    /**
174     * Return the list of menu items to use when the user has clicked on a 
175     * formula {@link DataSource}.
176     * 
177     * @param dataSource The data source clicked on.
178     * 
179     * @return {@link List} of menu items.
180     */
181    @SuppressWarnings("unchecked") // dealing with idv code that predates generics.
182    @Override public List doMakeFormulaDataSourceMenuItems(DataSource dataSource) {
183        List menuItems = new ArrayList(100);
184        JMenuItem menuItem;
185
186        menuItem = new JMenuItem("Create Formula");
187        menuItem.addActionListener(e -> showFormulaDialog());
188        menuItems.add(menuItem);
189
190        List editItems;
191        if (dataSource instanceof DescriptorDataSource) {
192            editItems = doMakeEditMenuItems((DescriptorDataSource)dataSource);
193        } else {
194            editItems = doMakeEditMenuItems();
195        }
196        menuItems.add(makeMenu("Edit Formulas", editItems));
197
198        menuItems.add(MENU_SEPARATOR);
199
200        menuItem = new JMenuItem("Jython Library");
201        menuItem.addActionListener(e -> showJythonEditor());
202        menuItems.add(menuItem);
203
204        menuItem = new JMenuItem("Jython Shell");
205        menuItem.addActionListener(e -> createShell());
206        menuItems.add(menuItem);
207
208        menuItem = new JMenuItem("Load Jython Script");
209        menuItem.addActionListener(e -> {
210            FileNameExtensionFilter filter = new FileNameExtensionFilter("Python Files (*.py)", "py");
211            String file = FileManager.getReadFile("Load Script", filter);
212            createShellWithScript(file);
213        });
214
215        menuItems.add(menuItem);
216
217        menuItems.add(MENU_SEPARATOR);
218
219        menuItem = new JMenuItem("Import");
220        menuItem.addActionListener(e -> importFormulas());
221        menuItems.add(menuItem);
222
223        menuItem = new JMenuItem("Export");
224        menuItem.addActionListener(e -> exportFormulas());
225        menuItems.add(menuItem);
226
227        return menuItems;
228    }
229
230    /**
231     * Determine if the user should be warned about a potential bug that we've been unable to resolve.
232     *
233     * <p>The conditions for the bug to appear are:
234     * <ul>
235     *     <li>In background mode (i.e. running a script).</li>
236     *     <li>Geometry by reference is <b>disabled</b>.</li>
237     *     <li>New font rendering is <b>enabled</b>.</li>
238     * </ul>
239     *
240     * @return {@code true} if the user's configuration has made it possible for bug to manifest.
241     */
242    public static boolean shouldWarnImageCapturing() {
243        boolean backgroundMode = McIDASV.getStaticMcv().getArgsManager().isScriptingMode();
244        boolean shouldWarn = false;
245        OptionMaster optMaster = OptionMaster.getInstance();
246        BooleanOption useGeometryByRef = optMaster.getBooleanOption("USE_GEOBYREF");
247        if (useGeometryByRef != null) {
248            shouldWarn = backgroundMode
249                    && !Boolean.parseBoolean(System.getProperty("visad.java3d.geometryByRef"))
250                    && Boolean.parseBoolean(System.getProperty(PROP_HIQ_FONT_RENDERING, "false"));
251        }
252//        System.out.println("background mode: "+backgroundMode);
253//        System.out.println("geo by ref: "+Boolean.parseBoolean(System.getProperty("visad.java3d.geometryByRef")));
254//        System.out.println("new fonts: "+Boolean.parseBoolean(System.getProperty(PROP_HIQ_FONT_RENDERING, "false")));
255//        System.out.println("shouldWarn: "+shouldWarn);
256//        System.out.println("props:\n"+System.getProperties());
257//        return shouldWarn;
258        return false;
259    }
260
261}