Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

JTextPane View Problem

843805Jan 9 2007 — edited Jan 9 2007
Hey all,

I'm having some trouble manipulating the view of a JTextPane. My goal is to be able to visualize all the whitespace in the pane (spaces as '.', newlines as 'P' etc).

So far I've succeeded in painting over the white space in lines containing other text. However, getting the new lines to be painted onto blank lines (lines just containing a newline and nothing else) has been a problem. It seems that the JTextPane view doesn't both creating a LabelView for lines that have nothing but a newline on them, so my paint method never gets called for them. Is there some class I can override, or some setting I can tweak, so the pane explicitly creates an instance of my custom label view, even for lines that consist solely of '\n' ?

Thanks for your help!

For reference, here's my code so far:

Here I override the EditorKit for the JTextPane:
    class WhitespaceEditorKit extends StyledEditorKit {
        public ViewFactory getViewFactory() {
            return new WhitespaceViewFactory();
        }
    }
Then I override the ViewFactory:
    class WhitespaceViewFactory implements ViewFactory {
        public View create(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new WhitespaceLabelView(elem);
                } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new ParagraphView(elem);
                } else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new BoxView(elem, View.Y_AXIS);
                } else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                } else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }
            }
            // default to text display
            //return new LabelView(elem);
            return new WhitespaceLabelView(elem);
        }
    }
