6 Replies Latest reply on Jan 9, 2007 11:19 PM by 843805

    JTextPane View Problem

    843805
      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);
                              
      
                          }
                      }
        • 1. Re: JTextPane View Problem
          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
          • 2. Re: JTextPane View Problem
            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
            • 3. Re: JTextPane View Problem
              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.
              • 4. Re: JTextPane View Problem
                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.
                • 5. Re: JTextPane View Problem
                  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); }
                  
                       }
                  }
                  • 6. Re: JTextPane View Problem
                    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.