001package jmri.util.swing;
002
003import javax.annotation.Nonnull;
004import javax.swing.event.DocumentEvent;
005import javax.swing.event.DocumentListener;
006import javax.swing.JTextArea;
007import javax.swing.text.BadLocationException;
008import javax.swing.text.Element;
009import jmri.util.ThreadingUtil;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Create a new TextAreaFIFO, an extended JTextArea
016 * Keeps message log windows to a reasonable length
017 * Scrolls down to last line of textarea by default
018 * Originally based on https://community.oracle.com/thread/1373400
019 * Modified for JMRI by Steve Young (c) 2018
020 * 
021 */
022public class TextAreaFIFO extends JTextArea implements DocumentListener {
023    private int _maxLines;
024    private Boolean _autoScroll;
025    
026    /**
027     * Add text to the console
028     *
029     * @param lines number of lines
030     */
031    public TextAreaFIFO(int lines) {
032        _maxLines = lines;
033        _autoScroll = true;
034        getDocument().addDocumentListener( this );
035    }
036    
037    @Override
038    public void insertUpdate(DocumentEvent e) {
039        ThreadingUtil.runOnGUIEventually( ()->{
040            removeLines();
041        });
042    }
043    @Override
044    public void removeUpdate(DocumentEvent e ) {
045        ThreadingUtil.runOnGUIEventually ( ()->{
046            removeLines();
047        });        
048    }
049    @Override
050    public void changedUpdate(DocumentEvent e) {
051        ThreadingUtil.runOnGUIEventually( ()->{
052            removeLines();
053        });        
054    }
055
056    /**
057     * Set whether the JTextArea should scroll to bottom on update
058     *
059     * @param newval  autoscrolls if true
060     */
061    public void setAutoScroll(@Nonnull Boolean newval) {
062        _autoScroll = newval;
063        if (_autoScroll) {
064            ThreadingUtil.runOnGUIEventually( ()->{
065                removeLines();
066            });
067        }
068    }
069    
070    /**
071     * Edit maximum lines in JTextArea before trimming from top
072     *
073     * @param newval  Number of lines
074     */
075    public void setMaxLines(int newval){
076        _maxLines = newval;
077    }
078    
079
080    /**
081     * Main internal method to trim from top, then if needed, move scroll position
082     *
083     */
084    private void removeLines() {
085        Element root = getDocument().getDefaultRootElement();
086        while (root.getElementCount() > ( _maxLines + 1 ) ) {
087            Element firstLine = root.getElement(0);
088            try {
089                getDocument().remove(0, firstLine.getEndOffset());
090            } catch(BadLocationException ble) {
091                log.error("bad location {}",ble);
092            }
093        }
094        if ( _autoScroll ) {
095             setCaretPosition( getDocument().getLength() );
096        }
097    }
098    
099    /**
100     * Removes document listener
101     *
102     */
103    public void dispose() {
104        getDocument().removeDocumentListener( this );
105    }
106    private final static Logger log = LoggerFactory.getLogger(TextAreaFIFO.class);    
107}