run and halt buttons must have listeners added
[jalview.git] / src / jalview / gui / ScriptWindow.java
1 /* $RCSfile$\r
2  * $Author$\r
3  * $Date$\r
4  * $Revision$\r
5  *\r
6  * Copyright (C) 2002-2005  The Jmol Development Team\r
7  *\r
8  * Contact: jmol-developers@lists.sf.net\r
9  *\r
10  *  This library is free software; you can redistribute it and/or\r
11  *  modify it under the terms of the GNU Lesser General Public\r
12  *  License as published by the Free Software Foundation; either\r
13  *  version 2.1 of the License, or (at your option) any later version.\r
14  *\r
15  *  This library is distributed in the hope that it will be useful,\r
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
18  *  Lesser General Public License for more details.\r
19  *\r
20  *  You should have received a copy of the GNU Lesser General Public\r
21  *  License along with this library; if not, write to the Free Software\r
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\r
23 \r
24  *\r
25  * Modified and added to Jalview by A Waterhouse to extend JInternalFrame\r
26  *\r
27 \r
28  */\r
29 package jalview.gui;\r
30 \r
31 import org.jmol.api.*;\r
32 \r
33 import java.awt.*;\r
34 import java.awt.event.*;\r
35 import javax.swing.*;\r
36 import javax.swing.text.*;\r
37 import java.util.Vector;\r
38 \r
39 import org.jmol.i18n.GT;\r
40 import org.jmol.util.Logger;\r
41 import org.jmol.util.CommandHistory;\r
42 \r
43 public final class ScriptWindow extends JPanel\r
44     implements ActionListener, EnterListener{\r
45 \r
46   private ConsoleTextPane console;\r
47   private JButton closeButton;\r
48   private JButton runButton;\r
49   private JButton haltButton;\r
50   private JButton clearButton;\r
51   private JButton historyButton;\r
52   private JButton stateButton;\r
53    JmolViewer viewer;\r
54   AppJMol appJmol;\r
55 \r
56   public ScriptWindow(AppJMol appJmol)\r
57   {\r
58     this.viewer = appJmol.viewer;\r
59     this.appJmol = appJmol;\r
60 \r
61 \r
62     setLayout(new BorderLayout());\r
63 \r
64     console = new ConsoleTextPane(this);\r
65 \r
66 \r
67     console.setPrompt();\r
68     add(new JScrollPane(console)\r
69                          , BorderLayout.CENTER);\r
70 \r
71     JPanel buttonPanel = new JPanel();\r
72     add(buttonPanel, BorderLayout.SOUTH);\r
73 \r
74     runButton = new JButton(GT._("Run"));\r
75     haltButton = new JButton(GT._("Halt"));\r
76     runButton.addActionListener(this);\r
77    // buttonPanel.add(runButton);\r
78     haltButton.addActionListener(this);\r
79   //  buttonPanel.add(haltButton);\r
80     haltButton.setEnabled(false);\r
81 \r
82     clearButton = new JButton(GT._("Clear"));\r
83     clearButton.addActionListener(this);\r
84     buttonPanel.add(clearButton);\r
85 \r
86     historyButton = new JButton(GT._("History"));\r
87     historyButton.addActionListener(this);\r
88     buttonPanel.add(historyButton);\r
89 \r
90     stateButton = new JButton(GT._("State"));\r
91     stateButton.addActionListener(this);\r
92     buttonPanel.add(stateButton);\r
93 \r
94     closeButton = new JButton(GT._("Close"));\r
95     closeButton.addActionListener(this);\r
96     buttonPanel.add(closeButton);\r
97 \r
98     for(int i=0; i<buttonPanel.getComponentCount(); i++)\r
99     {\r
100     //  ((JButton)buttonPanel.getComponent(i))\r
101     //      .setMargin(new Insets(0, 0, 0, 0));\r
102     }\r
103 \r
104   }\r
105 \r
106   public void sendConsoleEcho(String strEcho) {\r
107     if (strEcho != null && !isError) {\r
108 \r
109       console.outputEcho(strEcho);\r
110 \r
111     }\r
112     setError(false);\r
113   }\r
114 \r
115   boolean isError = false;\r
116   void setError(boolean TF) {\r
117     isError = TF;\r
118     //if (isError)\r
119       //console.recallCommand(true);\r
120   }\r
121 \r
122   public void sendConsoleMessage(String strStatus) {\r
123     if (strStatus == null) {\r
124       console.clearContent();\r
125       console.outputStatus("");\r
126     } else if (strStatus.indexOf("ERROR:") >= 0) {\r
127       console.outputError(strStatus);\r
128       isError = true;\r
129     } else if (!isError) {\r
130       console.outputStatus(strStatus);\r
131     }\r
132   }\r
133 \r
134   public void notifyScriptTermination(String strMsg, int msWalltime) {\r
135     if (strMsg != null && strMsg.indexOf("ERROR") >= 0) {\r
136       console.outputError(strMsg);\r
137     }\r
138     runButton.setEnabled(true);\r
139     haltButton.setEnabled(false);\r
140   }\r
141 \r
142   public void enterPressed() {\r
143     runButton.doClick(100);\r
144     //    executeCommand();\r
145   }\r
146 \r
147 \r
148   class ExecuteCommandThread extends Thread {\r
149 \r
150     String strCommand;\r
151     ExecuteCommandThread (String command) {\r
152       strCommand = command;\r
153     }\r
154 \r
155     public void run() {\r
156       try {\r
157         executeCommand(strCommand);\r
158       } catch (Exception ie) {\r
159         Logger.debug("execution command interrupted!"+ie);\r
160       }\r
161     }\r
162   }\r
163 \r
164   ExecuteCommandThread execThread;\r
165   void executeCommandAsThread(){\r
166     String strCommand = console.getCommandString().trim();\r
167     if (strCommand.length() > 0) {\r
168       execThread = new ExecuteCommandThread(strCommand);\r
169       execThread.start();\r
170     }\r
171   }\r
172 \r
173   void executeCommand(String strCommand) {\r
174     boolean doWait;\r
175     setError(false);\r
176     console.appendNewline();\r
177     console.setPrompt();\r
178     if (strCommand.length() > 0) {\r
179       String strErrorMessage = null;\r
180       doWait = (strCommand.indexOf("WAIT ") == 0);\r
181       if (doWait) { //for testing, mainly\r
182         // demonstrates using the statusManager system.\r
183         runButton.setEnabled(false);\r
184         haltButton.setEnabled(true);\r
185 \r
186         Vector info = (Vector) viewer\r
187             .scriptWaitStatus(strCommand.substring(5),\r
188                 "+fileLoaded,+scriptStarted,+scriptStatus,+scriptEcho,+scriptTerminated");\r
189         runButton.setEnabled(true);\r
190         haltButton.setEnabled(false);\r
191         /*\r
192          * info = [ statusRecortSet0, statusRecortSet1, statusRecortSet2, ...]\r
193          * statusRecordSet = [ statusRecord0, statusRecord1, statusRecord2, ...]\r
194          * statusRecord = [int msgPtr, String statusName, int intInfo, String msg]\r
195          */\r
196         for (int i = 0; i < info.size(); i++) {\r
197           Vector statusRecordSet = (Vector) info.get(i);\r
198           for (int j = 0; j < statusRecordSet.size(); j++) {\r
199             Vector statusRecord = (Vector) statusRecordSet.get(j);\r
200             Logger.info("msg#=" + statusRecord.get(0) + " "\r
201                 + statusRecord.get(1) + " intInfo=" + statusRecord.get(2)\r
202                 + " stringInfo=" + statusRecord.get(3));\r
203           }\r
204         }\r
205         console.appendNewline();\r
206       } else {\r
207         boolean isScriptExecuting = viewer.isScriptExecuting();\r
208         if (viewer.checkHalt(strCommand))\r
209           strErrorMessage = (isScriptExecuting ? "string execution halted with " + strCommand : "no script was executing");\r
210         else\r
211           strErrorMessage = "";//viewer.scriptCheck(strCommand);\r
212         //the problem is that scriptCheck is synchronized, so these might get backed up.\r
213         if (strErrorMessage != null && strErrorMessage.length() > 0) {\r
214           console.outputError(strErrorMessage);\r
215         } else {\r
216           //runButton.setEnabled(false);\r
217           haltButton.setEnabled(true);\r
218           viewer.script(strCommand);\r
219         }\r
220       }\r
221     }\r
222     console.grabFocus();\r
223   }\r
224 \r
225   public void actionPerformed(ActionEvent e) {\r
226     Object source = e.getSource();\r
227     if (source == closeButton) {\r
228       appJmol.showConsole(false);\r
229     } else if (source == runButton) {\r
230       executeCommandAsThread();\r
231     } else if (source == clearButton) {\r
232       console.clearContent();\r
233     } else if (source == historyButton) {\r
234       console.clearContent(viewer.getSetHistory(Integer.MAX_VALUE));\r
235     } else if (source == stateButton) {\r
236       console.clearContent(viewer.getStateInfo());\r
237     } else if (source == haltButton) {\r
238       viewer.haltScriptExecution();\r
239     }\r
240     console.grabFocus(); // always grab the focus (e.g., after clear)\r
241   }\r
242 }\r
243 \r
244 class ConsoleTextPane extends JTextPane {\r
245 \r
246   ConsoleDocument consoleDoc;\r
247   EnterListener enterListener;\r
248   JmolViewer viewer;\r
249 \r
250   ConsoleTextPane(ScriptWindow scriptWindow) {\r
251     super(new ConsoleDocument());\r
252     consoleDoc = (ConsoleDocument)getDocument();\r
253     consoleDoc.setConsoleTextPane(this);\r
254     this.enterListener = (EnterListener) scriptWindow;\r
255     this.viewer = scriptWindow.viewer;\r
256   }\r
257 \r
258   public String getCommandString() {\r
259     String cmd = consoleDoc.getCommandString();\r
260     return cmd;\r
261   }\r
262 \r
263   public void setPrompt() {\r
264     consoleDoc.setPrompt();\r
265   }\r
266 \r
267   public void appendNewline() {\r
268     consoleDoc.appendNewline();\r
269   }\r
270 \r
271   public void outputError(String strError) {\r
272     consoleDoc.outputError(strError);\r
273   }\r
274 \r
275   public void outputErrorForeground(String strError) {\r
276     consoleDoc.outputErrorForeground(strError);\r
277   }\r
278 \r
279   public void outputEcho(String strEcho) {\r
280     consoleDoc.outputEcho(strEcho);\r
281   }\r
282 \r
283   public void outputStatus(String strStatus) {\r
284     consoleDoc.outputStatus(strStatus);\r
285   }\r
286 \r
287   public void enterPressed() {\r
288     if (enterListener != null)\r
289       enterListener.enterPressed();\r
290   }\r
291 \r
292   public void clearContent() {\r
293     clearContent(null);\r
294   }\r
295   public void clearContent(String text) {\r
296     consoleDoc.clearContent();\r
297     if (text != null)\r
298       consoleDoc.outputEcho(text);\r
299     setPrompt();\r
300   }\r
301 \r
302    /* (non-Javadoc)\r
303     * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)\r
304     */\r
305 \r
306    /**\r
307     * Custom key event processing for command 0 implementation.\r
308     *\r
309     * Captures key up and key down strokes to call command history\r
310     * and redefines the same events with control down to allow\r
311     * caret vertical shift.\r
312     *\r
313     * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)\r
314     */\r
315    protected void processKeyEvent(KeyEvent ke)\r
316    {\r
317       // Id Control key is down, captures events does command\r
318       // history recall and inhibits caret vertical shift.\r
319       if (ke.getKeyCode() == KeyEvent.VK_UP\r
320          && ke.getID() == KeyEvent.KEY_PRESSED\r
321          && !ke.isControlDown())\r
322       {\r
323          recallCommand(true);\r
324       }\r
325       else if (\r
326          ke.getKeyCode() == KeyEvent.VK_DOWN\r
327             && ke.getID() == KeyEvent.KEY_PRESSED\r
328             && !ke.isControlDown())\r
329       {\r
330          recallCommand(false);\r
331       }\r
332       // If Control key is down, redefines the event as if it\r
333       // where a key up or key down stroke without modifiers.\r
334       // This allows to move the caret up and down\r
335       // with no command history recall.\r
336       else if (\r
337          (ke.getKeyCode() == KeyEvent.VK_DOWN\r
338             || ke.getKeyCode() == KeyEvent.VK_UP)\r
339             && ke.getID() == KeyEvent.KEY_PRESSED\r
340             && ke.isControlDown())\r
341       {\r
342          super\r
343             .processKeyEvent(new KeyEvent(\r
344                (Component) ke.getSource(),\r
345                ke.getID(),\r
346                ke.getWhen(),\r
347                0,         // No modifiers\r
348                ke.getKeyCode(),\r
349                ke.getKeyChar(),\r
350                ke.getKeyLocation()));\r
351       }\r
352       // Standard processing for other events.\r
353       else\r
354       {\r
355          super.processKeyEvent(ke);\r
356          //check command for compiler-identifyable syntax issues\r
357          //this may have to be taken out if people start complaining\r
358          //that only some of the commands are being checked\r
359          //that is -- that the script itself is not being fully checked\r
360 \r
361          //not perfect -- help here?\r
362          if (ke.getID() == KeyEvent.KEY_RELEASED\r
363              && (ke.getKeyCode() > KeyEvent.VK_DOWN) || ke.getKeyCode() == KeyEvent.VK_BACK_SPACE)\r
364            checkCommand();\r
365       }\r
366    }\r
367 \r
368    /**\r
369    * Recall command history.\r
370    *\r
371    * @param up - history up or down\r
372    */\r
373    void recallCommand(boolean up) {\r
374      String cmd = viewer.getSetHistory(up ? -1 : 1);\r
375     if (cmd == null) {\r
376       return;\r
377     }\r
378     try {\r
379       if (cmd.endsWith(CommandHistory.ERROR_FLAG)) {\r
380         cmd = cmd.substring(0, cmd.indexOf(CommandHistory.ERROR_FLAG));\r
381         consoleDoc.replaceCommand(cmd, true);\r
382       } else {\r
383         consoleDoc.replaceCommand(cmd, false);\r
384       }\r
385     } catch (BadLocationException e) {\r
386       e.printStackTrace();\r
387     }\r
388   }\r
389 \r
390    void checkCommand() {\r
391     String strCommand = consoleDoc.getCommandString();\r
392     if (strCommand.length() == 0)\r
393       return;\r
394     consoleDoc\r
395         .colorCommand(viewer.scriptCheck(strCommand) == null ? consoleDoc.attUserInput\r
396             : consoleDoc.attError);\r
397   }\r
398 \r
399 \r
400 }\r
401 \r
402 class ConsoleDocument extends DefaultStyledDocument {\r
403 \r
404   ConsoleTextPane consoleTextPane;\r
405 \r
406   SimpleAttributeSet attError;\r
407   SimpleAttributeSet attEcho;\r
408   SimpleAttributeSet attPrompt;\r
409   SimpleAttributeSet attUserInput;\r
410   SimpleAttributeSet attStatus;\r
411 \r
412   ConsoleDocument() {\r
413     super();\r
414 \r
415     attError = new SimpleAttributeSet();\r
416     StyleConstants.setForeground(attError, Color.red);\r
417 \r
418     attPrompt = new SimpleAttributeSet();\r
419     StyleConstants.setForeground(attPrompt, Color.magenta);\r
420 \r
421     attUserInput = new SimpleAttributeSet();\r
422     StyleConstants.setForeground(attUserInput, Color.black);\r
423 \r
424     attEcho = new SimpleAttributeSet();\r
425     StyleConstants.setForeground(attEcho, Color.blue);\r
426     StyleConstants.setBold(attEcho, true);\r
427 \r
428     attStatus = new SimpleAttributeSet();\r
429     StyleConstants.setForeground(attStatus, Color.black);\r
430     StyleConstants.setItalic(attStatus, true);\r
431   }\r
432 \r
433   void setConsoleTextPane(ConsoleTextPane consoleTextPane) {\r
434     this.consoleTextPane = consoleTextPane;\r
435   }\r
436 \r
437   Position positionBeforePrompt; // starts at 0, so first time isn't tracked (at least on Mac OS X)\r
438   Position positionAfterPrompt;  // immediately after $, so this will track\r
439   int offsetAfterPrompt;         // only still needed for the insertString override and replaceCommand\r
440 \r
441   /**\r
442    * Removes all content of the script window, and add a new prompt.\r
443    */\r
444   void clearContent() {\r
445       try {\r
446           super.remove(0, getLength());\r
447       } catch (BadLocationException exception) {\r
448           System.out.println("Could not clear script window content: " + exception.getMessage());\r
449       }\r
450   }\r
451 \r
452   void setPrompt() {\r
453     try {\r
454       super.insertString(getLength(), "$ ", attPrompt);\r
455       setOffsetPositions();\r
456       consoleTextPane.setCaretPosition(offsetAfterPrompt);\r
457     } catch (BadLocationException e) {\r
458       e.printStackTrace();\r
459     }\r
460   }\r
461 \r
462   void setOffsetPositions() {\r
463     try {\r
464       offsetAfterPrompt = getLength();\r
465       positionBeforePrompt = createPosition(offsetAfterPrompt - 2);\r
466       // after prompt should be immediately after $ otherwise tracks the end\r
467       // of the line (and no command will be found) at least on Mac OS X it did.\r
468       positionAfterPrompt = createPosition(offsetAfterPrompt - 1);\r
469     } catch (BadLocationException e) {\r
470       e.printStackTrace();\r
471     }\r
472   }\r
473 \r
474   void setNoPrompt() {\r
475     try {\r
476       offsetAfterPrompt = getLength();\r
477       positionAfterPrompt = positionBeforePrompt = createPosition(offsetAfterPrompt);\r
478       consoleTextPane.setCaretPosition(offsetAfterPrompt);\r
479     } catch (BadLocationException e) {\r
480       e.printStackTrace();\r
481     }\r
482   }\r
483 \r
484   // it looks like the positionBeforePrompt does not track when it started out as 0\r
485   // and a insertString at location 0 occurs. It may be better to track the\r
486   // position after the prompt in stead\r
487   void outputBeforePrompt(String str, SimpleAttributeSet attribute) {\r
488     try {\r
489       int pt = consoleTextPane.getCaretPosition();\r
490       Position caretPosition = createPosition(pt);\r
491       pt = positionBeforePrompt.getOffset();\r
492       super.insertString(pt, str+"\n", attribute);\r
493       setOffsetPositions();\r
494       pt = caretPosition.getOffset();\r
495       consoleTextPane.setCaretPosition(pt);\r
496     } catch (BadLocationException e) {\r
497       e.printStackTrace();\r
498     }\r
499   }\r
500 \r
501   void outputError(String strError) {\r
502     outputBeforePrompt(strError, attError);\r
503   }\r
504 \r
505   void outputErrorForeground(String strError) {\r
506     try {\r
507       super.insertString(getLength(), strError+"\n", attError);\r
508       consoleTextPane.setCaretPosition(getLength());\r
509     } catch (BadLocationException e) {\r
510       e.printStackTrace();\r
511 \r
512     }\r
513   }\r
514 \r
515   void outputEcho(String strEcho) {\r
516     outputBeforePrompt(strEcho, attEcho);\r
517   }\r
518 \r
519   void outputStatus(String strStatus) {\r
520     outputBeforePrompt(strStatus, attStatus);\r
521   }\r
522 \r
523   void appendNewline() {\r
524     try {\r
525       super.insertString(getLength(), "\n", attUserInput);\r
526       consoleTextPane.setCaretPosition(getLength());\r
527     } catch (BadLocationException e) {\r
528       e.printStackTrace();\r
529     }\r
530   }\r
531 \r
532   // override the insertString to make sure everything typed ends up at the end\r
533   // or in the 'command line' using the proper font, and the newline is processed.\r
534   public void insertString(int offs, String str, AttributeSet a)\r
535     throws BadLocationException {\r
536     int ichNewline = str.indexOf('\n');\r
537     if (ichNewline > 0)\r
538       str = str.substring(0, ichNewline);\r
539     if (ichNewline != 0) {\r
540       if (offs < offsetAfterPrompt) {\r
541         offs = getLength();\r
542       }\r
543       super.insertString(offs, str, a == attError ? a : attUserInput);\r
544       consoleTextPane.setCaretPosition(offs+str.length());\r
545     }\r
546     if (ichNewline >= 0) {\r
547       consoleTextPane.enterPressed();\r
548     }\r
549   }\r
550 \r
551   String getCommandString() {\r
552     String strCommand = "";\r
553     try {\r
554       int cmdStart = positionAfterPrompt.getOffset();\r
555       strCommand =  getText(cmdStart, getLength() - cmdStart);\r
556       while (strCommand.length() > 0 && strCommand.charAt(0) == ' ')\r
557         strCommand = strCommand.substring(1);\r
558     } catch (BadLocationException e) {\r
559       e.printStackTrace();\r
560     }\r
561     return strCommand;\r
562   }\r
563 \r
564   public void remove(int offs, int len)\r
565     throws BadLocationException {\r
566     if (offs < offsetAfterPrompt) {\r
567       len -= offsetAfterPrompt - offs;\r
568       if (len <= 0)\r
569         return;\r
570       offs = offsetAfterPrompt;\r
571     }\r
572     super.remove(offs, len);\r
573 //    consoleTextPane.setCaretPosition(offs);\r
574   }\r
575 \r
576   public void replace(int offs, int length, String str, AttributeSet attrs)\r
577     throws BadLocationException {\r
578     if (offs < offsetAfterPrompt) {\r
579       if (offs + length < offsetAfterPrompt) {\r
580         offs = getLength();\r
581         length = 0;\r
582       } else {\r
583         length -= offsetAfterPrompt - offs;\r
584         offs = offsetAfterPrompt;\r
585       }\r
586     }\r
587     super.replace(offs, length, str, attrs);\r
588 //    consoleTextPane.setCaretPosition(offs + str.length());\r
589   }\r
590 \r
591    /**\r
592    * Replaces current command on script.\r
593    *\r
594    * @param newCommand new command value\r
595    * @param isError    true to set error color  ends with #??\r
596    *\r
597    * @throws BadLocationException\r
598    */\r
599   void replaceCommand(String newCommand, boolean isError) throws BadLocationException {\r
600     if (positionAfterPrompt == positionBeforePrompt)\r
601       return;\r
602     replace(offsetAfterPrompt, getLength() - offsetAfterPrompt, newCommand,\r
603         isError ? attError : attUserInput);\r
604   }\r
605 \r
606   void colorCommand(SimpleAttributeSet att) {\r
607     if (positionAfterPrompt == positionBeforePrompt)\r
608       return;\r
609     setCharacterAttributes(offsetAfterPrompt, getLength() - offsetAfterPrompt, att, true);\r
610   }\r
611 }\r
612 \r
613 interface EnterListener {\r
614   public void enterPressed();\r
615 }\r
616 \r