Highlight current row in JTextPane
Asked Answered
I

3

11

I'm trying for more than 2 days to implement a specific requirement for a text editor window... unfortunately without success so far :(

The goal is to get a text editor window which will highlight the current row, like other text editors do. With current row I mean the row where currently the cursor/caret is positioned.

I already found two different approaches but unfortunately I'm not able to adopt them so they work as expected.

The first approach is to overwrite the DefaultHighlighter (http://snippets.dzone.com/posts/show/6688). In the second approach the HighlighterPainter will be overwritten instead (http://www.jroller.com/santhosh/date/20050622).

Right now I'm trying to adopt the first approach in my project but as I said it is not working as desired.

At the end of this post I'm posting a small sample application which demonstrates the problem.

  • If I start the program, the caret is placed at the beginning of the first line. However, the line is not highlighted.
  • Now I type in some characters. Those chars will be highlighted but only those chars not the complete line
  • I hit enter to move to the next line. The first line is not highlighted anymore what is correct. The second line isn't highlighted as well, what is not correct. Again, when I type in some chars, those will be higlighted but not the complete row.
  • When I now move back the caret to the first line, either by cursor up key or mouse clicking, the complete first line will be highlighted, not only the existing chars. This is the behavior I want right from the start.

I hope anybody can tell me what I'm doing wrong here... or explain why it is not possible to resolve that issue at all. Any alternative solutions how I could realize the line highlighting are also highly appreciated!

Thanks a lot in advance Cheers Preachie

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;

public class HighlightProblem extends JFrame {
    private static final long serialVersionUID = 1L;
    private final JTextPane textPane;
    private final Highlighter.HighlightPainter cyanPainter;

    public HighlightProblem() {
        cyanPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.CYAN);

        textPane = new JTextPane();
        textPane.setPreferredSize(new Dimension(500, 300));
        textPane.setHighlighter(new LineHighlighter());
        textPane.addCaretListener(new CaretListener() {
            @Override
            public void caretUpdate(CaretEvent e) {
                setHighlight(e);
            }
        });
        getContentPane().add(textPane);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }

    public static void main(String[] args) {
        new HighlightProblem();
    }

    public void setHighlight(CaretEvent e) {
        textPane.getHighlighter().removeAllHighlights();
        int currentLine = getLineFromOffset(textPane, e.getDot());
        int startPos = getLineStartOffsetForLine(textPane, currentLine);
        int endOffset = getLineEndOffsetForLine(textPane, currentLine);

        try {
            textPane.getHighlighter().addHighlight(startPos, endOffset, cyanPainter);           
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        textPane.repaint();
    }

    public int getLineFromOffset(JTextComponent component, int offset) {
        return component.getDocument().getDefaultRootElement().getElementIndex(offset);
    }

    public int getLineStartOffsetForLine(JTextComponent component, int line) {
        return component.getDocument().getDefaultRootElement().getElement(line).getStartOffset();
    }

    public int getLineEndOffsetForLine(JTextComponent component, int line) {
        return component.getDocument().getDefaultRootElement().getElement(line).getEndOffset();
    }

    public class LineHighlighter extends DefaultHighlighter {
        private JTextComponent component;

        @Override
        public final void install(final JTextComponent c) {
            super.install(c);
            this.component = c;
        }

        @Override
        public final void deinstall(final JTextComponent c) {
            super.deinstall(c);
            this.component = null;
        }

        @Override
        public final void paint(final Graphics g) {
            final Highlighter.Highlight[] highlights = getHighlights();
            final int len = highlights.length;
            for (int i = 0; i < len; i++) {
                Highlighter.Highlight info = highlights[i];
                if (info.getClass().getName().indexOf("LayeredHighlightInfo") > -1) {
                    // Avoid allocing unless we need it.
                    final Rectangle a = this.component.getBounds();
                    final Insets insets = this.component.getInsets();
                    a.x = insets.left;
                    a.y = insets.top;
                    // a.width -= insets.left + insets.right + 100;
                    a.height -= insets.top + insets.bottom;
                    final Highlighter.HighlightPainter p = info.getPainter();
                    p.paint(g, info.getStartOffset(), info.getEndOffset(), a, this.component);
                }
            }
        }

        @Override
        public void removeAllHighlights() {
            textPane.repaint(0, 0, textPane.getWidth(), textPane.getHeight());
            super.removeAllHighlights();
        }
    }
}
Irk answered 23/3, 2011 at 12:55 Comment(0)
B
4

http://tips4java.wordpress.com/2008/10/29/line-painter/

I think this is what you are looking for. I took that LinePainter class and copied your constructor over into a main method, took out your highlighter parts and added a new LinePainter(textPane); Works like a charm

Baynebridge answered 23/3, 2011 at 13:53 Comment(0)
U
2

Below is the code to extract text from current line. You can use same logic to get required indexes and highlight text

    private String getCurrentEditLine() {
        int readBackChars = 100;
        int caretPosition = scriptEditor.getCaretPosition();

        if (caretPosition == 0) {
            return null;
        }

        StyledDocument doc = scriptEditor.getStyledDocument();

        int offset = caretPosition <= readBackChars ? 0 : caretPosition
                - readBackChars;

        String text = null;
        try {
            text = doc.getText(offset, caretPosition);
        } catch (BadLocationException e) {
        }

        if (text != null) {
            int idx = text.lastIndexOf("\n");
            if(idx != -1) {
                return text.substring(idx);
            }else {
                return text;
            }
        }

        return null;
    }
Unchristian answered 15/3, 2012 at 3:10 Comment(0)
B
1

I think this might be difficult to achieve using highlighters - I don't think it is what they were designed for. You may need to do it with custom painting code:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;

public class HighlightLineTest {
    private static class HighlightLineTextPane extends JTextPane {
        public HighlightLineTextPane() {
            // Has to be marked as transparent so the background is not replaced by 
            // super.paintComponent(g);
            setOpaque(false);
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(getBackground());
            g.fillRect(0, 0, getWidth(), getHeight());
            try {
                Rectangle rect = modelToView(getCaretPosition());
                if (rect != null) {
                    g.setColor(Color.CYAN);
                    g.fillRect(0, rect.y, getWidth(), rect.height);
                }
            } catch (BadLocationException e) {
            }
            super.paintComponent(g);
        }

        @Override
        public void repaint(long tm, int x, int y, int width, int height) {
            // This forces repaints to repaint the entire TextPane.
            super.repaint(tm, 0, 0, getWidth(), getHeight());
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Highlight test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new HighlightLineTextPane());
        frame.setBounds(100, 100, 300, 400);
        frame.setVisible(true);
    }
}
Belding answered 23/3, 2011 at 13:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.