Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine which lines are visible in scrollable JTextArea?

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

like image 373
Ma99uS Avatar asked Oct 31 '12 18:10

Ma99uS


People also ask

How can we display the line numbers inside a JTextArea in Java?

In this new class, the text component class is JTextPane but you can change it for JTextComponent. To use it, put your JTextLineNumber component into a panel (with a border layout) at WEST and your text component at CENTER. Finally put this panel into a scroll pane.

What are rows and columns in JTextArea?

JTextArea(int row, int column) : constructs a new text area with a given number of rows and columns. JTextArea(String s, int row, int column) : constructs a new text area with a given number of rows and columns and a given initial text.

What is the default found for JTextArea?

Constructs a new TextArea. A default model is set, the initial string is null, and rows/columns are set to 0.

Is JTextArea editable?

The JTextArea class provides a component that displays multiple lines of text and optionally allows the user to edit the text.


2 Answers

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

like image 164
Guillaume Polet Avatar answered Oct 09 '22 15:10

Guillaume Polet


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;
        }
    }
}
like image 40
MadProgrammer Avatar answered Oct 09 '22 16:10

MadProgrammer