001: /*=============================================================================
002: * Copyright 1996-2000 Texas Instruments Inc. All rights reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.swing;
020:
021: import ti.swing.console.*;
022: import ti.exceptions.ProgrammingErrorException;
023:
024: import java.awt.*;
025: import javax.swing.*;
026: import java.awt.event.*;
027: import java.awt.datatransfer.*;
028: import java.io.*;
029:
030: /**
031: * ConsoleTextArea is like a regular text area, except that you can
032: * <code>getReader()</code>/<code>getWriter()</code> to get a Reader/Writer
033: * that can be used for input/output with a non-GUI program. Because of this,
034: * ConsoleTextArea can be used as a console for non-GUI programs.
035: *
036: * @author Rob Clark
037: * @version 0.4
038: */
039: public class ConsoleTextArea extends Console implements Serializable {
040: /**
041: * Handling keyboard input, and providing a Reader interface is done by
042: * the key-listener.
043: */
044: private ConsoleKeyListener keyListener = null;
045:
046: /*=======================================================================*/
047: /**
048: * Class Constructor
049: */
050: public ConsoleTextArea() {
051: this (25, 80);
052: }
053:
054: /*=======================================================================*/
055: /**
056: * Class Constructor. Create a standard read-write console.
057: *
058: * @param rows the number of rows for the console
059: * @param columns the number of columns for the console
060: */
061: public ConsoleTextArea(int rows, int columns) {
062: // for now we ignore the read-only flag, and create a normal
063: // "read-write" console window:
064: this (rows, columns, false);
065: }
066:
067: private MouseSelectionHandler mouseSelectionHandler;
068:
069: /*=======================================================================*/
070: /**
071: * Class Constructor. Create a console window. If <code>ro</code> is
072: * true, then the console is "read-only", meaning that the user cannot
073: * type input into the console.
074: *<p>
075: * You can still get a reader for a "read-only" console, but because the
076: * user cannot enter any input there is probably no point in doing so.
077: *
078: * @param rows the number of rows for the console
079: * @param columns the number of columns for the console
080: * @param readonly true if this should be a read-only console
081: */
082: public ConsoleTextArea(int rows, int columns, boolean readonly) {
083: super (rows, columns);
084:
085: if (!readonly) {
086: // the key-listener is setup before we tack on any additional
087: // input-handlers so that it doesn't have to know to perform
088: // any translations... ie. "<" -> "<"
089: keyListener = new ConsoleKeyListener(this );
090: addKeyListener(keyListener);
091: }
092:
093: mouseSelectionHandler = new MouseSelectionHandler();
094: addMouseListener(mouseSelectionHandler);
095: addMouseMotionListener(mouseSelectionHandler);
096:
097: // create an input handler to do misc nice things, like handle
098: // '\t', '\r', etc.
099: setInputHandler(new DefaultInputAdapter(getInputHandler()));
100: }
101:
102: /**
103: * InputHandler that deals with \r, \t, etc
104: */
105: private static class DefaultInputAdapter extends InputAdapter {
106: /**
107: * don't use this constructor... just here to make things serializable
108: */
109: public DefaultInputAdapter() {
110: super (null);
111: }
112:
113: DefaultInputAdapter(InputHandler ih) {
114: super (ih);
115: }
116:
117: private char[] TAB = new char[] { ' ', ' ', ' ', ' ', ' ' };
118:
119: public void append(char[] cbuf, int off, int len) {
120: lock();
121:
122: int startIdx = 0;
123:
124: for (int i = 0; i < len; i++) {
125: switch (cbuf[i + off]) {
126: case '\t':
127: super .append(cbuf, off + startIdx, i - startIdx);
128: super .append(TAB, 0, TAB.length);
129: startIdx = i + 1;
130: break;
131: case '\r':
132: super .append(cbuf, off + startIdx, i - startIdx);
133: startIdx = i + 1;
134: break;
135: }
136: }
137:
138: super .append(cbuf, off + startIdx, len - startIdx);
139:
140: unlock();
141: }
142: }
143:
144: /**
145: * Paint this component. We overload this so we have a chance to draw
146: * a cursor over things.
147: */
148: protected void paintComponent(Graphics g) {
149: super .paintComponent(g);
150: if (keyListener != null)
151: keyListener.paintCursor(g);
152: }
153:
154: /**
155: * Overload so that non-read-only consoles don't let TAB change focus.
156: */
157: public boolean isManagingFocus() {
158: return true;
159: }
160:
161: /*=======================================================================*/
162: /**
163: * Scroll to a specified text coordinate. This method figures out the
164: * minimum amount of scrolling needed to make a particular text coordinate
165: * visible, and scrolls there, if it is not already visible.
166: *
167: * @param p the position to scroll to
168: */
169: protected void scrollTo(Point p) {
170: throw new ProgrammingErrorException("unimplemented");
171: }
172:
173: /*=======================================================================*/
174: /*============= History stuff: ==========================================*/
175: /*=======================================================================*/
176:
177: /**
178: * Set the history. The history object must be an object that was previously
179: * returned by {@link #getHistory}, but may be serialized/deserialized.
180: *
181: * @param history a history object, as returned by {@link #getHistory}
182: * @see #getHistory
183: */
184: public void setHistory(Object history) {
185: if (keyListener != null)
186: keyListener.setHistory(history);
187: }
188:
189: /**
190: * Get the history. The history is externally treated as opaque, but the
191: * get/set methods allow the creator of the {@link Console} to make history
192: * persistant. The history object is {@link java.io.Serializable}
193: *
194: * @return a opaque history object, which is serializable
195: * @see #setHistory
196: */
197: public Object getHistory() {
198: if (keyListener != null)
199: return keyListener.getHistory();
200: return null;
201: }
202:
203: /*=======================================================================*/
204: /*============= Tab completion stuff: ===================================*/
205: /*=======================================================================*/
206:
207: /**
208: * Set the {@link ConsoleTabCompleter}
209: */
210: public void setTabCompleter(ConsoleTabCompleter ctc) {
211: if (keyListener != null)
212: keyListener.setTabCompleter(ctc);
213: }
214:
215: /*=======================================================================*/
216: /*============= Copy, Paste, and Select stuff: ==========================*/
217: /*=======================================================================*/
218:
219: /*=======================================================================*/
220: /**
221: * Select the entire buffer.
222: */
223: public void selectAll() {
224: throw new ProgrammingErrorException("unimplemented");
225: }
226:
227: /*=======================================================================*/
228: /**
229: * Copy the selected text into the system-wide clipboard.
230: */
231: public void copy() {
232: copy((getToolkit()).getSystemClipboard());
233: }
234:
235: /*=======================================================================*/
236: /**
237: * Copy the selected text into the provided clipboard. If no text is
238: * selected, this is a no-op.
239: *
240: * @param clipboard the clipboard to copy into
241: */
242: public void copy(Clipboard clipboard) {
243: int startOffset = mouseSelectionHandler
244: .getSelectedStartOffset();
245: int endOffset = mouseSelectionHandler.getSelectedEndOffset();
246:
247: String str = new String(getInputHandler().getData(startOffset,
248: endOffset - startOffset));
249: clipboard.setContents(new StringSelection(str), null);
250: }
251:
252: /*=======================================================================*/
253: /**
254: * Paste the text from the system-wide clipboard into the text area at
255: * the specified position. If the data in the clipboard is not text,
256: * then this is a no-op.
257: */
258: public void paste() {
259: paste((getToolkit()).getSystemClipboard());
260: }
261:
262: /*=======================================================================*/
263: /**
264: * Paste the text from the specified clipboard into the text area at
265: * the specified position. If the data in the clipboard is not text,
266: * then this is a no-op.
267: *
268: * @param clipboard the clipboard to copy from
269: */
270: public void paste(Clipboard clipboard) {
271: if (keyListener != null) // can only paste if not read-only!
272: {
273: Transferable contents = clipboard.getContents(this );
274: String str;
275:
276: try {
277: str = (String) (contents
278: .getTransferData(DataFlavor.stringFlavor));
279: } catch (Exception e) {
280: str = contents.toString();
281: }
282:
283: paste(str);
284: }
285: }
286:
287: /*=======================================================================*/
288: /**
289: * Paste the text into the text area.
290: *
291: * @param str the string to paste.
292: */
293: public void paste(String str) {
294: if (keyListener != null) // can only paste if not read-only!
295: {
296: char data[] = str.toCharArray();
297:
298: getInputHandler().lock();
299:
300: for (int i = 0; i < data.length; i++)
301: keyListener.insertChar(data[i]);
302:
303: getInputHandler().unlock();
304: }
305: }
306:
307: /*=======================================================================*/
308: /**
309: * This should called when getting rid of this text-area... this will
310: * cause any blocking readers to return.
311: */
312: public void dispose() {
313: if (keyListener != null)
314: keyListener.dispose();
315:
316: }
317:
318: /*=======================================================================*/
319: /**
320: * Get the Writer for this console. By using this writer, an
321: * application can use this console for output.
322: *
323: * @return a writer that can be used to write into the console
324: */
325: public Writer getWriter() {
326: return new ConsoleWriter();
327: }
328:
329: /**
330: * A ConsoleWriter is a Writer that sends its output to it's ConsoleTextArea
331: * outer class.
332: */
333: private class ConsoleWriter extends Writer {
334: private static final int BUFSIZE = 40;
335:
336: private char buf[];
337: private int cursor;
338:
339: /**
340: * Class Constructor
341: */
342: public ConsoleWriter() {
343: super ();
344: initBuf();
345: }
346:
347: /**
348: * Initialize the buffer
349: */
350: private void initBuf() {
351: buf = new char[BUFSIZE];
352: cursor = 0;
353: }
354:
355: /**
356: * Write a portion of an array of characters.
357: *
358: * @param cbuf Array of characters
359: * @param off Offset from which to start writing characters
360: * @param len Number of characters to write
361: * @exception IOException If an I/O error occurs
362: */
363: public void write(char[] cbuf, int off, int len)
364: throws IOException {
365: synchronized (this ) {
366: if (len + cursor >= buf.length) {
367: int newsize = buf.length;
368: while (len + cursor >= newsize)
369: newsize += BUFSIZE;
370:
371: char newbuf[] = new char[newsize];
372: for (int i = 0; i < cursor; i++)
373: newbuf[i] = buf[i];
374:
375: buf = newbuf;
376: }
377:
378: for (int i = off; i < (off + len); i++) {
379: buf[cursor] = cbuf[i];
380: cursor++;
381: }
382: }
383:
384: // if( autoflush )
385: // flush();
386: }
387:
388: /**
389: * Flush the stream. If the stream has saved any characters from the various
390: * write() methods in a buffer, write them immediately to their intended
391: * destination. Then, if that destination is another character or byte stream,
392: * flush it. Thus one flush() invocation will flush all the buffers in a chain
393: * of Writers and OutputStreams.
394: *
395: * @exception IOException - If an I/O error occurs
396: */
397: public void flush() throws IOException {
398: char[] buf;
399: int cursor;
400:
401: synchronized (this ) {
402: buf = this .buf;
403: cursor = this .cursor;
404: initBuf();
405: }
406:
407: ConsoleTextArea.this .write(buf, 0, cursor);
408: }
409:
410: /**
411: * You can't close this!
412: *
413: * @exception IOException - If an I/O error occurs
414: */
415: public void close() throws IOException {
416: flush();
417: getInputHandler().close();
418: }
419: }
420:
421: private final void write(char[] cbuf, int off, int len) {
422: InputHandler ih = getInputHandler();
423:
424: ih.append(cbuf, off, len);
425: waitForRedraw();
426: }
427:
428: /*=======================================================================*/
429: /**
430: * Get the Reader for this console. By using this reader, an application
431: * can use this console for input.
432: *
433: * @return a reader that can be used to read from the console
434: */
435: public Reader getReader() {
436: if (keyListener != null)
437: return keyListener.getReader();
438: else
439: throw new ProgrammingErrorException("read-only console");
440: }
441:
442: /*=======================================================================*/
443: /**
444: * The mouse-handler keeps track of the selected region of the document,
445: * and applies an INVERSE attribute over that region.
446: */
447: private class MouseSelectionHandler implements MouseMotionListener,
448: MouseListener, Serializable {
449: private int startOffset = -1;
450: private int endOffset = -1;
451:
452: public int getSelectedStartOffset() {
453: return Math.min(startOffset, endOffset);
454: }
455:
456: public int getSelectedEndOffset() {
457: return Math.max(startOffset, endOffset);
458: }
459:
460: public void mouseDragged(MouseEvent evt) {
461: endOffset = getEventOffset(evt);
462: updateSelectedRegion();
463: }
464:
465: public void mousePressed(MouseEvent evt) {
466: startOffset = endOffset = getEventOffset(evt);
467: updateSelectedRegion();
468: }
469:
470: public void mouseReleased(MouseEvent evt) {
471: if (startOffset != endOffset)
472: copy();
473: }
474:
475: public void mouseClicked(MouseEvent evt) {
476: if ((evt.getModifiers() & MouseEvent.BUTTON2_MASK) != 0)
477: paste();
478: }
479:
480: public void mouseMoved(MouseEvent evt) {
481: } // no-op
482:
483: public void mouseEntered(MouseEvent evt) {
484: } // no-op
485:
486: public void mouseExited(MouseEvent evt) {
487: } // no-op
488:
489: private final int getEventOffset(MouseEvent evt) {
490: return toOffset(evt.getPoint());
491: }
492:
493: private Region selectedRegion = null;
494:
495: private final void updateSelectedRegion() {
496: if ((startOffset == -1) || (endOffset == -1))
497: return;
498:
499: int actualStartOffset = getSelectedStartOffset();
500: int actualEndOffset = getSelectedEndOffset();
501:
502: InputHandler ih = getInputHandler();
503:
504: ih.lock();
505:
506: if (selectedRegion != null) {
507: ih.removeRegion(selectedRegion);
508: selectedRegion = null;
509: }
510:
511: if (actualStartOffset != actualEndOffset) {
512: selectedRegion = InverseAttribute.INVERSE.getRegion(
513: actualStartOffset, actualEndOffset
514: - actualStartOffset);
515:
516: ih.addRegion(selectedRegion);
517: }
518:
519: ih.unlock();
520: }
521: }
522: }
523:
524: /*
525: * Local Variables:
526: * tab-width: 2
527: * indent-tabs-mode: nil
528: * mode: java
529: * c-indentation-style: java
530: * c-basic-offset: 2
531: * eval: (c-set-offset 'substatement-open '0)
532: * eval: (c-set-offset 'case-label '+)
533: * eval: (c-set-offset 'inclass '+)
534: * eval: (c-set-offset 'inline-open '0)
535: * End:
536: */
|