How to determine which lines are visible in scrollable JTextArea?
Asked Answered
C

2

10

How to determine number of the first visible line and the number of lines currently visible in scrollable JTextArea (JTextArea inside a JScrollPane)?

Coagulum answered 31/10, 2012 at 18:48 Comment(0)
C
3

Okay, this is my take on the problem... (Nice question though)

enter image description here

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;
        }
    }
}
Courser answered 1/11, 2012 at 1:15 Comment(1)
It looks like viewToModel() is the way to go. Thanks.Coagulum
L
5

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:

  1. Find which rows are visible using FontMetrics and getVisibleRect()
  2. Find the content of the visible rows.

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().

Logion answered 31/10, 2012 at 22:24 Comment(2)
+1 but it's easier to use viewToModel() method passing the visible rectangle y rather than calculating with font metricsDeadman
@Deadman Yeah you are right. MadProgrammer's solution is more elegant.Logion
C
3

Okay, this is my take on the problem... (Nice question though)

enter image description here

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;
        }
    }
}
Courser answered 1/11, 2012 at 1:15 Comment(1)
It looks like viewToModel() is the way to go. Thanks.Coagulum

© 2022 - 2024 — McMap. All rights reserved.