I am creating a basic terminal chat application in Java using Lanterna. I have a TextBox component that I call addLine() on as new messages come in. The default behavior of a TextBox appears to be to maintain its previous scroll position until the user focuses on it and scrolls manually. I would like for the TextBox itself to scroll to the bottom automatically.
There is no obvious way to set the scroll position of a TextBox programmatically, so my best idea was to extend TextBox and make my own version of addLine():
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.TextBox;
public class ChatWindowTextBox extends TextBox{
public ChatWindowTextBox(TerminalSize preferredSize, Style style){
super(preferredSize, style);
}
public ChatWindowTextBox addLineAndScrollDown(String line){
addLine(line);
setReadOnly(false); // I make the chat window read only
// in my screen setup, so I undo that
takeFocus();
setCaretPosition(Integer.MAX_VALUE, Integer.MAX_VALUE);
setReadOnly(true);
return this;
}
}
Via debugging, I have verified that the arguments to setCaretPosition get correctly clamped to the actual values of the last line and column, and the internal value of caretPosition is updated to those values. However, this does not make the TextBox scroll. Have I misunderstood how setCaretPosition() behaves, and is there a viable way to programmatically make a TextBox scroll to the bottom?
A solution I found is to force the program to pause before making the
ChatWindowTextBoxread-only again:15 ms is about the smallest pause that allows the
ChatWindowTextBoxto scroll as expected. You may want to go for 20 or 30 ms.The primary problem with the original code has to do with making the
ChatWindowTextBoxread-only. The solution proposed in the question actually works perfectly forTextBoxes that are not made read-only.Digging into Lanterna's source code, the caret position of a
TextBoxis not taken into account if it was set to read-only. However, the code in the question would appear to account for this by unsettingreadOnly, changing the caret position, then resettingreadOnly. So why is there unexpected behavior? Internally,readOnly()also callsinvalidate(), which according to Lanterna, "Marks the component as invalid and requiring to be drawn at next opportunity." I assume that theChatWindowTextBoxbeing invalidated and soon redrawn causes the change in caret position to not completely go through before it is made read-only again.