How to determine number of the first visible line and the number of lines currently visible in scrollable JTextArea (JTextArea inside a JScrollPane)?
Interesting question that took me a while but I think I have a quite valid answer. Yet there might be some better ways; feel free to comment to improve the answer.
Stategy:
So, my idea is that we should start from the visible rect. Based on that we can find out what is the first visible vertical offset (getVisibleRect().y
) and the end of the visible vertical offset (getVisibleRect().y+getVisibleRect().height
). Once we have that, by using the height of the font, we can determine which rows are visible.
The second part is to find out what does those rows contain. This is where I use Utilities with getRowStart()
and getRowEnd()
comes into play.
Here is a sample code of what I was detailing (the result are output to the console as you resize the frame or scroll the textarea):
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Utilities;
public class TestTextAre {
private void initUI() {
JFrame frame = new JFrame(TestTextAre.class.getSimpleName());
final JTextArea ta = new JTextArea(5, 25);
ta.setText("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has "
+ "been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of "
+ "type and scrambled it to make a type specimen book.\n It has survived not only five centuries, but also the "
+ "leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the"
+ " release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing "
+ "software like Aldus PageMaker including versions of Lorem Ipsum.");
ta.setColumns(20);
ta.setEditable(false);
ta.setLineWrap(true);
ta.setWrapStyleWord(true);
JScrollPane scrollpane = new JScrollPane(ta);
scrollpane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
printTAVisibleInfo(ta);
}
});
frame.add(scrollpane);
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
printTAVisibleInfo(ta);
}
});
frame.pack();
frame.setVisible(true);
}
private void printTAVisibleInfo(final JTextArea ta) {
List<Row> taInfo = getTAInfo(ta);
int y1 = ta.getVisibleRect().y;
int y2 = y1 + ta.getVisibleRect().height;
int lineHeight = ta.getFontMetrics(ta.getFont()).getHeight();
int startRow = (int) Math.ceil((double) y1 / lineHeight);
int endRow = (int) Math.floor((double) y2 / lineHeight);
endRow = Math.min(endRow, taInfo.size());
System.err.println(startRow + " " + endRow);
for (int i = startRow; i < endRow; i++) {
System.err.println(taInfo.get(i) + " is visible");
}
}
private List<Row> getTAInfo(final JTextArea ta) {
List<Row> taInfo = new ArrayList<TestTextAre.Row>();
int start = 0;
int end = -1;
int i = 0;
try {
do {
start = Utilities.getRowStart(ta, end + 1);
end = Utilities.getRowEnd(ta, start);
taInfo.add(new Row(i, start, end, ta.getDocument().getText(start, end - start)));
i++;
} while (end < ta.getDocument().getLength());
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return taInfo;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new TestTextAre().initUI();
}
});
}
public static class Row {
private final int row;
private final int start;
private final int end;
private final String text;
public Row(int row, int start, int end, String text) {
super();
this.row = row;
this.start = start;
this.end = end;
this.text = text;
}
public int getRow() {
return row;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public String getText() {
return text;
}
@Override
public String toString() {
return "Row " + row + " contains " + text + " (" + start + "," + end + ")";
}
}
}
If you also have an horizontal scrollbar, I guess you should be able to compute horizontal offsets with FontMetrics.stringWidth()
.
Okay, this is my take on the problem... (Nice question though)
There is a small consideration you need to have with this solution. It will return partially displayed lines.
public class TestTextArea {
public static void main(String[] args) {
new TestTextArea();
}
public TestTextArea() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestTextAreaPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestTextAreaPane extends JPanel {
private JTextArea textArea;
private JTextArea viewText;
public TestTextAreaPane() {
setLayout(new GridLayout(2, 1));
textArea = new JTextArea(20, 100);
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
textArea.setText(loadText());
viewText = new JTextArea(20, 100);
viewText.setWrapStyleWord(false);
viewText.setLineWrap(false);
viewText.setEditable(false);
JScrollPane scrollPane = new JScrollPane(textArea);
add(scrollPane);
add(viewText);
scrollPane.getViewport().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (textArea.getText().length() > 0) {
JViewport viewport = (JViewport) e.getSource();
Rectangle viewRect = viewport.getViewRect();
Point p = viewRect.getLocation();
int startIndex = textArea.viewToModel(p);
p.x += viewRect.width;
p.y += viewRect.height;
int endIndex = textArea.viewToModel(p);
if (endIndex - startIndex >= 0) {
try {
viewText.setText(textArea.getText(startIndex, (endIndex - startIndex)));
} catch (BadLocationException ex) {
ex.printStackTrace();
viewText.setText(ex.getMessage());
}
}
}
}
});
}
protected String loadText() {
String text = null;
File file = new File("src/testtextarea/TestTextArea.java");
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(file));
StringBuilder sb = new StringBuilder(128);
String read = null;
while ((read = br.readLine()) != null) {
sb.append(read).append("\n");
}
text = sb.toString();
} catch (IOException exp) {
exp.printStackTrace();
} finally {
try {
br.close();
} catch (Exception e) {
}
}
return text;
}
}
}