+/* $RCSfile$\r
+ * $Author$\r
+ * $Date$\r
+ * $Revision$\r
+ *\r
+ * Copyright (C) 2002-2005 The Jmol Development Team\r
+ *\r
+ * Contact: jmol-developers@lists.sf.net\r
+ *\r
+ * This library is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU Lesser General Public\r
+ * License as published by the Free Software Foundation; either\r
+ * version 2.1 of the License, or (at your option) any later version.\r
+ *\r
+ * This library is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+ * Lesser General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU Lesser General Public\r
+ * License along with this library; if not, write to the Free Software\r
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\r
+\r
+ *\r
+ * Modified and added to Jalview by A Waterhouse to extend JInternalFrame\r
+ *\r
+\r
+ */\r
+package jalview.gui;\r
+\r
+import org.jmol.api.*;\r
+\r
+import java.awt.*;\r
+import java.awt.event.*;\r
+import javax.swing.*;\r
+import javax.swing.text.*;\r
+import java.util.Vector;\r
+\r
+import org.jmol.i18n.GT;\r
+import org.jmol.util.Logger;\r
+import org.jmol.util.CommandHistory;\r
+\r
+public final class ScriptWindow extends JInternalFrame\r
+ implements ActionListener, EnterListener{\r
+\r
+ private ConsoleTextPane console;\r
+ private JButton closeButton;\r
+ private JButton runButton;\r
+ private JButton haltButton;\r
+ private JButton clearButton;\r
+ private JButton historyButton;\r
+ private JButton stateButton;\r
+ private JButton helpButton;\r
+ JmolViewer viewer;\r
+\r
+ public ScriptWindow(JmolViewer viewer)\r
+ {\r
+ this.viewer = viewer;\r
+\r
+ getContentPane().setLayout(new BorderLayout());\r
+\r
+ console = new ConsoleTextPane(this);\r
+\r
+\r
+ console.setPrompt();\r
+ getContentPane().add(new JScrollPane(console)\r
+ , BorderLayout.CENTER);\r
+\r
+ JPanel buttonPanel = new JPanel();\r
+ getContentPane().add(buttonPanel, BorderLayout.SOUTH);\r
+\r
+ runButton = new JButton(GT._("Run"));\r
+ runButton.addActionListener(this);\r
+ buttonPanel.add(runButton);\r
+\r
+ haltButton = new JButton(GT._("Halt"));\r
+ haltButton.addActionListener(this);\r
+ buttonPanel.add(haltButton);\r
+ haltButton.setEnabled(false);\r
+\r
+ clearButton = new JButton(GT._("Clear"));\r
+ clearButton.addActionListener(this);\r
+ buttonPanel.add(clearButton);\r
+\r
+ historyButton = new JButton(GT._("History"));\r
+ historyButton.addActionListener(this);\r
+ buttonPanel.add(historyButton);\r
+\r
+ stateButton = new JButton(GT._("State"));\r
+ stateButton.addActionListener(this);\r
+ buttonPanel.add(stateButton);\r
+\r
+ helpButton = new JButton(GT._("Help"));\r
+ helpButton.addActionListener(this);\r
+ buttonPanel.add(helpButton);\r
+\r
+ closeButton = new JButton(GT._("Close"));\r
+ closeButton.addActionListener(this);\r
+ buttonPanel.add(closeButton);\r
+\r
+ }\r
+\r
+ public void sendConsoleEcho(String strEcho) {\r
+ if (strEcho != null && !isError) {\r
+\r
+ console.outputEcho(strEcho);\r
+\r
+ }\r
+ setError(false);\r
+ }\r
+\r
+ boolean isError = false;\r
+ void setError(boolean TF) {\r
+ isError = TF;\r
+ //if (isError)\r
+ //console.recallCommand(true);\r
+ }\r
+\r
+ public void sendConsoleMessage(String strStatus) {\r
+ if (strStatus == null) {\r
+ console.clearContent();\r
+ console.outputStatus("");\r
+ } else if (strStatus.indexOf("ERROR:") >= 0) {\r
+ console.outputError(strStatus);\r
+ isError = true;\r
+ } else if (!isError) {\r
+ console.outputStatus(strStatus);\r
+ }\r
+ }\r
+\r
+ public void notifyScriptTermination(String strMsg, int msWalltime) {\r
+ if (strMsg != null && strMsg.indexOf("ERROR") >= 0) {\r
+ console.outputError(strMsg);\r
+ }\r
+ runButton.setEnabled(true);\r
+ haltButton.setEnabled(false);\r
+ }\r
+\r
+ public void enterPressed() {\r
+ runButton.doClick(100);\r
+ // executeCommand();\r
+ }\r
+\r
+\r
+ class ExecuteCommandThread extends Thread {\r
+\r
+ String strCommand;\r
+ ExecuteCommandThread (String command) {\r
+ strCommand = command;\r
+ }\r
+\r
+ public void run() {\r
+ try {\r
+ executeCommand(strCommand);\r
+ } catch (Exception ie) {\r
+ Logger.debug("execution command interrupted!"+ie);\r
+ }\r
+ }\r
+ }\r
+\r
+ ExecuteCommandThread execThread;\r
+ void executeCommandAsThread(){\r
+ String strCommand = console.getCommandString().trim();\r
+ if (strCommand.length() > 0) {\r
+ execThread = new ExecuteCommandThread(strCommand);\r
+ execThread.start();\r
+ }\r
+ }\r
+\r
+ void executeCommand(String strCommand) {\r
+ boolean doWait;\r
+ setError(false);\r
+ console.appendNewline();\r
+ console.setPrompt();\r
+ if (strCommand.length() > 0) {\r
+ String strErrorMessage = null;\r
+ doWait = (strCommand.indexOf("WAIT ") == 0);\r
+ if (doWait) { //for testing, mainly\r
+ // demonstrates using the statusManager system.\r
+ runButton.setEnabled(false);\r
+ haltButton.setEnabled(true);\r
+\r
+ Vector info = (Vector) viewer\r
+ .scriptWaitStatus(strCommand.substring(5),\r
+ "+fileLoaded,+scriptStarted,+scriptStatus,+scriptEcho,+scriptTerminated");\r
+ runButton.setEnabled(true);\r
+ haltButton.setEnabled(false);\r
+ /*\r
+ * info = [ statusRecortSet0, statusRecortSet1, statusRecortSet2, ...]\r
+ * statusRecordSet = [ statusRecord0, statusRecord1, statusRecord2, ...]\r
+ * statusRecord = [int msgPtr, String statusName, int intInfo, String msg]\r
+ */\r
+ for (int i = 0; i < info.size(); i++) {\r
+ Vector statusRecordSet = (Vector) info.get(i);\r
+ for (int j = 0; j < statusRecordSet.size(); j++) {\r
+ Vector statusRecord = (Vector) statusRecordSet.get(j);\r
+ Logger.info("msg#=" + statusRecord.get(0) + " "\r
+ + statusRecord.get(1) + " intInfo=" + statusRecord.get(2)\r
+ + " stringInfo=" + statusRecord.get(3));\r
+ }\r
+ }\r
+ console.appendNewline();\r
+ } else {\r
+ boolean isScriptExecuting = viewer.isScriptExecuting();\r
+ if (viewer.checkHalt(strCommand))\r
+ strErrorMessage = (isScriptExecuting ? "string execution halted with " + strCommand : "no script was executing");\r
+ else\r
+ strErrorMessage = "";//viewer.scriptCheck(strCommand);\r
+ //the problem is that scriptCheck is synchronized, so these might get backed up.\r
+ if (strErrorMessage != null && strErrorMessage.length() > 0) {\r
+ console.outputError(strErrorMessage);\r
+ } else {\r
+ //runButton.setEnabled(false);\r
+ haltButton.setEnabled(true);\r
+ viewer.script(strCommand);\r
+ }\r
+ }\r
+ }\r
+ console.grabFocus();\r
+ }\r
+\r
+ public void actionPerformed(ActionEvent e) {\r
+ Object source = e.getSource();\r
+ if (source == closeButton) {\r
+ hide();\r
+ } else if (source == runButton) {\r
+ executeCommandAsThread();\r
+ } else if (source == clearButton) {\r
+ console.clearContent();\r
+ } else if (source == historyButton) {\r
+ console.clearContent(viewer.getSetHistory(Integer.MAX_VALUE));\r
+ } else if (source == stateButton) {\r
+ console.clearContent(viewer.getStateInfo());\r
+ } else if (source == haltButton) {\r
+ viewer.haltScriptExecution();\r
+ } else if (source == helpButton) {\r
+ try{\r
+ jalview.util.BrowserLauncher.openURL(\r
+ "http://jmol.sourceforge.net/docs/JmolUserGuide/ch04.html");\r
+ }catch(Exception ex){}\r
+\r
+ }\r
+ console.grabFocus(); // always grab the focus (e.g., after clear)\r
+ }\r
+}\r
+\r
+class ConsoleTextPane extends JTextPane {\r
+\r
+ ConsoleDocument consoleDoc;\r
+ EnterListener enterListener;\r
+ JmolViewer viewer;\r
+\r
+ ConsoleTextPane(ScriptWindow scriptWindow) {\r
+ super(new ConsoleDocument());\r
+ consoleDoc = (ConsoleDocument)getDocument();\r
+ consoleDoc.setConsoleTextPane(this);\r
+ this.enterListener = (EnterListener) scriptWindow;\r
+ this.viewer = scriptWindow.viewer;\r
+ }\r
+\r
+ public String getCommandString() {\r
+ String cmd = consoleDoc.getCommandString();\r
+ return cmd;\r
+ }\r
+\r
+ public void setPrompt() {\r
+ consoleDoc.setPrompt();\r
+ }\r
+\r
+ public void appendNewline() {\r
+ consoleDoc.appendNewline();\r
+ }\r
+\r
+ public void outputError(String strError) {\r
+ consoleDoc.outputError(strError);\r
+ }\r
+\r
+ public void outputErrorForeground(String strError) {\r
+ consoleDoc.outputErrorForeground(strError);\r
+ }\r
+\r
+ public void outputEcho(String strEcho) {\r
+ consoleDoc.outputEcho(strEcho);\r
+ }\r
+\r
+ public void outputStatus(String strStatus) {\r
+ consoleDoc.outputStatus(strStatus);\r
+ }\r
+\r
+ public void enterPressed() {\r
+ if (enterListener != null)\r
+ enterListener.enterPressed();\r
+ }\r
+\r
+ public void clearContent() {\r
+ clearContent(null);\r
+ }\r
+ public void clearContent(String text) {\r
+ consoleDoc.clearContent();\r
+ if (text != null)\r
+ consoleDoc.outputEcho(text);\r
+ setPrompt();\r
+ }\r
+\r
+ /* (non-Javadoc)\r
+ * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)\r
+ */\r
+\r
+ /**\r
+ * Custom key event processing for command 0 implementation.\r
+ *\r
+ * Captures key up and key down strokes to call command history\r
+ * and redefines the same events with control down to allow\r
+ * caret vertical shift.\r
+ *\r
+ * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)\r
+ */\r
+ protected void processKeyEvent(KeyEvent ke)\r
+ {\r
+ // Id Control key is down, captures events does command\r
+ // history recall and inhibits caret vertical shift.\r
+ if (ke.getKeyCode() == KeyEvent.VK_UP\r
+ && ke.getID() == KeyEvent.KEY_PRESSED\r
+ && !ke.isControlDown())\r
+ {\r
+ recallCommand(true);\r
+ }\r
+ else if (\r
+ ke.getKeyCode() == KeyEvent.VK_DOWN\r
+ && ke.getID() == KeyEvent.KEY_PRESSED\r
+ && !ke.isControlDown())\r
+ {\r
+ recallCommand(false);\r
+ }\r
+ // If Control key is down, redefines the event as if it\r
+ // where a key up or key down stroke without modifiers.\r
+ // This allows to move the caret up and down\r
+ // with no command history recall.\r
+ else if (\r
+ (ke.getKeyCode() == KeyEvent.VK_DOWN\r
+ || ke.getKeyCode() == KeyEvent.VK_UP)\r
+ && ke.getID() == KeyEvent.KEY_PRESSED\r
+ && ke.isControlDown())\r
+ {\r
+ super\r
+ .processKeyEvent(new KeyEvent(\r
+ (Component) ke.getSource(),\r
+ ke.getID(),\r
+ ke.getWhen(),\r
+ 0, // No modifiers\r
+ ke.getKeyCode(),\r
+ ke.getKeyChar(),\r
+ ke.getKeyLocation()));\r
+ }\r
+ // Standard processing for other events.\r
+ else\r
+ {\r
+ super.processKeyEvent(ke);\r
+ //check command for compiler-identifyable syntax issues\r
+ //this may have to be taken out if people start complaining\r
+ //that only some of the commands are being checked\r
+ //that is -- that the script itself is not being fully checked\r
+\r
+ //not perfect -- help here?\r
+ if (ke.getID() == KeyEvent.KEY_RELEASED\r
+ && (ke.getKeyCode() > KeyEvent.VK_DOWN) || ke.getKeyCode() == KeyEvent.VK_BACK_SPACE)\r
+ checkCommand();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Recall command history.\r
+ *\r
+ * @param up - history up or down\r
+ */\r
+ void recallCommand(boolean up) {\r
+ String cmd = viewer.getSetHistory(up ? -1 : 1);\r
+ if (cmd == null) {\r
+ return;\r
+ }\r
+ try {\r
+ if (cmd.endsWith(CommandHistory.ERROR_FLAG)) {\r
+ cmd = cmd.substring(0, cmd.indexOf(CommandHistory.ERROR_FLAG));\r
+ consoleDoc.replaceCommand(cmd, true);\r
+ } else {\r
+ consoleDoc.replaceCommand(cmd, false);\r
+ }\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ void checkCommand() {\r
+ String strCommand = consoleDoc.getCommandString();\r
+ if (strCommand.length() == 0)\r
+ return;\r
+ consoleDoc\r
+ .colorCommand(viewer.scriptCheck(strCommand) == null ? consoleDoc.attUserInput\r
+ : consoleDoc.attError);\r
+ }\r
+\r
+\r
+}\r
+\r
+class ConsoleDocument extends DefaultStyledDocument {\r
+\r
+ ConsoleTextPane consoleTextPane;\r
+\r
+ SimpleAttributeSet attError;\r
+ SimpleAttributeSet attEcho;\r
+ SimpleAttributeSet attPrompt;\r
+ SimpleAttributeSet attUserInput;\r
+ SimpleAttributeSet attStatus;\r
+\r
+ ConsoleDocument() {\r
+ super();\r
+\r
+ attError = new SimpleAttributeSet();\r
+ StyleConstants.setForeground(attError, Color.red);\r
+\r
+ attPrompt = new SimpleAttributeSet();\r
+ StyleConstants.setForeground(attPrompt, Color.magenta);\r
+\r
+ attUserInput = new SimpleAttributeSet();\r
+ StyleConstants.setForeground(attUserInput, Color.black);\r
+\r
+ attEcho = new SimpleAttributeSet();\r
+ StyleConstants.setForeground(attEcho, Color.blue);\r
+ StyleConstants.setBold(attEcho, true);\r
+\r
+ attStatus = new SimpleAttributeSet();\r
+ StyleConstants.setForeground(attStatus, Color.black);\r
+ StyleConstants.setItalic(attStatus, true);\r
+ }\r
+\r
+ void setConsoleTextPane(ConsoleTextPane consoleTextPane) {\r
+ this.consoleTextPane = consoleTextPane;\r
+ }\r
+\r
+ Position positionBeforePrompt; // starts at 0, so first time isn't tracked (at least on Mac OS X)\r
+ Position positionAfterPrompt; // immediately after $, so this will track\r
+ int offsetAfterPrompt; // only still needed for the insertString override and replaceCommand\r
+\r
+ /**\r
+ * Removes all content of the script window, and add a new prompt.\r
+ */\r
+ void clearContent() {\r
+ try {\r
+ super.remove(0, getLength());\r
+ } catch (BadLocationException exception) {\r
+ System.out.println("Could not clear script window content: " + exception.getMessage());\r
+ }\r
+ }\r
+\r
+ void setPrompt() {\r
+ try {\r
+ super.insertString(getLength(), "$ ", attPrompt);\r
+ setOffsetPositions();\r
+ consoleTextPane.setCaretPosition(offsetAfterPrompt);\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ void setOffsetPositions() {\r
+ try {\r
+ offsetAfterPrompt = getLength();\r
+ positionBeforePrompt = createPosition(offsetAfterPrompt - 2);\r
+ // after prompt should be immediately after $ otherwise tracks the end\r
+ // of the line (and no command will be found) at least on Mac OS X it did.\r
+ positionAfterPrompt = createPosition(offsetAfterPrompt - 1);\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ void setNoPrompt() {\r
+ try {\r
+ offsetAfterPrompt = getLength();\r
+ positionAfterPrompt = positionBeforePrompt = createPosition(offsetAfterPrompt);\r
+ consoleTextPane.setCaretPosition(offsetAfterPrompt);\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ // it looks like the positionBeforePrompt does not track when it started out as 0\r
+ // and a insertString at location 0 occurs. It may be better to track the\r
+ // position after the prompt in stead\r
+ void outputBeforePrompt(String str, SimpleAttributeSet attribute) {\r
+ try {\r
+ int pt = consoleTextPane.getCaretPosition();\r
+ Position caretPosition = createPosition(pt);\r
+ pt = positionBeforePrompt.getOffset();\r
+ super.insertString(pt, str+"\n", attribute);\r
+ setOffsetPositions();\r
+ pt = caretPosition.getOffset();\r
+ consoleTextPane.setCaretPosition(pt);\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ void outputError(String strError) {\r
+ outputBeforePrompt(strError, attError);\r
+ }\r
+\r
+ void outputErrorForeground(String strError) {\r
+ try {\r
+ super.insertString(getLength(), strError+"\n", attError);\r
+ consoleTextPane.setCaretPosition(getLength());\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+\r
+ }\r
+ }\r
+\r
+ void outputEcho(String strEcho) {\r
+ outputBeforePrompt(strEcho, attEcho);\r
+ }\r
+\r
+ void outputStatus(String strStatus) {\r
+ outputBeforePrompt(strStatus, attStatus);\r
+ }\r
+\r
+ void appendNewline() {\r
+ try {\r
+ super.insertString(getLength(), "\n", attUserInput);\r
+ consoleTextPane.setCaretPosition(getLength());\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ // override the insertString to make sure everything typed ends up at the end\r
+ // or in the 'command line' using the proper font, and the newline is processed.\r
+ public void insertString(int offs, String str, AttributeSet a)\r
+ throws BadLocationException {\r
+ int ichNewline = str.indexOf('\n');\r
+ if (ichNewline > 0)\r
+ str = str.substring(0, ichNewline);\r
+ if (ichNewline != 0) {\r
+ if (offs < offsetAfterPrompt) {\r
+ offs = getLength();\r
+ }\r
+ super.insertString(offs, str, a == attError ? a : attUserInput);\r
+ consoleTextPane.setCaretPosition(offs+str.length());\r
+ }\r
+ if (ichNewline >= 0) {\r
+ consoleTextPane.enterPressed();\r
+ }\r
+ }\r
+\r
+ String getCommandString() {\r
+ String strCommand = "";\r
+ try {\r
+ int cmdStart = positionAfterPrompt.getOffset();\r
+ strCommand = getText(cmdStart, getLength() - cmdStart);\r
+ while (strCommand.length() > 0 && strCommand.charAt(0) == ' ')\r
+ strCommand = strCommand.substring(1);\r
+ } catch (BadLocationException e) {\r
+ e.printStackTrace();\r
+ }\r
+ return strCommand;\r
+ }\r
+\r
+ public void remove(int offs, int len)\r
+ throws BadLocationException {\r
+ if (offs < offsetAfterPrompt) {\r
+ len -= offsetAfterPrompt - offs;\r
+ if (len <= 0)\r
+ return;\r
+ offs = offsetAfterPrompt;\r
+ }\r
+ super.remove(offs, len);\r
+// consoleTextPane.setCaretPosition(offs);\r
+ }\r
+\r
+ public void replace(int offs, int length, String str, AttributeSet attrs)\r
+ throws BadLocationException {\r
+ if (offs < offsetAfterPrompt) {\r
+ if (offs + length < offsetAfterPrompt) {\r
+ offs = getLength();\r
+ length = 0;\r
+ } else {\r
+ length -= offsetAfterPrompt - offs;\r
+ offs = offsetAfterPrompt;\r
+ }\r
+ }\r
+ super.replace(offs, length, str, attrs);\r
+// consoleTextPane.setCaretPosition(offs + str.length());\r
+ }\r
+\r
+ /**\r
+ * Replaces current command on script.\r
+ *\r
+ * @param newCommand new command value\r
+ * @param isError true to set error color ends with #??\r
+ *\r
+ * @throws BadLocationException\r
+ */\r
+ void replaceCommand(String newCommand, boolean isError) throws BadLocationException {\r
+ if (positionAfterPrompt == positionBeforePrompt)\r
+ return;\r
+ replace(offsetAfterPrompt, getLength() - offsetAfterPrompt, newCommand,\r
+ isError ? attError : attUserInput);\r
+ }\r
+\r
+ void colorCommand(SimpleAttributeSet att) {\r
+ if (positionAfterPrompt == positionBeforePrompt)\r
+ return;\r
+ setCharacterAttributes(offsetAfterPrompt, getLength() - offsetAfterPrompt, att, true);\r
+ }\r
+}\r
+\r
+interface EnterListener {\r
+ public void enterPressed();\r
+}\r
+\r