001/** 002* Please feel free to use any fragment of the code in this file that you need 003* in your own work. As far as I am concerned, it's in the public domain. No 004* permission is necessary or required. Credit is always appreciated if you 005* use a large chunk or base a significant product on one of my examples, 006* but that's not required either. 007* 008* This code is distributed in the hope that it will be useful, 009* but WITHOUT ANY WARRANTY; without even the implied warranty of 010* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 011* 012* --- Elliotte Rusty Harold 013**/ 014package jahuwaldt.swing; 015 016import java.awt.Dimension; 017import java.awt.Font; 018import java.awt.Rectangle; 019import java.awt.geom.Rectangle2D; 020import java.io.OutputStream; 021import static java.util.Objects.isNull; 022import javax.swing.JTextArea; 023import javax.swing.text.BadLocationException; 024 025/** 026 * A JTextArea component that can display text that has been streamed to it 027 * using an OutputStream. 028 * 029 * <p> Modified by: Joseph A. Huwaldt </p> 030 * 031 * @author Elliotte Rusty Harold Date: 1999 032 * @version December 20, 2023 033 * 034 */ 035@SuppressWarnings("serial") 036public class JStreamedTextArea extends JTextArea { 037 038 private final OutputStream theOutput = new TextAreaOutputStream(); 039 private boolean autoscroll = false; // Auto scroll to bottom when new content added if true. 040 041 public JStreamedTextArea() { 042 this("", 12, 40); 043 } 044 045 public JStreamedTextArea(String text) { 046 this(text, 12, 40); 047 } 048 049 public JStreamedTextArea(int rows, int columns) { 050 this("", rows, columns); 051 } 052 053 @SuppressWarnings("OverridableMethodCallInConstructor") 054 public JStreamedTextArea(String text, int rows, int columns) { 055 super(text, rows, columns); 056 this.setEditable(false); 057 this.setFont(new Font("Monospaced", Font.PLAIN, 12)); 058 } 059 060 public OutputStream getOutputStream() { 061 return theOutput; 062 } 063 064 @Override 065 public Dimension getMinimumSize() { 066 return new Dimension(120, 200); 067 } 068 069 @Override 070 public void append(String str) { 071 super.append(str); 072 if (autoscroll) // Automatically scroll to the bottom when text is appended if requested. 073 { 074 scrollToBottom(); 075 } 076 } 077 078 @SuppressWarnings("null") 079 private static Rectangle toRectangle(Rectangle2D b) { 080 if (isNull(b)) 081 return null; 082 if (b instanceof Rectangle) { 083 return (Rectangle) b; 084 } else { 085 return new Rectangle((int) b.getX(), (int) b.getY(), 086 (int) b.getWidth(), (int) b.getHeight()); 087 } 088 } 089 090 /** 091 * Scroll to the bottom in a way that plays nice with Swing updates and that 092 * does not remove the current user's selection. 093 */ 094 private void scrollToBottom() { 095 javax.swing.SwingUtilities.invokeLater(new Runnable() { 096 @Override 097 public void run() { 098 try { 099 int endPosition = getDocument().getLength(); 100 Rectangle bottom = toRectangle(modelToView2D(endPosition)); 101 scrollRectToVisible(bottom); 102 } catch (BadLocationException e) { 103 System.err.println("Could not scroll to " + e); 104 } 105 } 106 }); 107 } 108 109 /** 110 * Automatically move the display to the bottom when new content is appended 111 * if true. Do not automatically scroll to bottom when new content is 112 * appended if false. 113 * 114 * @param autoscroll Set to <code>true</code> to automatically scroll to the 115 * bottom when new content is appended. 116 */ 117 public void setAutoScrollToBottom(boolean autoscroll) { 118 this.autoscroll = autoscroll; 119 } 120 121 private class TextAreaOutputStream extends OutputStream { 122 123 @Override 124 public void write(int b) { 125 126 // recall that the int should really just be a byte 127 b &= 0x000000FF; 128 129 // must convert byte to a char in order to append it 130 char c = (char) b; 131 append(String.valueOf(c)); 132 133 } 134 135 @Override 136 public void write(byte[] b, int offset, int length) { 137 138 append(new String(b, offset, length)); 139 140 } 141 } 142}