001: /*
002: * <copyright>
003: *
004: * Copyright 2000-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.tools.csmart.ui.console;
028:
029: import org.cougaar.tools.csmart.ui.viewer.CSMART;
030: import org.cougaar.util.log.Logger;
031:
032: import javax.swing.*;
033: import javax.swing.event.CaretListener;
034: import javax.swing.event.DocumentEvent;
035: import javax.swing.event.DocumentListener;
036: import javax.swing.text.BadLocationException;
037: import javax.swing.text.DefaultHighlighter;
038: import javax.swing.text.Document;
039: import javax.swing.text.Highlighter;
040: import javax.swing.text.Position;
041: import java.awt.*;
042:
043: /**
044: * A text pane that contains a ConsoleStyledDocument and supports
045: * searching and highlighting that document.
046: */
047: public class ConsoleTextPane extends JTextPane {
048: ConsoleStyledDocument doc;
049: String searchString;
050: Position searchPosition;
051: String notifyCondition;
052: Position notifyPosition;
053: Highlighter highlighter;
054: DefaultHighlighter.DefaultHighlightPainter searchHighlight;
055: DefaultHighlighter.DefaultHighlightPainter notifyHighlight;
056: Object searchHighlightReference;
057: Object notifyHighlightReference;
058: NodeStatusButton statusButton;
059: int notifyCount;
060: MyDocumentListener docListener = null;
061:
062: private transient Logger log;
063:
064: public ConsoleTextPane(ConsoleStyledDocument doc,
065: NodeStatusButton statusButton) {
066: super (doc);
067: createLogger();
068: this .doc = doc;
069: this .statusButton = statusButton;
070: highlighter = getHighlighter();
071: searchHighlight = new DefaultHighlighter.DefaultHighlightPainter(
072: Color.yellow);
073: notifyHighlight = new DefaultHighlighter.DefaultHighlightPainter(
074: Color.magenta);
075: }
076:
077: private void createLogger() {
078: log = CSMART.createLogger(this .getClass().getName());
079: }
080:
081: private void highlightSearchString(int startOffset, int endOffset) {
082: if (searchHighlightReference != null)
083: highlighter.removeHighlight(searchHighlightReference);
084: try {
085: searchHighlightReference = highlighter.addHighlight(
086: startOffset, endOffset, searchHighlight);
087: } catch (BadLocationException ble) {
088: if (log.isErrorEnabled()) {
089: log.error("Bad location exception: "
090: + ble.offsetRequested(), ble);
091: }
092: }
093: }
094:
095: private void highlightNotifyString(int startOffset, int endOffset) {
096: if (notifyHighlightReference != null)
097: highlighter.removeHighlight(notifyHighlightReference);
098: try {
099: notifyHighlightReference = highlighter.addHighlight(
100: startOffset, endOffset, notifyHighlight);
101: } catch (BadLocationException ble) {
102: if (log.isErrorEnabled()) {
103: log.error("Bad location exception: "
104: + ble.offsetRequested(), ble);
105: }
106: }
107: }
108:
109: private void displayHighlightedText(int startOffset, int endOffset) {
110: try {
111: setCaretPosition(endOffset);
112: Rectangle r = modelToView(startOffset);
113: scrollRectToVisible(r);
114: } catch (BadLocationException ble) {
115: if (log.isErrorEnabled()) {
116: log.error("Bad location exception: "
117: + ble.offsetRequested(), ble);
118: }
119: }
120: }
121:
122: /**
123: * Search for a string starting at a given position.
124: * If the string is found, set the position at the end of the
125: * string so it can be used as the start of the next search.
126: * If the string is not found, return false.
127: * @param s string to search for
128: * @param startPosition position in document to start search
129: * @param search true for "search"; false for "notify"
130: * @return boolean whether or not string was found
131: * TODO: if the last text in the buffer is highlighted, then
132: * the highlighting is automatically applied to any new text added,
133: * how to avoid this?
134: */
135: private boolean worker(String s, Position startPosition,
136: boolean search) {
137: s = s.toLowerCase();
138: int startOffset = startPosition.getOffset();
139: try {
140: String content = getText(startOffset, doc.getEndPosition()
141: .getOffset()
142: - startOffset);
143: content = content.toLowerCase();
144: int index = content.indexOf(s);
145: if (index == -1)
146: return false;
147: startOffset = startOffset + index;
148: int endOffset = startOffset + s.length();
149: if (search) {
150: highlightSearchString(startOffset, endOffset);
151: searchPosition = doc.createPosition(endOffset);
152: } else {
153: highlightNotifyString(startOffset, endOffset);
154: notifyPosition = doc.createPosition(endOffset);
155: }
156: displayHighlightedText(startOffset, endOffset);
157: } catch (BadLocationException ble) {
158: if (log.isErrorEnabled()) {
159: log.error("Bad location exception: "
160: + ble.offsetRequested(), ble);
161: }
162: return false;
163: }
164: return true;
165: }
166:
167: /**
168: * Specify a "notify" string. If this string
169: * is detected in the output of the node, then the node status
170: * button color is set to blue and remains set until the user resets it;
171: * the first instance of the "notify" string is highlighted.
172: * Search is case insensitive.
173: * @param s string to watch for in node output
174: */
175: public void setNotifyCondition(String s) {
176: clearNotify();
177: if (s == null) {
178: doc.removeDocumentListener(docListener);
179: docListener = null;
180: notifyCondition = s;
181: } else {
182: notifyCondition = s.toLowerCase();
183: if (docListener == null) {
184: docListener = new MyDocumentListener();
185: doc.addDocumentListener(docListener);
186: }
187: }
188: }
189:
190: /**
191: * Return notify condition in use.
192: */
193: public String getNotifyCondition() {
194: return notifyCondition;
195: }
196:
197: /**
198: * Clear the notify highlighting and position. Starts searching for
199: * notify conditions with new text appended after this method is called.
200: * Reset the count.
201: */
202: public void clearNotify() {
203: notifyPosition = null;
204: if (notifyHighlightReference != null)
205: highlighter.removeHighlight(notifyHighlightReference);
206: notifyCount = 0;
207: }
208:
209: /**
210: * Return number of occurrences of notify string received from the
211: * node. This number is reset when a new notify string is specified
212: * or when the node status is reset (via clearNotify).
213: */
214: public int getNotifyCount() {
215: return notifyCount;
216: }
217:
218: /**
219: * Search for and highlight the next instance of the notify string,
220: * starting at the end of the last notify string found (or at the
221: * beginning of the screen buffer, if the previous notify string
222: * was removed from the buffer).
223: * Search is case insensitive.
224: * @return true if notify string is found and false otherwise
225: */
226: public boolean notifyNext() {
227: if (notifyCondition != null && notifyPosition != null)
228: return worker(notifyCondition, notifyPosition, false);
229: return false;
230: }
231:
232: /**
233: * Search for the string starting at the current search position or
234: * at the beginning of the document, and
235: * highlight it if found.
236: * Search is case insensitive.
237: * @param s string to search for
238: * @return true if string found and false otherwise
239: */
240: public boolean search(String s) {
241: searchString = s;
242: if (searchPosition == null)
243: return worker(s, doc.getStartPosition(), true);
244: else
245: return worker(s, searchPosition, true);
246: }
247:
248: public String getSearchString() {
249: return searchString;
250: }
251:
252: /**
253: * Search for the "current" string (i.e. the last string specified in
254: * a call to the search method) starting at the end of the last
255: * search string found (or at the beginning of the screen buffer,
256: * if the previous search string was removed from the buffer).
257: * Search is case insensitive.
258: * @return true if string found and false otherwise
259: */
260: public boolean searchNext() {
261: if (searchString != null)
262: return worker(searchString, searchPosition, true);
263: return false;
264: }
265:
266: // for testing, print keymap with recursion
267: static void printKeymap(javax.swing.text.Keymap m, int indent) {
268: Logger log = CSMART
269: .createLogger("org.cougaar.tools.csmart.ui.console.ConsoleTextPane");
270: javax.swing.KeyStroke[] k = m.getBoundKeyStrokes();
271: for (int i = 0; i < k.length; i++) {
272: for (int j = 0; j < indent; j++)
273: if (log.isDebugEnabled()) {
274: log.debug(" ");
275: log.debug("Keystroke <"
276: + java.awt.event.KeyEvent
277: .getKeyModifiersText(k[i]
278: .getModifiers())
279: + " "
280: + java.awt.event.KeyEvent.getKeyText(k[i]
281: .getKeyCode()) + "> ");
282: javax.swing.Action a = m.getAction(k[i]);
283: log.debug((String) a
284: .getValue(javax.swing.Action.NAME));
285: }
286: }
287: m = m.getResolveParent();
288: if (m != null)
289: printKeymap(m, indent + 2);
290: }
291:
292: public static void main(String[] args) {
293: ConsoleStyledDocument doc = new ConsoleStyledDocument();
294: javax.swing.text.AttributeSet a = new javax.swing.text.SimpleAttributeSet();
295: ConsoleTextPane pane = new ConsoleTextPane(doc,
296: new NodeStatusButton(null));
297: doc.setBufferSize(20);
298: doc.appendString("abcdefghijklmnopqrstuvw", a);
299: // for debugging, print the keymap for this text component
300: // printKeymap(pane.getKeymap(), 0);
301: pane.setNotifyCondition("now is the time for all good men");
302: doc.appendString("now is the time for all good men", a);
303: JScrollPane scrollPane = new JScrollPane(pane);
304: javax.swing.JDesktopPane desktop = new javax.swing.JDesktopPane();
305: javax.swing.JInternalFrame internalFrame = new javax.swing.JInternalFrame(
306: "", true, false, true, true);
307: internalFrame.getContentPane().add(scrollPane);
308: internalFrame.setSize(100, 100);
309: internalFrame.setLocation(10, 10);
310: internalFrame.setVisible(true);
311: desktop.add(internalFrame,
312: javax.swing.JLayeredPane.DEFAULT_LAYER);
313: javax.swing.JFrame frame = new javax.swing.JFrame();
314: frame.getContentPane().add(desktop);
315: frame.pack();
316: frame.setSize(200, 200);
317: frame.setVisible(true);
318: }
319:
320: /**
321: * Listen for text added to the displayed document, and highlight
322: * any text matching the notify condition.
323: */
324: class MyDocumentListener implements DocumentListener {
325:
326: public void insertUpdate(DocumentEvent e) {
327: Document doc = e.getDocument();
328: try {
329: String newContent = doc.getText(e.getOffset(), e
330: .getLength());
331: newContent = newContent.toLowerCase();
332: int index = newContent.indexOf(notifyCondition);
333: if (index != -1) {
334: notifyCount++;
335: // if there's already a highlighted notify condition,
336: // then don't highlight a new one
337: if (notifyPosition != null)
338: return;
339: int startOffset = doc.getEndPosition().getOffset()
340: - newContent.length() + index - 1;
341: int endOffset = startOffset
342: + notifyCondition.length();
343: highlightNotifyString(startOffset, endOffset);
344: notifyPosition = doc.createPosition(endOffset);
345: statusButton.getMyModel().setStatus(
346: NodeStatusButton.STATUS_NOTIFY);
347: }
348: } catch (BadLocationException ble) {
349: if (log.isErrorEnabled()) {
350: log.error("DocumentListener got Exception", ble);
351: }
352: }
353: }
354:
355: public void changedUpdate(DocumentEvent e) {
356: }
357:
358: public void removeUpdate(DocumentEvent e) {
359: }
360:
361: }
362:
363: /**
364: * When you're done with this component, call this to free
365: * up resources. This recurses down to the document itself.
366: **/
367: public void cleanUp() {
368: if (log.isDebugEnabled())
369: log.debug("TextPane.cleanUp");
370: clearNotify();
371: searchHighlight = null;
372: notifyHighlight = null;
373: if (doc != null) {
374: // if (log.isDebugEnabled())
375: // log.debug(".. had a doc");
376: if (docListener != null) {
377: // if (log.isDebugEnabled())
378: // log.debug(".. had a doc listener to remove");
379: doc.removeDocumentListener(docListener);
380: docListener = null;
381: }
382: // Recurse to document? Only if not re-using...
383: doc.cleanUp();
384: doc = null;
385: }
386: statusButton = null;
387: CaretListener[] lists = getCaretListeners();
388: for (int i = 0; i < lists.length; i++)
389: removeCaretListener(lists[i]);
390: removeNotify();
391: removeAll();
392:
393: }
394:
395: }
|