001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.ui;
038:
039: import java.awt.Toolkit;
040: import java.awt.Color;
041: import java.awt.EventQueue;
042: import java.awt.RenderingHints;
043: import java.awt.Graphics;
044: import java.awt.Graphics2D;
045: import java.awt.event.ActionEvent;
046: import java.awt.event.KeyEvent;
047:
048: import java.io.File;
049:
050: import java.util.EventListener;
051: import java.util.Vector;
052:
053: import javax.swing.AbstractAction;
054: import javax.swing.Action;
055: import javax.swing.ActionMap;
056: import javax.swing.BorderFactory;
057: import javax.swing.InputMap;
058: import javax.swing.JTextArea;
059: import javax.swing.KeyStroke;
060: import javax.swing.SwingUtilities;
061: import javax.swing.border.Border;
062: import javax.swing.event.CaretListener;
063: import javax.swing.event.CaretEvent;
064: import javax.swing.text.AttributeSet;
065: import javax.swing.text.BadLocationException;
066: import javax.swing.text.DefaultEditorKit;
067: import javax.swing.text.MutableAttributeSet;
068: import javax.swing.text.SimpleAttributeSet;
069: import javax.swing.text.StyleConstants;
070: import javax.swing.text.DefaultStyledDocument;
071:
072: import edu.rice.cs.drjava.DrJava;
073: import edu.rice.cs.drjava.config.OptionConstants;
074: import edu.rice.cs.drjava.config.OptionListener;
075: import edu.rice.cs.drjava.config.OptionEvent;
076: import edu.rice.cs.drjava.model.repl.InputListener;
077: import edu.rice.cs.drjava.model.repl.InteractionsDocument;
078: import edu.rice.cs.drjava.model.repl.InteractionsDJDocument;
079: import edu.rice.cs.drjava.model.repl.InteractionsListener;
080: import edu.rice.cs.drjava.model.repl.InteractionsModel;
081:
082: import edu.rice.cs.util.swing.Utilities;
083: import edu.rice.cs.util.text.ConsoleDocument;
084: import edu.rice.cs.util.CompletionMonitor;
085: import edu.rice.cs.util.Lambda;
086: import edu.rice.cs.util.Log;
087: import edu.rice.cs.util.UnexpectedException;
088:
089: /** This class installs listeners and actions between an InteractionsDocument (the model) and an InteractionsPane
090: * (the view). We may want to refactor this class into a different package. <p>
091: * (The PopupConsole was introduced in version 1.29 of this file and subsequently removed.)
092: *
093: * @version $Id: InteractionsController.java 4255 2007-08-28 19:17:37Z mgricken $
094: */
095: public class InteractionsController extends AbstractConsoleController {
096:
097: private static final Log _log = new Log("ConsoleController.txt",
098: false);
099:
100: private static final String INPUT_ENTERED_NAME = "Input Entered";
101: private static final String INSERT_NEWLINE_NAME = "Insert Newline";
102:
103: /** Style for System.in box */
104: public static final String INPUT_BOX_STYLE = "input.box.style";
105:
106: /** The symbol used in the document for the input box. */
107: public static final String INPUT_BOX_SYMBOL = "[DrJava Input Box]";
108:
109: /** InteractionsModel to handle interpretation. */
110: private volatile InteractionsModel _model;
111:
112: /** Document from the model.*/
113: private volatile InteractionsDocument _doc;
114:
115: /** Style to use for error messages. */
116: private volatile SimpleAttributeSet _errStyle;
117:
118: /** Style to use for debug messages. */
119: private final SimpleAttributeSet _debugStyle;
120:
121: /** Lambda used to input text into the embedded System.in input box */
122: private volatile Lambda<String, String> _insertTextCommand;
123:
124: /** Runnable command used to force the System.in input to complete <p>
125: * <b>NOTE:</b> This command must be executed on swing's event handling thread.
126: */
127: private volatile Runnable _inputCompletionCommand;
128:
129: /** Default implementation of the insert text command */
130: private static final Lambda<String, String> _defaultInsertTextCommand = new Lambda<String, String>() {
131: public String apply(String input) {
132: throw new UnsupportedOperationException(
133: "Cannot insert text. There is no console input in progress");
134: }
135: };
136:
137: /** Default implementation of the input completion command */
138: private static final Runnable _defaultInputCompletionCommand = new Runnable() {
139: public void run() { /* Do nothing */
140: }
141: };
142:
143: /** A temporary variable used to hold a box allocated inside getConsoleInput below. */
144: private volatile InputBox _box;
145: /** A temporary variable used to hold the text fetched from _box in getConsoleInput below. */
146: private volatile String _text;
147:
148: /** Listens for input requests from System.in, displaying an input box as needed. */
149: protected volatile InputListener _inputListener = new InputListener() {
150: public String getConsoleInput() {
151: final CompletionMonitor completionMonitor = new CompletionMonitor();
152: _box = new InputBox();
153:
154: // Embed the input box into the interactions pane.
155: // This operation must be performed in the UI thread
156: SwingUtilities.invokeLater(new Runnable() {
157: public void run() {
158:
159: // These commands only run in the event thread
160: final Lambda<String, String> insertTextCommand = _box
161: .makeInsertTextCommand(); // command for testing
162:
163: final Runnable inputCompletionCommand = new Runnable() { // command for terminating each input interaction
164: public void run() {
165: assert EventQueue.isDispatchThread();
166: // Reset the commands to their default inactive state
167: _setConsoleInputCommands(
168: _defaultInputCompletionCommand,
169: _defaultInsertTextCommand);
170:
171: _box.disableInputs();
172: _text = _box.getText() + "\n";
173:
174: /* Move the cursor back to the end of the interactions pane while preventing _doc from changing in the
175: * interim. */
176: _doc.acquireReadLock();
177: try {
178: _pane.setEditable(true);
179: _pane
180: .setCaretPosition(_doc
181: .getLength());
182: } finally {
183: _doc.releaseReadLock();
184: }
185: _pane.requestFocusInWindow();
186:
187: completionMonitor.set();
188: }
189: };
190:
191: _box
192: .setInputCompletionCommand(inputCompletionCommand);
193: _setConsoleInputCommands(inputCompletionCommand,
194: insertTextCommand);
195: _pane.setEditable(true);
196:
197: // create an empty MutableAttributeSet for _box
198: MutableAttributeSet inputAttributes = new SimpleAttributeSet();
199:
200: // initialize MutableAttributeSet to the attributes of the _box component
201: StyleConstants.setComponent(inputAttributes, _box);
202:
203: /* Insert box in document. */
204: _doc.acquireWriteLock();
205: try {
206: _doc.insertBeforeLastPrompt(" ",
207: _doc.DEFAULT_STYLE);
208:
209: // bind INPUT_BOX_STYLE to inputAttributes in the associated InteractionsDJDocument
210: _adapter.setDocStyle(INPUT_BOX_STYLE,
211: inputAttributes);
212:
213: // and insert the symbol for the input box with the correct style (identifying it as our InputBox)
214: _doc.insertBeforeLastPrompt(INPUT_BOX_SYMBOL,
215: INPUT_BOX_STYLE);
216:
217: _doc.insertBeforeLastPrompt("\n",
218: _doc.DEFAULT_STYLE);
219: } finally {
220: _doc.releaseWriteLock();
221: }
222:
223: _box.setVisible(true);
224: SwingUtilities.invokeLater(new Runnable() {
225: public void run() {
226: _box.requestFocusInWindow();
227: }
228: });
229:
230: _pane.setEditable(false);
231: }
232: });
233: fireConsoleInputStarted();
234:
235: // Wait for the inputCompletionCommand to be invoked
236: completionMonitor.waitOne();
237:
238: fireConsoleInputCompleted(_text);
239:
240: return _text;
241: }
242: };
243:
244: private Vector<ConsoleStateListener> _consoleStateListeners;
245:
246: private InteractionsListener _viewListener = new InteractionsListener() {
247: public void interactionStarted() {
248: }
249:
250: public void interactionEnded() {
251: _pane.requestFocusInWindow();
252: }
253:
254: public void interactionErrorOccurred(int offset, int length) {
255: }
256:
257: public void interpreterResetting() {
258: Utilities.invokeLater(new Runnable() {
259: public void run() {
260: _adapter.clearColoring();
261: // _pane.resetPrompts(); // NOT USED
262: }
263: });
264: }
265:
266: public void interpreterReady(File wd) {
267: }
268:
269: public void interpreterResetFailed(Throwable t) {
270: }
271:
272: public void interpreterExited(int status) {
273: }
274:
275: public void interpreterChanged(boolean inProgress) {
276: }
277:
278: public void interactionIncomplete() {
279: }
280:
281: public void slaveJVMUsed() {
282: }
283: };
284:
285: /** Glue together the given model and a new view.
286: * @param model An InteractionsModel
287: * @param adapter InteractionsDJDocument being used by the model's doc
288: */
289: public InteractionsController(final InteractionsModel model,
290: InteractionsDJDocument adapter) {
291: this (model, adapter, new InteractionsPane(adapter) {
292: public int getPromptPos() {
293: return model.getDocument().getPromptPos();
294: }
295: });
296: }
297:
298: /** Glue together the given model and view.
299: * @param model An InteractionsModel
300: * @param adapter InteractionsDJDocument being used by the model's doc
301: * @param pane An InteractionsPane
302: */
303: public InteractionsController(InteractionsModel model,
304: InteractionsDJDocument adapter, InteractionsPane pane) {
305: super (adapter, pane);
306: DefaultEditorKit d = pane.EDITOR_KIT;
307:
308: for (Action a : d.getActions()) {
309: if (a.getValue(Action.NAME).equals(
310: DefaultEditorKit.upAction))
311: defaultUpAction = a;
312: if (a.getValue(Action.NAME).equals(
313: DefaultEditorKit.downAction))
314: defaultDownAction = a;
315: }
316:
317: _model = model;
318: _doc = model.getDocument();
319: _errStyle = new SimpleAttributeSet();
320: _debugStyle = new SimpleAttributeSet();
321:
322: _model.setInputListener(_inputListener);
323: _model.addListener(_viewListener);
324:
325: _inputCompletionCommand = _defaultInputCompletionCommand;
326: _insertTextCommand = _defaultInsertTextCommand;
327: _consoleStateListeners = new Vector<ConsoleStateListener>();
328: // _pane.addCaretListener(new CaretListener() { // Update the cachedCaretPostion
329: // public void caretUpdate(CaretEvent e) {
330: // _log.log("Caret Event: " + e + " from source " + e.getSource());
331: //// setCachedCaretPos(e.getDot());
332: // }
333: // });
334:
335: _init(); // residual superclass initialization
336: }
337:
338: public void addConsoleStateListener(ConsoleStateListener listener) {
339: _consoleStateListeners.add(listener);
340: }
341:
342: public void removeConsoleStateListener(ConsoleStateListener listener) {
343: _consoleStateListeners.remove(listener);
344: }
345:
346: private void fireConsoleInputStarted() {
347: for (ConsoleStateListener listener : _consoleStateListeners) {
348: listener.consoleInputStarted(this );
349: }
350: }
351:
352: private void fireConsoleInputCompleted(String text) {
353: for (ConsoleStateListener listener : _consoleStateListeners) {
354: listener.consoleInputCompleted(text, this );
355: }
356: }
357:
358: /** Gets the input listener for console input requests. ONLY used in unit tests.
359: * @return the input listener for console input requests.
360: */
361: public InputListener getInputListener() {
362: return _inputListener;
363: }
364:
365: /** Forces console input to complete without the user hitting <Enter>. Called by MainFrame when reset is called so
366: * that this lock is released. This method is thread safe.
367: * @throws UnsupportedOperationException If the interactions pane is not receiving console input
368: */
369: public void interruptConsoleInput() {
370: SwingUtilities.invokeLater(_inputCompletionCommand);
371: }
372:
373: /** Inserts text into the console. Can only be called from the event thread. ONLY used in unit tests.
374: * @param input The text to insert into the console input box
375: * @throws UnsupportedOperationException If the the interactions pane is not receiving console input
376: */
377: public void insertConsoleText(String input) {
378: _insertTextCommand.apply(input);
379: }
380:
381: /** Accessor method for the InteractionsModel.
382: * @return the interactions model
383: */
384: public InteractionsModel getInteractionsModel() {
385: return _model;
386: }
387:
388: /** Allows the abstract superclass to use the document.
389: * @return the InteractionsDocument
390: */
391: public ConsoleDocument getConsoleDoc() {
392: return _doc;
393: }
394:
395: /** Accessor method for the InteractionsDocument. */
396: public InteractionsDocument getDocument() {
397: return _doc;
398: }
399:
400: /** Adds AttributeSets as named styles to the document adapter. */
401: protected void _addDocumentStyles() {
402: // Add AbstractConsoleController styles
403: super ._addDocumentStyles();
404:
405: // Error
406: _errStyle.addAttributes(_defaultStyle);
407: _errStyle.addAttribute(StyleConstants.Foreground, DrJava
408: .getConfig().getSetting(
409: OptionConstants.INTERACTIONS_ERROR_COLOR));
410: _errStyle.addAttribute(StyleConstants.Bold, Boolean.TRUE);
411: _adapter.setDocStyle(InteractionsDocument.ERROR_STYLE,
412: _errStyle);
413: DrJava.getConfig().addOptionListener(
414: OptionConstants.INTERACTIONS_ERROR_COLOR,
415: new OptionListener<Color>() {
416: public void optionChanged(OptionEvent<Color> oe) {
417: _errStyle.addAttribute(
418: StyleConstants.Foreground, oe.value);
419: }
420: });
421:
422: // Debug
423: _debugStyle.addAttributes(_defaultStyle);
424: _debugStyle.addAttribute(StyleConstants.Foreground, DrJava
425: .getConfig().getSetting(
426: OptionConstants.DEBUG_MESSAGE_COLOR));
427: _debugStyle.addAttribute(StyleConstants.Bold, Boolean.TRUE);
428: _adapter.setDocStyle(InteractionsDocument.DEBUGGER_STYLE,
429: _debugStyle);
430: DrJava.getConfig().addOptionListener(
431: OptionConstants.DEBUG_MESSAGE_COLOR,
432: new OptionListener<Color>() {
433: public void optionChanged(OptionEvent<Color> oe) {
434: _debugStyle.addAttribute(
435: StyleConstants.Foreground, oe.value);
436: }
437: });
438: }
439:
440: /** Updates all document styles with the attributes contained in newSet. This behavior is only used in Mac OS X,
441: * JDK 1.4.1, since setFont() works fine on JTextPane on all other tested platforms.
442: * @param newSet Style containing new attributes to use.
443: */
444: protected void _updateStyles(AttributeSet newSet) {
445: super ._updateStyles(newSet);
446: _errStyle.addAttributes(newSet);
447: StyleConstants.setBold(_errStyle, true); // ensure err is always bold
448: _debugStyle.addAttributes(newSet);
449: StyleConstants.setBold(_debugStyle, true); // ensure debug is always bold
450: }
451:
452: /** Adds listeners to the model. */
453: protected void _setupModel() {
454: _adapter.addDocumentListener(new CaretUpdateListener());
455: _doc.setBeep(_pane.getBeep());
456: }
457:
458: /** Adds actions to the view. */
459: protected void _setupView() {
460: super ._setupView();
461:
462: // Get proper cross-platform mask.
463: int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
464:
465: // Add actions with keystrokes
466: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
467: KeyEvent.VK_ENTER, 0), evalAction);
468: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
469: KeyEvent.VK_ENTER, java.awt.Event.SHIFT_MASK),
470: newLineAction);
471: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
472: KeyEvent.VK_B, mask), clearCurrentAction);
473:
474: // Up and down need to be bound both for keypad and not
475: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
476: KeyEvent.VK_KP_UP, 0), moveUpAction);
477: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
478: KeyEvent.VK_UP, 0), moveUpAction);
479: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
480: KeyEvent.VK_UP, mask), historyPrevAction);
481: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
482: KeyEvent.VK_KP_DOWN, 0), moveDownAction);
483: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
484: KeyEvent.VK_DOWN, 0), moveDownAction);
485: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
486: KeyEvent.VK_DOWN, mask), historyNextAction);
487: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
488: KeyEvent.VK_TAB, 0), historyReverseSearchAction);
489: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
490: KeyEvent.VK_TAB, java.awt.Event.SHIFT_MASK),
491: historyForwardSearchAction);
492:
493: // Left needs to be prevented from rolling cursor back before the prompt.
494: // Both left and right should lock when caret is before the prompt.
495: // Caret is allowed before the prompt for the purposes of mouse-based copy-
496: // and-paste.
497: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
498: KeyEvent.VK_KP_LEFT, 0), moveLeftAction);
499: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
500: KeyEvent.VK_LEFT, 0), moveLeftAction);
501: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
502: KeyEvent.VK_KP_RIGHT, 0), moveRightAction);
503: _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(
504: KeyEvent.VK_RIGHT, 0), moveRightAction);
505:
506: // Prevent previous word action from going past the prompt
507: _pane.addActionForKeyStroke(DrJava.getConfig().getSetting(
508: OptionConstants.KEY_PREVIOUS_WORD), prevWordAction);
509: DrJava.getConfig().addOptionListener(
510: OptionConstants.KEY_PREVIOUS_WORD,
511: new OptionListener<KeyStroke>() {
512: public void optionChanged(OptionEvent<KeyStroke> oe) {
513: _pane
514: .addActionForKeyStroke(
515: DrJava
516: .getConfig()
517: .getSetting(
518: OptionConstants.KEY_PREVIOUS_WORD),
519: prevWordAction);
520: }
521: });
522:
523: _pane.addActionForKeyStroke(DrJava.getConfig().getSetting(
524: OptionConstants.KEY_NEXT_WORD), nextWordAction);
525: DrJava.getConfig().addOptionListener(
526: OptionConstants.KEY_NEXT_WORD,
527: new OptionListener<KeyStroke>() {
528: public void optionChanged(OptionEvent<KeyStroke> oe) {
529: _pane.addActionForKeyStroke(DrJava.getConfig()
530: .getSetting(
531: OptionConstants.KEY_NEXT_WORD),
532: nextWordAction);
533: }
534: });
535: }
536:
537: /** Sets the commands used to manipulate the console input process. Only runs in the event thread. */
538: private void _setConsoleInputCommands(
539: Runnable inputCompletionCommand,
540: Lambda<String, String> insertTextCommand) {
541: _insertTextCommand = insertTextCommand;
542: _inputCompletionCommand = inputCompletionCommand;
543: }
544:
545: // The fields below were made package private for testing purposes.
546:
547: /** Evaluates the interaction on the current line. */
548: AbstractAction evalAction = new AbstractAction() {
549: public void actionPerformed(ActionEvent e) {
550: if (!_adapter.inCommentBlock()) {
551: Thread command = new Thread("Evaluating Interaction") {
552: public void run() {
553: _model.interpretCurrentInteraction();
554: }
555: };
556: command.start();
557: } else {
558: _model.addNewLine();
559: _model.interactionContinues();
560: }
561: }
562: };
563:
564: /** Recalls the previous command from the history. */
565: AbstractAction historyPrevAction = new AbstractAction() {
566: public void actionPerformed(ActionEvent e) {
567: if (!_busy()) {
568: _doc.acquireWriteLock(); // recall... below acquires WriteLock!
569: try {
570: if (_doc.recallPreviousInteractionInHistory())
571: moveToEnd();
572: if (!_isCursorAfterPrompt())
573: moveToPrompt();
574: } finally {
575: _doc.releaseWriteLock();
576: }
577: }
578: }
579: };
580:
581: /** Recalls the next command from the history. */
582: AbstractAction historyNextAction = new AbstractAction() {
583: public void actionPerformed(ActionEvent e) {
584: if (!_busy()) {
585: _doc.acquireWriteLock();
586: try {
587: if (_doc.recallNextInteractionInHistory()
588: || !_isCursorAfterPrompt())
589: moveToPrompt();
590: } finally {
591: _doc.releaseWriteLock();
592: }
593: }
594: }
595: };
596:
597: /** Added feature for up. If the cursor is on the first line of the current interaction, it goes into the history.
598: * Otherwise, stays within the current interaction
599: */
600: AbstractAction moveUpAction = new AbstractAction() {
601: public void actionPerformed(ActionEvent e) {
602: if (!_busy()) {
603: _doc.acquireWriteLock();
604: try {
605: if (_shouldGoIntoHistory(_doc.getPromptPos(), _pane
606: .getCaretPosition()))
607: historyPrevAction.actionPerformed(e);
608: else {
609: defaultUpAction.actionPerformed(e);
610: if (!_isCursorAfterPrompt())
611: moveToPrompt();
612: }
613: } finally {
614: _doc.releaseWriteLock();
615: }
616: }
617: }
618: };
619:
620: /** Added feature for down. If the cursor is on the last line of the current interaction, it goes into the history.
621: * Otherwise, stays within the current interaction
622: */
623: AbstractAction moveDownAction = new AbstractAction() {
624: public void actionPerformed(ActionEvent e) {
625: if (!_busy()) {
626: _doc.acquireWriteLock();
627: try {
628: if (_shouldGoIntoHistory(_pane.getCaretPosition(),
629: _adapter.getLength())) {
630: historyNextAction.actionPerformed(e);
631: } else {
632: defaultDownAction.actionPerformed(e);
633: }
634: } finally {
635: _doc.releaseWriteLock();
636: }
637: }
638: }
639: };
640:
641: /** Tests whether or not to move into the history
642: * @return true iff there are no "\n" characters between the start and the end
643: */
644: private boolean _shouldGoIntoHistory(int start, int end) {
645: if (_isCursorAfterPrompt() && end >= start) {
646: String text = "";
647: try {
648: text = _adapter.getText(start, end - start);
649: } catch (BadLocationException ble) {
650: throw new UnexpectedException(ble); //The conditional should prevent this from ever happening
651: }
652: if (text.indexOf("\n") != -1)
653: return false;
654: }
655: return true;
656: }
657:
658: private boolean _isCursorAfterPrompt() {
659: return _pane.getCaretPosition() >= _doc.getPromptPos();
660: }
661:
662: Action defaultUpAction;
663: Action defaultDownAction;
664:
665: /** Reverse searches in the history. */
666: AbstractAction historyReverseSearchAction = new AbstractAction() {
667: public void actionPerformed(ActionEvent e) {
668: if (!_busy()) {
669: // _doc.acquireReadLock(); // may be overkill
670: // try {
671: _doc.reverseSearchInteractionsInHistory();
672: moveToEnd();
673: // }
674: // finally { _doc.releaseReadLock(); }
675: }
676: }
677: };
678:
679: /** Forward searches in the history. */
680: AbstractAction historyForwardSearchAction = new AbstractAction() {
681: public void actionPerformed(ActionEvent e) {
682: if (!_busy()) {
683: // _doc.acquireReadLock(); // may be overkill
684: // try {
685: _doc.forwardSearchInteractionsInHistory();
686: moveToEnd();
687: // }
688: // finally { _doc.releaseReadLock(); }
689: }
690: }
691: };
692:
693: /** Moves the caret left or wraps around. */
694: AbstractAction moveLeftAction = new AbstractAction() {
695: public void actionPerformed(ActionEvent e) {
696: if (!_busy()) {
697: _doc.acquireReadLock();
698: try {
699: int promptPos = _doc.getPromptPos();
700: int pos = _pane.getCaretPosition();
701: if (pos < promptPos)
702: moveToPrompt();
703: else if (pos == promptPos)
704: moveToEnd(); // Wrap around to the end
705: else {
706: _pane.setCaretPosition(pos - 1); // pos > promptPos
707: setCachedCaretPos(pos - 1);
708: }
709: } finally {
710: _doc.releaseReadLock();
711: }
712: }
713: }
714: };
715:
716: /** Moves the caret right or wraps around. */
717: AbstractAction moveRightAction = new AbstractAction() {
718: public void actionPerformed(ActionEvent e) {
719: _doc.acquireReadLock();
720: try {
721: int pos = _pane.getCaretPosition();
722: if (pos < _doc.getPromptPos())
723: moveToEnd();
724: else if (pos >= _doc.getLength())
725: moveToPrompt(); // Wrap around to the star
726: else {
727: _pane.setCaretPosition(pos + 1); // position between prompt and end
728: setCachedCaretPos(pos + 1);
729: }
730: } finally {
731: _doc.releaseReadLock();
732: }
733: }
734: };
735:
736: /** Skips back one word. Doesn't move past the prompt. */
737: AbstractAction prevWordAction = new AbstractAction() {
738: public void actionPerformed(ActionEvent e) {
739: _doc.acquireReadLock();
740: try {
741: int position = _pane.getCaretPosition();
742: int promptPos = _doc.getPromptPos();
743: if (position < promptPos)
744: moveToPrompt();
745: else if (position == promptPos)
746: moveToEnd(); // Wrap around to the end
747: else
748: _pane.getActionMap().get(
749: DefaultEditorKit.previousWordAction)
750: .actionPerformed(e);
751: } finally {
752: _doc.releaseReadLock();
753: }
754: }
755: };
756:
757: /** Skips forward one word. Doesn't move past the prompt. */
758: AbstractAction nextWordAction = new AbstractAction() {
759: public void actionPerformed(ActionEvent e) {
760: _doc.acquireReadLock();
761: try {
762: int position = _pane.getCaretPosition();
763: int promptPos = _doc.getPromptPos();
764: if (position < promptPos)
765: moveToEnd();
766: else if (position >= _doc.getLength())
767: moveToPrompt(); // Wrap around to the start
768: else
769: _pane.getActionMap().get(
770: DefaultEditorKit.nextWordAction)
771: .actionPerformed(e);
772: } finally {
773: _doc.releaseReadLock();
774: }
775: }
776: };
777:
778: /** A box that can be inserted into the interactions pane for separate input. Do not confuse with
779: * edu.rice.cs.util.swing.InputBox. */
780: private static class InputBox extends JTextArea {
781: private static final int BORDER_WIDTH = 1;
782: private static final int INNER_BUFFER_WIDTH = 3;
783: private static final int OUTER_BUFFER_WIDTH = 2;
784: private volatile Color _bgColor = DrJava.getConfig()
785: .getSetting(
786: OptionConstants.DEFINITIONS_BACKGROUND_COLOR);
787: private volatile Color _fgColor = DrJava.getConfig()
788: .getSetting(OptionConstants.DEFINITIONS_NORMAL_COLOR);
789: private volatile Color _sysInColor = DrJava.getConfig()
790: .getSetting(OptionConstants.SYSTEM_IN_COLOR);
791: private volatile boolean _antiAliasText = DrJava.getConfig()
792: .getSetting(OptionConstants.TEXT_ANTIALIAS);
793:
794: public InputBox() {
795: setForeground(_sysInColor);
796: setBackground(_bgColor);
797: setCaretColor(_fgColor);
798: setBorder(_createBorder());
799: setLineWrap(true);
800:
801: DrJava.getConfig().addOptionListener(
802: OptionConstants.DEFINITIONS_NORMAL_COLOR,
803: new OptionListener<Color>() {
804: public void optionChanged(OptionEvent<Color> oe) {
805: _fgColor = oe.value;
806: setBorder(_createBorder());
807: setCaretColor(oe.value);
808: }
809: });
810: DrJava.getConfig().addOptionListener(
811: OptionConstants.DEFINITIONS_BACKGROUND_COLOR,
812: new OptionListener<Color>() {
813: public void optionChanged(OptionEvent<Color> oe) {
814: _bgColor = oe.value;
815: setBorder(_createBorder());
816: setBackground(oe.value);
817: }
818: });
819: DrJava.getConfig().addOptionListener(
820: OptionConstants.SYSTEM_IN_COLOR,
821: new OptionListener<Color>() {
822: public void optionChanged(OptionEvent<Color> oe) {
823: _sysInColor = oe.value;
824: setForeground(oe.value);
825: }
826: });
827: DrJava.getConfig().addOptionListener(
828: OptionConstants.TEXT_ANTIALIAS,
829: new OptionListener<Boolean>() {
830: public void optionChanged(
831: OptionEvent<Boolean> oce) {
832: _antiAliasText = oce.value.booleanValue();
833: InputBox.this .repaint();
834: }
835: });
836:
837: // Add the input listener for <Shift+Enter>
838: final Action newLineAction = new AbstractAction() {
839: public void actionPerformed(ActionEvent e) {
840: insert("\n", getCaretPosition());
841: }
842: };
843:
844: final InputMap im = getInputMap(WHEN_FOCUSED);
845: im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
846: java.awt.Event.SHIFT_MASK), INSERT_NEWLINE_NAME);
847:
848: final ActionMap am = getActionMap();
849: am.put(INSERT_NEWLINE_NAME, newLineAction);
850:
851: }
852:
853: private Border _createBorder() {
854: Border outerouter = BorderFactory.createLineBorder(
855: _bgColor, OUTER_BUFFER_WIDTH);
856: Border outer = BorderFactory.createLineBorder(_fgColor,
857: BORDER_WIDTH);
858: Border inner = BorderFactory.createLineBorder(_bgColor,
859: INNER_BUFFER_WIDTH);
860: Border temp = BorderFactory.createCompoundBorder(outer,
861: inner);
862: return BorderFactory.createCompoundBorder(outerouter, temp);
863: }
864:
865: /** Enable anti-aliased text by overriding paintComponent. */
866: protected void paintComponent(Graphics g) {
867: if (_antiAliasText && g instanceof Graphics2D) {
868: Graphics2D g2d = (Graphics2D) g;
869: g2d.setRenderingHint(
870: RenderingHints.KEY_TEXT_ANTIALIASING,
871: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
872: }
873: super .paintComponent(g);
874: }
875:
876: /** Specifies what to do when the <Enter> key is hit. */
877: void setInputCompletionCommand(final Runnable command) {
878: final InputMap im = getInputMap(WHEN_FOCUSED);
879: im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
880: INPUT_ENTERED_NAME);
881:
882: final ActionMap am = getActionMap();
883: am.put(INPUT_ENTERED_NAME, new AbstractAction() {
884: public void actionPerformed(ActionEvent e) {
885: command.run();
886: }
887: });
888: }
889:
890: /** Generates a lambda that can be used to insert text into this input box. Only runs in event thread.
891: * @return A lambda that inserts the given text into the textbox when applied
892: */
893: Lambda<String, String> makeInsertTextCommand() {
894: return new Lambda<String, String>() {
895: public String apply(String input) {
896: insert(input, getCaretPosition());
897: return input;
898: }
899: };
900: }
901:
902: /** Behaves somewhat like setEnable(false) in that it disables all
903: * input to the text box, but it does not change the appearance of the text.
904: */
905: void disableInputs() {
906: setEditable(false);
907:
908: ActionMap am = getActionMap();
909: Action action;
910:
911: action = am.get(INPUT_ENTERED_NAME);
912: if (action != null)
913: action.setEnabled(false);
914:
915: action = am.get(INSERT_NEWLINE_NAME);
916: if (action != null)
917: action.setEnabled(false);
918:
919: getCaret().setVisible(false);
920: }
921: }
922:
923: /** A listener interface that allows for others outside the interactions controller to be notified when the input
924: * console is enabled in the interactions pane.
925: */
926: public interface ConsoleStateListener extends EventListener {
927:
928: /** Called when the input console is started in the interactions pane. <p>
929: * This method is called from the thread that initiated the console input,
930: */
931: public void consoleInputStarted(InteractionsController c);
932:
933: /** Called when the console input is complete. <p>
934: * This method is called from the thread that initiated the console input.
935: * @param result The text that was inputted to the console
936: */
937: public void consoleInputCompleted(String result,
938: InteractionsController c);
939:
940: }
941: }
|