Finally, I override the LabelView's paint method, so I can paint over the whitespace. The \u007 are the Unicode characters I'm painting over the whitespace:
    class WhitespaceLabelView extends LabelView {
        public WhitespaceLabelView(Element elem) {
            super(elem);
        }
        
        public void paint(Graphics g, Shape a) {
            super.paint(g,a);
            
            //Here we give a visual representation of whitespace if the option is selected.
            if(showWhitespace) {
                java.awt.FontMetrics fontMetrics = g.getFontMetrics();
                String text = getText(getStartOffset(),getEndOffset()).toString();
                Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
                int spaceWidth = fontMetrics.stringWidth(" ");
                int newlineWidth = fontMetrics.stringWidth("D");
                
                int sumOfTabs = 0;
                
                for(int i = 0; i < text.length(); i++) {
                    boolean isNewlinePostfixed = false;
                    if(text.substring(i,i+1).equals(" ")) {
                        int previousStringWidth = fontMetrics.stringWidth(text.substring(0,i)) + sumOfTabs;
                        //g.setColor(RBConstants.whitespaceHighlight);
                        //g.fillRect(alloc.x + previousStringWidth, alloc.y, spaceWidth, alloc.height);
                        g.setColor(Color.DARK_GRAY);
                        g.drawString("\u00b7",alloc.x+previousStringWidth,alloc.y+alloc.height-4);
                        
                    } else if (text.substring(i,i+1).equals("\t")) {
                        int previousStringWidth = fontMetrics.stringWidth(text.substring(0,i)) + sumOfTabs;
                        int tabWidth = (int)getTabExpander().nextTabStop((float)alloc.x+previousStringWidth,i) - previousStringWidth - alloc.x;
                        //g.setColor(RBConstants.tabColor);
                        //g.fillRect(alloc.x+previousStringWidth,alloc.y,tabWidth,alloc.height);
                        
                        g.setColor(Color.DARK_GRAY);
                        g.drawString(">",alloc.x+previousStringWidth+(tabWidth/2),alloc.y+alloc.height-4);
                        
                        sumOfTabs+=tabWidth;
                        
                    } else if (text.substring(i,i+1).equals("\n") || text.substring(i,i+1).equals("\r")) {
                        int previousStringWidth = fontMetrics.stringWidth(text.substring(0,i)) + sumOfTabs;
                        //g.setColor(Color.LIGHT_GRAY);
                        //g.fillRect(6+alloc.x+previousStringWidth,alloc.y,newlineWidth,alloc.height);
                        
                        g.setColor(Color.DARK_GRAY);
                        g.drawString("\u00b6",6+alloc.x+previousStringWidth,alloc.y+alloc.height-4);
                        

                    }
                }

Comments

camickr
It seems that the JTextPane view doesn't both creating a LabelView > for lines that have nothing but a newline on them, so my paint method never gets called for them
The paint() method always gets called in this example:

http://forum.java.sun.com/thread.jspa?forumID=57&threadID=602340
843805
hi camickr, thanks for the quick response.

As you may have noticed from the similarities in code, the example you pointed me towards was the foundation for my code. I may be missing something, but I'm still not seeing anything drawn on blank lines. To test this out, I changed your example a bit by changing the line:
int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth());
to:
int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth() + 30);
so it paints your jagged line even if the the bounding rectangle has 0 width. Running the example, if you edit the text pane to include some blank lines, like:

test test test

test test test

test test test


I would expect to have it draw:
test test test___
___
test test test___
___
test test test___


But instead it draws:
test test test___

test test test___

test test test___

With nothing drawn for the blank lines. From what I can tell, it's because no label view gets created for the blank lines, so they never get drawn. Is there a way to have your example come out like the middle one above, with some underlining draw even for the blank lines?

Thanks again!
Rob
camickr
I changed the text so it has a couple of blank lines
pane.setText("test test test\n\n\n\ntest test test");
I then displayed the Shape at the start of the printJaggedLine() method:
public void paintJaggedLine(Graphics g, Shape a) {
    System.out.println(a);
I got 5 lines of squiggles (so they where painted for the blank lines) and I got the following output:

java.awt.Rectangle[x=3,y=3,width=98,height=17]
java.awt.Rectangle[x=3,y=3,width=98,height=17]
java.awt.Rectangle[x=3,y=20,width=1,height=17]
java.awt.Rectangle[x=3,y=37,width=1,height=17]
java.awt.Rectangle[x=3,y=54,width=1,height=17]
java.awt.Rectangle[x=3,y=71,width=98,height=17]
java.awt.Rectangle[x=3,y=3,width=98,height=17]

Notice how the "Y" coordinate increases for each line and the width is different for each line depending on whether the line has text or not.

I'm using JDK1.4.2 on XP. Maybe its a platform or version issue, which I can't help you with.
843805
It is indeed a platform issue. I've been using Java 1.6 on windows XP. I downloaded the 1.4.2 jre and ran the same code using it, and saw the exact behavior you described - paint being called on every line. I also tried it using 1.5.0_10, and it failed to paint every line as with 1.6.

So, I guess in the Java 1.5 update the behavior was changed, such that it's "smart" enough to not bother painting blank lines. Too bad I only found it now, I would have liked a T-shirt.
camickr
Don't know if this will help your or not, but I noticed that the ParagraphView is also called in JDK1.4.2, so maybe its called in later releases as well.

Anyway, I changed the code to draw a paragraph marker:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import java.util.*;

class TextPaneParagraphMark {

	public TextPaneParagraphMark() {
		JFrame fr = new JFrame("TEST");
		fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		JEditorPane pane = new JEditorPane();
		pane.setEditorKit(new NewEditorKit());
		pane.setText("test test test\n\n\n\ntest test test");
		JScrollPane sp = new JScrollPane(pane);

		fr.getContentPane().add(sp);
		fr.setSize(300, 300);
		fr.show();
	}

	public static void main(String[] args) {
		TextPaneParagraphMark test = new TextPaneParagraphMark();
	}
}

class NewEditorKit extends StyledEditorKit {
	public ViewFactory getViewFactory() {
		return new NewViewFactory();
	}
}

class NewViewFactory implements ViewFactory {
	public View create(Element elem) {
		String kind = elem.getName();
		if (kind != null) {
			if (kind.equals(AbstractDocument.ContentElementName)) {
				return new LabelView(elem);
			}
			else if (kind.equals(AbstractDocument.ParagraphElementName)) {
				return new MyParagraphView(elem);
			}
			else if (kind.equals(AbstractDocument.SectionElementName)) {
				return new BoxView(elem, View.Y_AXIS);
			}
			else if (kind.equals(StyleConstants.ComponentElementName)) {
				return new ComponentView(elem);
			}
			else if (kind.equals(StyleConstants.IconElementName)) {
				return new IconView(elem);
			}
		}

		// default to text display
		return new LabelView(elem);
	}
}

class MyParagraphView extends ParagraphView {

	public MyParagraphView(Element elem) {
		super(elem);
	}

	public void paint(Graphics g, Shape allocation) {
		super.paint(g, allocation);
		paintCustomParagraph(g, allocation);
	}

	public void paintCustomParagraph(Graphics g, Shape a) {
		try
		{
			Shape paragraph = modelToView( getEndOffset(), a, Position.Bias.Backward);

			int y = a.getBounds().y;
			int x = paragraph == null ? a.getBounds().x : paragraph.getBounds().x;

			Color old = g.getColor();
			g.setColor(Color.red);
			g.drawArc(x + 3, y + 6, 3, 3, 0, 180);
			g.drawArc(x + 6, y + 6, 3, 3, 180, 181);
			g.setColor(old);
		}
		catch(Exception e) { System.out.println(e); }

	}
}
843805
Thanks camickr, using the ParagraphView that way worked perfectly. I'd tried it before, overriding the paintChild() method, but couldn't get anything useful out of it. However, your method worked quite well.
1 - 6
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Feb 6 2007
Added on Jan 9 2007
6 comments
960 views