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}