001: /*=============================================================================
002: * Copyright Texas Instruments, Inc., 2002. 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.console;
020:
021: import ti.exceptions.ProgrammingErrorException;
022:
023: import javax.swing.*;
024: import java.awt.*;
025: import java.awt.event.*;
026:
027: /**
028: * The console is a swing component used to display a character stream. The
029: * character stream can be modified via the {@link InputHandler}. The console
030: * also supports mapping {@link Region}s over sections of the character stream.
031: * These regions can be used to, for example, apply different attributes over
032: * that section of the character stream as its being rendered (for example,
033: * different fg or bg color, different font, etc.).
034: * <p>
035: * This class is the swing component, it delegates most of the details of
036: * managing the console buffer, regions, and rendering the whole thing to the
037: * {@link ConsoleBuffer} class.
038: *
039: * @author Rob Clark
040: * @version 0.0
041: */
042: public class Console extends JPanel {
043: public static final char LF = '\n';
044: public static final char CR = '\r';
045:
046: /**
047: * The buffer management and rendering is handled by the buffer,
048: * leaving this class to mainly deal with being a swing component... ie.
049: * resizing, scroll bar, etc.
050: */
051: private ConsoleBuffer buffer;
052:
053: /**
054: * There is a single vertical scrollbar.
055: */
056: private ScrollBar scrollBar;
057:
058: // track initial nrows/ncols, and use that for getPreferredSize(), etc.
059: private int initialNRows;
060: private int initialNCols;
061:
062: /**
063: * Create a new console with the specified number of rows and cols. The
064: * number of columns could change as a result of a resize, but the number
065: * of rows is fixed.
066: *
067: * @param nrows the number of rows
068: * @param ncols the initial number of cols
069: */
070: public Console(int nrows, int ncols) {
071: super ();
072:
073: this .initialNRows = nrows;
074: this .initialNCols = ncols;
075:
076: buffer = new ConsoleBuffer(this , nrows, ncols);
077: setInputHandler(buffer);
078:
079: // XXX autoscroll to end should be a user preference:
080: setInputHandler(new InputAdapter(getInputHandler()) {
081: public void append(char[] cbuf, int off, int len) {
082: super .append(cbuf, off, len);
083: SwingUtilities.invokeLater(new Runnable() {
084: public void run() {
085: scrollToEnd();
086: }
087: });
088: }
089: });
090:
091: scrollBar = new ScrollBar();
092: add(scrollBar);
093:
094: setFont(new Font("Monospaced", Font.BOLD, 12));
095:
096: // we do our own double buffering:
097: setDoubleBuffered(false);
098:
099: // let us hear some events! (XXX not sure if these are needed?)
100: enableEvents(AWTEvent.KEY_EVENT_MASK
101: | AWTEvent.MOUSE_EVENT_MASK |
102: // AWTEvent.MOUSE_MOTION_EVENT_MASK |
103: // AWTEvent.COMPONENT_EVENT_MASK |
104: AWTEvent.FOCUS_EVENT_MASK);
105: }
106:
107: /*=======================================================================*/
108:
109: /**
110: * The default input handler, serves as the end of the chain of input
111: * handlers.
112: * <p>
113: * XXX note... the whole InputHandler stuff isn't serializable yet, so
114: * an unserialized Console is probably pretty useless
115: */
116: private transient InputHandler ih;
117:
118: /**
119: * Get the input handler for this console. The input handler is the
120: * mechanism for modifying the state of the console buffer. The input
121: * handler should not be cached!
122: *
123: * @return an input handler. This method will NOT return <code>null</code>
124: */
125: public InputHandler getInputHandler() {
126: return ih;
127: }
128:
129: /**
130: * Set the input handler.
131: *
132: * @param ih set the input handler.
133: */
134: public void setInputHandler(InputHandler ih) {
135: if (ih == null)
136: throw new ProgrammingErrorException(
137: "cannot have null input handler");
138:
139: this .ih = ih;
140: }
141:
142: /**
143: * Convert the specified point to offset. The point is specified in x and
144: * y pixel location relative to this component's origin, and the offset is
145: * an offset into the character stream.
146: *
147: * @param p the point
148: * @return the offset into character stream.
149: */
150: public int toOffset(Point p) {
151: int x = Math.min(Math.max(p.x, 0), getWidth() - 1);
152: int y = Math.min(Math.max(p.y, 0), getHeight() - 1)
153: + scrollBar.getSY();
154:
155: int rh = getRowHeight();
156: int cw = getColumnWidth();
157:
158: if ((rh == 0) || (cw == 0))
159: return -1;
160:
161: int row = y / getRowHeight();
162: int col = x / getColumnWidth();
163:
164: return buffer.toOffset(row, col);
165: }
166:
167: /**
168: * Convert the specified offset to a point. The offset is an offset into
169: * the character stream, and the returned point is an x/y pixel coordinate,
170: * relative to this components origin, of the upper left corner of the
171: * specified offset.
172: *
173: * @param offset the offset int character stream
174: * @return a pixel location
175: */
176: public Point toPoint(int offset) {
177: Point p = buffer.toPoint(offset);
178:
179: return new Point(p.x * getColumnWidth(), (p.y * getRowHeight())
180: - scrollBar.getSY());
181: }
182:
183: /**
184: * This is kinda a hack, but the <code>ConsoleKeyListener</code> needs a way
185: * to figure out if the console is locked... if the console is locked, then it
186: * can't use <code>ih.getOffset()</code> to compute where to draw the cursor,
187: * because the onscreen doesn't get redrawn when the console is locked, which
188: * means that the cursor position wouldn't match what is drawn to screen...
189: */
190: boolean locked() {
191: return buffer.locked();
192: }
193:
194: /*=======================================================================*/
195: private int rowHeight = 0;
196: private int columnWidth = 0;
197:
198: /**
199: * Get the height of a row
200: *
201: * @return the height >= 1
202: */
203: int getRowHeight() {
204: if (rowHeight == 0) {
205: Font font = getFont();
206: FontMetrics metrics = null;
207:
208: if (font != null)
209: metrics = getFontMetrics(font);
210:
211: if (metrics != null)
212: rowHeight = metrics.getHeight();
213: else
214: rowHeight = 1;
215: }
216:
217: return rowHeight;
218: }
219:
220: /**
221: * Gets column width. Since we should be using a fixed size font, this
222: * is ok.
223: *
224: * @return the column width >= 1
225: */
226: int getColumnWidth() {
227: if (columnWidth == 0) {
228: Font font = getFont();
229: FontMetrics metrics = null;
230:
231: if (font != null)
232: metrics = getFontMetrics(font);
233:
234: if (metrics != null)
235: columnWidth = metrics.charWidth('m');
236: else
237: columnWidth = 1;
238: }
239: return columnWidth;
240: }
241:
242: /**
243: * Sets the current font. This will redraw the component.
244: *
245: * @param f the font to use as the current font
246: */
247: public void setFont(Font f) {
248: rowHeight = 0;
249: columnWidth = 0;
250: super .setFont(f);
251: scrollToEnd();
252: }
253:
254: /**
255: * Returns the preferred size Dimension of the Console.
256: *
257: * @return the size
258: */
259: public Dimension getPreferredSize() {
260: return new Dimension(initialNCols * getColumnWidth(),
261: initialNRows * getRowHeight() / 5);
262: }
263:
264: /**
265: * Returns the minimum size Dimension of the Console.
266: *
267: * @return the size
268: */
269: public Dimension getMinimumSize() {
270: return new Dimension(getColumnWidth(), getRowHeight());
271: }
272:
273: /**
274: * After the console is resized, this is called to layout all the
275: * sub-components (ie. the scrollbars)
276: */
277: public void doLayout() {
278: super .doLayout();
279:
280: int cw = getWidth();
281: int ch = getHeight();
282:
283: int sw = scrollBar.getPreferredSize().width;
284:
285: if ((cw < 0) || (ch < 0) || (sw < 0) || (getColumnWidth() <= 0))
286: return;
287:
288: /* w/ MacOSX, calling scrollToEnd() each time seems to cause probs,
289: * cause for some reason repaint() is causing doLayout()... maybe
290: * something odd about their L&F? -- Rob
291: */
292: if (firstLayout) {
293: firstLayout = false;
294: scrollToEnd();
295: }
296:
297: // update the number of columns:
298: buffer.setColumnCount((cw - sw) / getColumnWidth());
299:
300: // layout and update the scrollbar:
301: scrollBar.setSize(sw, ch);
302: scrollBar.setLocation(cw - sw, 0);
303: scrollBar.updateScrollBar();
304: }
305:
306: private boolean firstLayout = true;
307:
308: private void scrollToEnd() {
309: if (scrollBar != null)
310: scrollBar.setSY((buffer.getRowCount() * getRowHeight())
311: - getHeight());
312: }
313:
314: // /**
315: // * This gets called whenever a component event occurs. Component events
316: // * happen when the component is resize, exposed, hidden, etc.
317: // *
318: // * @param e the event
319: // */
320: // protected void processComponentEvent( ComponentEvent e )
321: // {
322: // super.processComponentEvent(e);
323: // switch(e.getID())
324: // {
325: // case ComponentEvent.COMPONENT_RESIZED:
326: // doLayout();
327: // break;
328: // }
329: // }
330:
331: /**
332: * We need to intercept the mouse click, and request focus... probably
333: * there is a better way to get focus?
334: */
335: protected void processMouseEvent(MouseEvent evt) {
336: int id = evt.getID();
337:
338: switch (id) {
339: case MouseEvent.MOUSE_RELEASED:
340: // a button click means we should request focus:
341: if (!hasFocus())
342: requestFocus();
343: break;
344: }
345:
346: super .processMouseEvent(evt);
347: }
348:
349: /**
350: * Paint this component.
351: */
352: protected void paintComponent(Graphics g) {
353: // this should be the place to do a g.translate() and maybe setup a
354: // clip-rect to handle the scrollbar position...
355: g.translate(0, -scrollBar.getSY());
356: buffer.paintBuffer(g);
357: g.translate(0, scrollBar.getSY());
358:
359: // synchronized(buffer)
360: // {
361: // buffer.notifyAll();
362: // }
363: }
364:
365: /**
366: * Block until the console is repainted.
367: */
368: public void waitForRedraw() {
369: // synchronized(buffer)
370: // {
371: // try
372: // {
373: // buffer.wait();
374: // }
375: // catch(InterruptedException e)
376: // {
377: // }
378: // }
379: }
380:
381: /**
382: * The Vertical scrollbar for this console.
383: */
384: private class ScrollBar extends JScrollBar {
385: private int sy;
386:
387: /**
388: * Class Constructor.
389: */
390: ScrollBar() {
391: super (JScrollBar.VERTICAL);
392: enableEvents(AWTEvent.ADJUSTMENT_EVENT_MASK);
393: setUnitIncrement(1);
394:
395: addAdjustmentListener(new AdjustmentListener() {
396: public void adjustmentValueChanged(AdjustmentEvent evt) {
397: switch (evt.getID()) {
398: case AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED:
399: sy = evt.getValue();
400: Console.this .repaint();
401: break;
402: }
403: }
404: });
405: }
406:
407: /**
408: */
409: int getSY() {
410: return sy;
411: }
412:
413: /**
414: */
415: void setSY(int sy) {
416: if (this .sy != sy) {
417: this .sy = sy;
418: updateScrollBar();
419: }
420: }
421:
422: /**
423: * Called when the size of the console changes. This updates
424: * the scrollbar to reflect those changes.
425: */
426: void updateScrollBar() {
427: int rh = getRowHeight();
428: int vh = getHeight(); // XXX do I need to subtrace a border?
429: setValues(sy, vh, 0, rh * buffer.getRowCount());
430: setUnitIncrement(rh);
431: setBlockIncrement(vh);
432: }
433: }
434: }
435:
436: /*
437: * Local Variables:
438: * tab-width: 2
439: * indent-tabs-mode: nil
440: * mode: java
441: * c-indentation-style: java
442: * c-basic-offset: 2
443: * eval: (c-set-offset 'substatement-open '0)
444: * eval: (c-set-offset 'case-label '+)
445: * eval: (c-set-offset 'inclass '+)
446: * eval: (c-set-offset 'inline-open '0)
447: * End:
448: */
|