2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3 * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 import org.jmol.api.*;
24 import java.awt.event.*;
26 import javax.swing.text.*;
27 import java.util.Vector;
29 import org.jmol.i18n.GT;
30 import org.jmol.util.Logger;
31 import org.jmol.util.CommandHistory;
33 public final class ScriptWindow extends JPanel
34 implements ActionListener, EnterListener{
36 private ConsoleTextPane console;
37 private JButton closeButton;
38 private JButton runButton;
39 private JButton haltButton;
40 private JButton clearButton;
41 private JButton historyButton;
42 private JButton stateButton;
46 public ScriptWindow(AppJmol appJmol)
48 this.viewer = appJmol.viewer;
49 this.appJmol = appJmol;
52 setLayout(new BorderLayout());
54 console = new ConsoleTextPane(this);
58 add(new JScrollPane(console)
59 , BorderLayout.CENTER);
61 JPanel buttonPanel = new JPanel();
62 add(buttonPanel, BorderLayout.SOUTH);
64 runButton = new JButton(GT._("Run"));
65 haltButton = new JButton(GT._("Halt"));
66 runButton.addActionListener(this);
67 // buttonPanel.add(runButton);
68 haltButton.addActionListener(this);
69 // buttonPanel.add(haltButton);
70 haltButton.setEnabled(false);
72 clearButton = new JButton(GT._("Clear"));
73 clearButton.addActionListener(this);
74 buttonPanel.add(clearButton);
76 historyButton = new JButton(GT._("History"));
77 historyButton.addActionListener(this);
78 buttonPanel.add(historyButton);
80 stateButton = new JButton(GT._("State"));
81 stateButton.addActionListener(this);
82 buttonPanel.add(stateButton);
84 closeButton = new JButton(GT._("Close"));
85 closeButton.addActionListener(this);
86 buttonPanel.add(closeButton);
88 for(int i=0; i<buttonPanel.getComponentCount(); i++)
90 // ((JButton)buttonPanel.getComponent(i))
91 // .setMargin(new Insets(0, 0, 0, 0));
96 public void sendConsoleEcho(String strEcho) {
97 if (strEcho != null && !isError) {
99 console.outputEcho(strEcho);
105 boolean isError = false;
106 void setError(boolean TF) {
109 //console.recallCommand(true);
112 public void sendConsoleMessage(String strStatus) {
113 if (strStatus == null) {
114 console.clearContent();
115 console.outputStatus("");
116 } else if (strStatus.indexOf("ERROR:") >= 0) {
117 console.outputError(strStatus);
119 } else if (!isError) {
120 console.outputStatus(strStatus);
124 public void notifyScriptTermination(String strMsg, int msWalltime) {
125 if (strMsg != null && strMsg.indexOf("ERROR") >= 0) {
126 console.outputError(strMsg);
128 runButton.setEnabled(true);
129 haltButton.setEnabled(false);
132 public void enterPressed() {
133 runButton.doClick(100);
138 class ExecuteCommandThread extends Thread {
141 ExecuteCommandThread (String command) {
142 strCommand = command;
147 executeCommand(strCommand);
148 } catch (Exception ie) {
149 Logger.debug("execution command interrupted!"+ie);
154 ExecuteCommandThread execThread;
155 void executeCommandAsThread(){
156 String strCommand = console.getCommandString().trim();
157 if (strCommand.length() > 0) {
158 execThread = new ExecuteCommandThread(strCommand);
163 void executeCommand(String strCommand) {
166 console.appendNewline();
168 if (strCommand.length() > 0) {
169 String strErrorMessage = null;
170 doWait = (strCommand.indexOf("WAIT ") == 0);
171 if (doWait) { //for testing, mainly
172 // demonstrates using the statusManager system.
173 runButton.setEnabled(false);
174 haltButton.setEnabled(true);
176 Vector info = (Vector) viewer
177 .scriptWaitStatus(strCommand.substring(5),
178 "+fileLoaded,+scriptStarted,+scriptStatus,+scriptEcho,+scriptTerminated");
179 runButton.setEnabled(true);
180 haltButton.setEnabled(false);
182 * info = [ statusRecortSet0, statusRecortSet1, statusRecortSet2, ...]
183 * statusRecordSet = [ statusRecord0, statusRecord1, statusRecord2, ...]
184 * statusRecord = [int msgPtr, String statusName, int intInfo, String msg]
186 for (int i = 0; i < info.size(); i++) {
187 Vector statusRecordSet = (Vector) info.get(i);
188 for (int j = 0; j < statusRecordSet.size(); j++) {
189 Vector statusRecord = (Vector) statusRecordSet.get(j);
190 Logger.info("msg#=" + statusRecord.get(0) + " "
191 + statusRecord.get(1) + " intInfo=" + statusRecord.get(2)
192 + " stringInfo=" + statusRecord.get(3));
195 console.appendNewline();
197 boolean isScriptExecuting = viewer.isScriptExecuting();
198 if (viewer.checkHalt(strCommand))
199 strErrorMessage = (isScriptExecuting ? "string execution halted with " + strCommand : "no script was executing");
201 strErrorMessage = "";//viewer.scriptCheck(strCommand);
202 //the problem is that scriptCheck is synchronized, so these might get backed up.
203 if (strErrorMessage != null && strErrorMessage.length() > 0) {
204 console.outputError(strErrorMessage);
206 //runButton.setEnabled(false);
207 haltButton.setEnabled(true);
208 viewer.script(strCommand);
215 public void actionPerformed(ActionEvent e) {
216 Object source = e.getSource();
217 if (source == closeButton) {
218 appJmol.showConsole(false);
219 } else if (source == runButton) {
220 executeCommandAsThread();
221 } else if (source == clearButton) {
222 console.clearContent();
223 } else if (source == historyButton) {
224 console.clearContent(viewer.getSetHistory(Integer.MAX_VALUE));
225 } else if (source == stateButton) {
226 console.clearContent(viewer.getStateInfo());
227 } else if (source == haltButton) {
228 viewer.haltScriptExecution();
230 console.grabFocus(); // always grab the focus (e.g., after clear)
234 class ConsoleTextPane extends JTextPane {
236 ConsoleDocument consoleDoc;
237 EnterListener enterListener;
240 ConsoleTextPane(ScriptWindow scriptWindow) {
241 super(new ConsoleDocument());
242 consoleDoc = (ConsoleDocument)getDocument();
243 consoleDoc.setConsoleTextPane(this);
244 this.enterListener = (EnterListener) scriptWindow;
245 this.viewer = scriptWindow.viewer;
248 public String getCommandString() {
249 String cmd = consoleDoc.getCommandString();
253 public void setPrompt() {
254 consoleDoc.setPrompt();
257 public void appendNewline() {
258 consoleDoc.appendNewline();
261 public void outputError(String strError) {
262 consoleDoc.outputError(strError);
265 public void outputErrorForeground(String strError) {
266 consoleDoc.outputErrorForeground(strError);
269 public void outputEcho(String strEcho) {
270 consoleDoc.outputEcho(strEcho);
273 public void outputStatus(String strStatus) {
274 consoleDoc.outputStatus(strStatus);
277 public void enterPressed() {
278 if (enterListener != null)
279 enterListener.enterPressed();
282 public void clearContent() {
285 public void clearContent(String text) {
286 consoleDoc.clearContent();
288 consoleDoc.outputEcho(text);
293 * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)
297 * Custom key event processing for command 0 implementation.
299 * Captures key up and key down strokes to call command history
300 * and redefines the same events with control down to allow
301 * caret vertical shift.
303 * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)
305 protected void processKeyEvent(KeyEvent ke)
307 // Id Control key is down, captures events does command
308 // history recall and inhibits caret vertical shift.
309 if (ke.getKeyCode() == KeyEvent.VK_UP
310 && ke.getID() == KeyEvent.KEY_PRESSED
311 && !ke.isControlDown())
316 ke.getKeyCode() == KeyEvent.VK_DOWN
317 && ke.getID() == KeyEvent.KEY_PRESSED
318 && !ke.isControlDown())
320 recallCommand(false);
322 // If Control key is down, redefines the event as if it
323 // where a key up or key down stroke without modifiers.
324 // This allows to move the caret up and down
325 // with no command history recall.
327 (ke.getKeyCode() == KeyEvent.VK_DOWN
328 || ke.getKeyCode() == KeyEvent.VK_UP)
329 && ke.getID() == KeyEvent.KEY_PRESSED
330 && ke.isControlDown())
333 .processKeyEvent(new KeyEvent(
334 (Component) ke.getSource(),
340 ke.getKeyLocation()));
342 // Standard processing for other events.
345 super.processKeyEvent(ke);
346 //check command for compiler-identifyable syntax issues
347 //this may have to be taken out if people start complaining
348 //that only some of the commands are being checked
349 //that is -- that the script itself is not being fully checked
351 //not perfect -- help here?
352 if (ke.getID() == KeyEvent.KEY_RELEASED
353 && (ke.getKeyCode() > KeyEvent.VK_DOWN) || ke.getKeyCode() == KeyEvent.VK_BACK_SPACE)
359 * Recall command history.
361 * @param up - history up or down
363 void recallCommand(boolean up) {
364 String cmd = viewer.getSetHistory(up ? -1 : 1);
369 if (cmd.endsWith(CommandHistory.ERROR_FLAG)) {
370 cmd = cmd.substring(0, cmd.indexOf(CommandHistory.ERROR_FLAG));
371 consoleDoc.replaceCommand(cmd, true);
373 consoleDoc.replaceCommand(cmd, false);
375 } catch (BadLocationException e) {
380 void checkCommand() {
381 String strCommand = consoleDoc.getCommandString();
382 if (strCommand.length() == 0)
385 .colorCommand(viewer.scriptCheck(strCommand) == null ? consoleDoc.attUserInput
386 : consoleDoc.attError);
392 class ConsoleDocument extends DefaultStyledDocument {
394 ConsoleTextPane consoleTextPane;
396 SimpleAttributeSet attError;
397 SimpleAttributeSet attEcho;
398 SimpleAttributeSet attPrompt;
399 SimpleAttributeSet attUserInput;
400 SimpleAttributeSet attStatus;
405 attError = new SimpleAttributeSet();
406 StyleConstants.setForeground(attError, Color.red);
408 attPrompt = new SimpleAttributeSet();
409 StyleConstants.setForeground(attPrompt, Color.magenta);
411 attUserInput = new SimpleAttributeSet();
412 StyleConstants.setForeground(attUserInput, Color.black);
414 attEcho = new SimpleAttributeSet();
415 StyleConstants.setForeground(attEcho, Color.blue);
416 StyleConstants.setBold(attEcho, true);
418 attStatus = new SimpleAttributeSet();
419 StyleConstants.setForeground(attStatus, Color.black);
420 StyleConstants.setItalic(attStatus, true);
423 void setConsoleTextPane(ConsoleTextPane consoleTextPane) {
424 this.consoleTextPane = consoleTextPane;
427 Position positionBeforePrompt; // starts at 0, so first time isn't tracked (at least on Mac OS X)
428 Position positionAfterPrompt; // immediately after $, so this will track
429 int offsetAfterPrompt; // only still needed for the insertString override and replaceCommand
432 * Removes all content of the script window, and add a new prompt.
434 void clearContent() {
436 super.remove(0, getLength());
437 } catch (BadLocationException exception) {
438 System.out.println("Could not clear script window content: " + exception.getMessage());
444 super.insertString(getLength(), "$ ", attPrompt);
445 setOffsetPositions();
446 consoleTextPane.setCaretPosition(offsetAfterPrompt);
447 } catch (BadLocationException e) {
452 void setOffsetPositions() {
454 offsetAfterPrompt = getLength();
455 positionBeforePrompt = createPosition(offsetAfterPrompt - 2);
456 // after prompt should be immediately after $ otherwise tracks the end
457 // of the line (and no command will be found) at least on Mac OS X it did.
458 positionAfterPrompt = createPosition(offsetAfterPrompt - 1);
459 } catch (BadLocationException e) {
466 offsetAfterPrompt = getLength();
467 positionAfterPrompt = positionBeforePrompt = createPosition(offsetAfterPrompt);
468 consoleTextPane.setCaretPosition(offsetAfterPrompt);
469 } catch (BadLocationException e) {
474 // it looks like the positionBeforePrompt does not track when it started out as 0
475 // and a insertString at location 0 occurs. It may be better to track the
476 // position after the prompt in stead
477 void outputBeforePrompt(String str, SimpleAttributeSet attribute) {
479 int pt = consoleTextPane.getCaretPosition();
480 Position caretPosition = createPosition(pt);
481 pt = positionBeforePrompt.getOffset();
482 super.insertString(pt, str+"\n", attribute);
483 setOffsetPositions();
484 pt = caretPosition.getOffset();
485 consoleTextPane.setCaretPosition(pt);
486 } catch (BadLocationException e) {
491 void outputError(String strError) {
492 outputBeforePrompt(strError, attError);
495 void outputErrorForeground(String strError) {
497 super.insertString(getLength(), strError+"\n", attError);
498 consoleTextPane.setCaretPosition(getLength());
499 } catch (BadLocationException e) {
505 void outputEcho(String strEcho) {
506 outputBeforePrompt(strEcho, attEcho);
509 void outputStatus(String strStatus) {
510 outputBeforePrompt(strStatus, attStatus);
513 void appendNewline() {
515 super.insertString(getLength(), "\n", attUserInput);
516 consoleTextPane.setCaretPosition(getLength());
517 } catch (BadLocationException e) {
522 // override the insertString to make sure everything typed ends up at the end
523 // or in the 'command line' using the proper font, and the newline is processed.
524 public void insertString(int offs, String str, AttributeSet a)
525 throws BadLocationException {
526 int ichNewline = str.indexOf('\n');
528 str = str.substring(0, ichNewline);
529 if (ichNewline != 0) {
530 if (offs < offsetAfterPrompt) {
533 super.insertString(offs, str, a == attError ? a : attUserInput);
534 consoleTextPane.setCaretPosition(offs+str.length());
536 if (ichNewline >= 0) {
537 consoleTextPane.enterPressed();
541 String getCommandString() {
542 String strCommand = "";
544 int cmdStart = positionAfterPrompt.getOffset();
545 strCommand = getText(cmdStart, getLength() - cmdStart);
546 while (strCommand.length() > 0 && strCommand.charAt(0) == ' ')
547 strCommand = strCommand.substring(1);
548 } catch (BadLocationException e) {
554 public void remove(int offs, int len)
555 throws BadLocationException {
556 if (offs < offsetAfterPrompt) {
557 len -= offsetAfterPrompt - offs;
560 offs = offsetAfterPrompt;
562 super.remove(offs, len);
563 // consoleTextPane.setCaretPosition(offs);
566 public void replace(int offs, int length, String str, AttributeSet attrs)
567 throws BadLocationException {
568 if (offs < offsetAfterPrompt) {
569 if (offs + length < offsetAfterPrompt) {
573 length -= offsetAfterPrompt - offs;
574 offs = offsetAfterPrompt;
577 super.replace(offs, length, str, attrs);
578 // consoleTextPane.setCaretPosition(offs + str.length());
582 * Replaces current command on script.
584 * @param newCommand new command value
585 * @param isError true to set error color ends with #??
587 * @throws BadLocationException
589 void replaceCommand(String newCommand, boolean isError) throws BadLocationException {
590 if (positionAfterPrompt == positionBeforePrompt)
592 replace(offsetAfterPrompt, getLength() - offsetAfterPrompt, newCommand,
593 isError ? attError : attUserInput);
596 void colorCommand(SimpleAttributeSet att) {
597 if (positionAfterPrompt == positionBeforePrompt)
599 setCharacterAttributes(offsetAfterPrompt, getLength() - offsetAfterPrompt, att, true);
603 interface EnterListener {
604 public void enterPressed();