//
// JPythonEditor.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.python;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import visad.VisADException;
import visad.util.*;
/** An editor for writing and executing JPython code in Java runtime. */
public class JPythonEditor extends CodeEditor {
/** text to be prepended to all JPython documents */
private static final String PREPENDED_TEXT =
"from visad.python.JPythonMethods import *";
/** monospaced font to use for error message reporting */
private static final Font MONO_FONT = new Font("Monospaced", Font.PLAIN, 11);
/** wrapper for PythonInterpreter object */
protected RunJPython python = null;
/** flag indicating whether to warn before auto-saving */
protected boolean warnBeforeSave = true;
/** flag indicating whether to launch scripts in a separate process */
protected boolean runSeparate = true;
/** run menu item */
private JMenuItem runItem;
/** runs the given command in a separate process */
public static String[] runCommand(String cmd)
throws IOException, VisADException
{
ArrayList list = new ArrayList();
Process proc = Runtime.getRuntime().exec(cmd);
// capture program output
InputStream istr = proc.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(istr));
String str;
while ((str = br.readLine()) != null) list.add(str);
// wait for command to terminate
try { proc.waitFor(); }
catch (InterruptedException e) { e.printStackTrace(); }
br.close();
// check exit value
if (proc.exitValue() != 0) {
throw new VisADException("exit value was non-zero");
}
// return output as a list of strings
return (String[]) list.toArray(new String[0]);
}
/** constructs a JPythonEditor */
public JPythonEditor() throws VisADException {
this(null);
}
/** constructs a JPythonEditor containing text from the given filename */
public JPythonEditor(String filename) throws VisADException {
super(filename);
python = new RunJPython();
}
/** Create and initialize the the file chooser */
protected JFileChooser doMakeFileChooser() {
JFileChooser tmpFileChooser = super.doMakeFileChooser();
// add JPython files to file chooser dialog box
tmpFileChooser.addChoosableFileFilter(
new ExtensionFileFilter("py", "JPython source code"));
return tmpFileChooser;
}
/** adjusts the line number of the given error to match the displayed text,
and highlights that line of code to indicate the source of error */
private String handleError(String err, String filename) {
// convert eol character sequences to newlines
StringTokenizer st = new StringTokenizer(err, "\r\n");
StringBuffer sbuf = new StringBuffer(err.length());
while (st.hasMoreTokens()) {
String line = st.nextToken();
// convert tabs to spaces
int tab = line.indexOf("\t");
while (tab >= 0) {
line = line.substring(0, tab) + " " + line.substring(tab + 1);
tab = line.indexOf("\t");
}
sbuf.append(line + "\n");
}
String nerr = sbuf.toString();
// adjust error's line number to match displayed line numbers
String toLine = filename + "\", ";
int index = nerr.indexOf(toLine + "line ");
if (index >= 0) {
index += toLine.length() + 5;
int nline = nerr.indexOf("\n", index);
int lineNum = -1;
try {
lineNum = Integer.parseInt(nerr.substring(index, nline));
}
catch (NumberFormatException exc) { }
lineNum--;
if (lineNum >= 1) {
highlightLine(lineNum);
nerr = nerr.substring(0, index) + lineNum + nerr.substring(nline);
}
}
// set up error message dialog
Component c = getRootPane().getParent();
final JDialog dialog =
c instanceof Dialog ?
new JDialog((Dialog) c, "JPython script error", true) :
c instanceof Frame ?
new JDialog((Frame) c, "JPython script error", true) :
new JDialog((Frame) null, "JPython script error", true);
// create dialog components
JLabel label = new JLabel("An error in the script occurred:");
label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
JTextArea area = new JTextArea(nerr, 16, 80);
area.setEditable(false);
area.setFont(MONO_FONT);
area.setWrapStyleWord(true);
JButton button = new JButton("OK");
// define dialog actions
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// dismiss dialog box
dialog.hide();
}
});
// do dialog layout
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
p.add(label);
p.add(area);
p.add(button);
dialog.setContentPane(p);
// pop up dialog with error message
dialog.pack();
dialog.show();
return nerr;
}
/** executes a line of JPython code */
public void exec(String line) throws VisADException {
python.exec(line);
}
/** executes the document as JPython source code */
public void execfile(String filename) throws VisADException {
try {
python.execfile(filename);
}
catch (VisADException exc) {
String error = handleError(exc.getMessage(), filename);
throw new VisADException(error);
}
}
/** returns a string containing the text of the document */
public String getText() {
return PREPENDED_TEXT +
System.getProperty("line.separator") + super.getText();
}
/** sets the text of the document to the current string */
public void setText(String text) {
if (text.startsWith(PREPENDED_TEXT)) {
// strip off prepended text
text = text.substring(PREPENDED_TEXT.length()).trim();
}
super.setText(text);
}
/** sets whether editor should warn user before auto-saving */
public void setWarnBeforeSave(boolean warn) { warnBeforeSave = warn; }
/** sets whether editor should run scripts in a separate process */
public void setRunSeparateProcess(boolean separate) {
runSeparate = separate;
}
/** sets the run menu item */
public void setRunItem(JMenuItem run) { runItem = run; }
/** executes the JPython script */
public void run() {
if (hasChanged()) {
if (warnBeforeSave) {
int ans = JOptionPane.showConfirmDialog(this,
"A save is required before execution. Okay to save?",
"VisAD JPython Editor", JOptionPane.YES_NO_OPTION);
if (ans != JOptionPane.YES_OPTION) return;
}
boolean success = saveFile();
if (!success) return;
}
Thread t = new Thread(new Runnable() {
public void run() {
String name = getFilename();
if (runSeparate) {
String[] out;
try {
out = runCommand("java visad.python.RunJPython " + name);
}
catch (IOException exc) {
if (DEBUG) exc.printStackTrace();
}
catch (VisADException exc) {
if (DEBUG) exc.printStackTrace();
String error = handleError(exc.getMessage(), name);
}
}
else {
runItem.setEnabled(false);
runItem.setText("Running...");
try {
execfile(name);
}
catch (VisADException exc) {
if (DEBUG) exc.printStackTrace();
}
runItem.setText("Run");
runItem.setEnabled(true);
}
}
});
t.start();
}
/** compiles the JPython script to a Java class */
public void compile() throws VisADException {
throw new VisADException("Not yet implemented!");
}
}