Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / GUI / August 2005

Tip: Looking for answers? Try searching our database.

JTextPane: Workaround for invisible text with hanging indents?

Thread view: 
Adam Warner - 27 Aug 2005 02:43 GMT
Hi all,

I have a hanging indent requirement for paragraph formatting:
<http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/text/StyleConstants.html#Fir
stLineIndent
>

"The amount of space to indent the first line of the paragraph. This value
may be negative to offset in the reverse direction. The type is Float and
specifies the size of the space in points."

A search of Sun's bug database for [set]FirstLineIndent indicates that Sun
has fixed a number of bugs related to rendering negative first line
indents. Does the code I've written below demonstrate another rendering
bug? I'm typing invisible text on multiple platforms with the latest
stable JDK and Mustang beta:

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

public class InvisibleText extends JFrame {

   public InvisibleText() {
       JTextPane tp=new JTextPane();
       SimpleAttributeSet attrs=new SimpleAttributeSet();
       StyleConstants.setFirstLineIndent(attrs, -150.0f);
       StyleConstants.setLeftIndent(attrs, 150.0f);
       tp.setParagraphAttributes(attrs, true);

       JScrollPane sp=new JScrollPane(tp);
       sp.setPreferredSize(new Dimension(300, 200));
       getContentPane().add(sp);
    }

   public static void main(String[] args) {
       InvisibleText frame=new InvisibleText();
       frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
       frame.pack();
       frame.setVisible(true);
   }

While typing text finally appears at the left indent position of
subsequent lines.

Assuming my code correctly invokes this API, can anyone come up with a
workaround to make the text appear while it is typed?

Many thanks,
Adam
Christian Kaufhold - 27 Aug 2005 21:48 GMT
> import java.awt.*;
> import javax.swing.*;
[quoted text clipped - 26 lines]
> Assuming my code correctly invokes this API, can anyone come up with a
> workaround to make the text appear while it is typed?

See below, with NegativeFirstLineIndentParagraphView2.
ParagraphView2 allows settings first and other line indents independently
(but does not allow negative values)

Code just written, not really tested.

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

class StyledEditorKit2
   extends StyledEditorKit
   implements ViewFactory
{
   public static final Object
       OtherLinesIndent = "otherLinesIndent";

   private ViewFactory factory = super.getViewFactory();

   public View create(Element e)
   {
       if (e.getName().equals(AbstractDocument.ParagraphElementName))
           return new ParagraphView2(e);
       //            return new NegativeFirstLineIndentParagraphView(e);

       return factory.create(e);
   }
       
   public ViewFactory getViewFactory()
   {
       return this;
   }
}

class ParagraphView2
   extends ParagraphView
{
   private int firstLineIndent;
   private int otherLinesIndent;

   public ParagraphView2(Element e)
   {
       super(e);
   }

   public void setOtherLinesIndent(float value)
   {
       otherLinesIndent = Math.max(0, (int)value);
   }

   public final int otherLinesIndent()
   {
       return otherLinesIndent;
   }

   public void setFirstLineIndent(float value)
   {
       firstLineIndent = Math.max(0, (int)value);
   }

   public final int firstLineIndent()
   {
       return firstLineIndent;
   }

   protected void setPropertiesFromAttributes()
   {
       super.setPropertiesFromAttributes();

       super.firstLineIndent = 0;

       AttributeSet a = getAttributes();

       if (a != null)
       {
           Float f = (Float)a.getAttribute(StyledEditorKit2.OtherLinesIndent);
               
           setOtherLinesIndent(f == null ? 0f : f.floatValue());

           setFirstLineIndent(StyleConstants.getFirstLineIndent(a));
       }
   }

   public int getFlowSpan(int index)
   {
       return layoutSpan - (index == 0 ? firstLineIndent : otherLinesIndent);
   }
       
   public int getFlowStart(int index)
   {
       return ((int)getTabBase()) + (index == 0 ? firstLineIndent : otherLinesIndent);
   }

   private boolean isLeftToRight()
   {
       // There must be some better way to do this.
       return !flipEastAndWestAtEnds(-1, null);
   }

   // Unlike ParagraphView itself, the indent is not included in the Rows, but
   // implemented by a modification of the BoxView layout of the ParagraphView
   // itself.
       
   protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans)
   {
       boolean rtl = !isLeftToRight();

       int n = getViewCount();

       int indent = firstLineIndent;

       for (int i = 0; i < n; i++)
       {
           View v = getView(i);

           int max = (int)Math.ceil(v.getMaximumSpan(axis));

           if (max < targetSpan - indent)
           {
               float a = v.getAlignment(axis);
                   
               // This aligns in the span without the indent, unlike ParagraphView
               offsets[i] = (int)((targetSpan - indent - max) * a) + indent;
               spans[i] = max;
           }
           else
           {
               int min = (int)Math.ceil(v.getMinimumSpan(axis));
               offsets[i] = indent;
               spans[i] = Math.max(min, targetSpan - indent);
           }

           if (rtl)
               offsets[i] = targetSpan - offsets[i] - spans[i];
               
           indent = otherLinesIndent;
       }
   }

   protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r)
   {
       if (r == null)
           r = new SizeRequirements();

       float pref = poolPreferredXSpan();
       float min = poolMinimumXSpan();
           
       r.minimum = (int)Math.ceil(min);
       r.preferred = Math.max(r.minimum, (int)Math.ceil(pref));
       r.maximum = Integer.MAX_VALUE;
       r.alignment = 0.5f;

       return r;
   }

   private float poolPreferredXSpan()
   {
       float result = 0;
       float pref = 0;

       int indent = firstLineIndent;

       for (int n = layoutPool.getViewCount(), i = 0; i < n; i++)
       {
           View v = layoutPool.getView(i);

           pref += indent + v.getPreferredSpan(X_AXIS);

           if (v.getBreakWeight(X_AXIS, 0, Integer.MAX_VALUE) >= ForcedBreakWeight)
           {
               result = Math.max(result, pref);
               pref = 0;
               indent = otherLinesIndent;
           }
       }

       result = Math.max(result, pref);

       return result;
   }

   private float poolMinimumXSpan()
   {
       float result = 0;

       float min = 0;
       boolean lineHasView = false;

       int indent = firstLineIndent;

       for (int n = layoutPool.getViewCount(), i = 0; i < n; i++)
       {
           View v = layoutPool.getView(i);
               
           if (v.getBreakWeight(X_AXIS, 0, Integer.MAX_VALUE) == BadBreakWeight)
           {
               min += v.getPreferredSpan(X_AXIS);
               lineHasView = true;
           }
           else
           {
               if (lineHasView)
               {
                   result = Math.max(min, result);
                   
                   lineHasView = false;
                   min = 0;
                   
                   indent = otherLinesIndent;
               }
           }
       }

       result = Math.max(result, min);
       return result;
   }
}

class NegativeFirstLineIndentParagraphView
   extends ParagraphView
{
   public NegativeFirstLineIndentParagraphView(Element e)
   {
       super(e);
   }

   public void paint(Graphics g, Shape a)
   {
       super.paint(g, a);

       // do not optimize only if intersection, as super does
       // this, again, could be optimized not to paint twice
       if (firstLineIndent < 0)
       {
           Rectangle r = a.getBounds();

           // may need to ensure clipping on the paragraph bounds,
           // but is also not done by super
       
           r.x += getLeftInset() + getOffset(X_AXIS, 0);
           r.y += getTopInset() + getOffset(Y_AXIS, 0);
           r.width = getSpan(X_AXIS, 0) - firstLineIndent;
           r.height = getSpan(Y_AXIS, 0);                            

           paintChild(g, r, 0);                      
       }
   }
}

public class InvisibleText extends JFrame
{
   public InvisibleText()
   {
       JTextPane tp=new JTextPane();
       tp.setEditorKit(new StyledEditorKit2());

       SimpleAttributeSet attrs=new SimpleAttributeSet();

       // for ParagraphView2
       attrs.addAttribute(StyleConstants.FirstLineIndent, new Float(50f));
       attrs.addAttribute(StyledEditorKit2.OtherLinesIndent, new Float(100f));

       // for fun
       StyleConstants.setAlignment(attrs, StyleConstants.ALIGN_CENTER);

       // for NegativeFirstLineIndentParagraphView
       //        StyleConstants.setFirstLineIndent(attrs, -150.0f);
       //        StyleConstants.setLeftIndent(attrs, 150.0f);

       tp.setParagraphAttributes(attrs, true);

       JScrollPane sp=new JScrollPane(tp);
       sp.setPreferredSize(new Dimension(300, 200));
       getContentPane().add(sp);
    }

   public static void main(String[] args) {
       InvisibleText frame=new InvisibleText();
       frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
       frame.pack();
       frame.setVisible(true);
   }

}

Christian
Adam Warner - 28 Aug 2005 00:10 GMT
>> Assuming my code correctly invokes this API, can anyone come up with a
>> workaround to make the text appear while it is typed?
>
> See below, with NegativeFirstLineIndentParagraphView2. ParagraphView2
> allows settings first and other line indents independently (but does not
> allow negative values)

Christian, your hundreds of lines example of how to implement a new styled
editor kit view to overcome this bug is greatly appreciated! It also helps
me understand how developers manage to work around bugs in Sun's Java when
they're prohibited from fixing Java itself.

It looks like this Bug ID comment from 2001 is fully applicable today:
<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4336804>

  Submitted On 05-NOV-2001
  hubersn

  It is just incredible if you look at the amount of bugs and the
  apparent lack of regression tests with the Swing text classes. 1.1.x
  had working TABs, but had problems with a negative first line indent.
  1.2.2 had a working first line indent, but TABs did not work at all
  (!). 1.3 had working TABs again, but no first line indent - again. All
  versions are not able to fully justify text, although that
  functionality is properly documented and should be expected to work.

  End of story: implement your own paragraph view. Based on past
  experience, I would not rely on any coming Sun implementation.

Thanks again,
Adam
Christian Kaufhold - 28 Aug 2005 11:36 GMT
Hello!


>    private float poolPreferredXSpan()

[...]

>    private float poolMinimumXSpan()

[...]

I forgot to actually take the indent into account here. But at least
"poolMinimumXSpan" is not anywhere near working anyway (It always is just
the indent of the first line) -- there is no feature in View to find the
actual minimum width if all possible break points are chosen. Also see
javax.swing.text.html for a hack.

Anyway, some better readable, more correct implementations.

   public float poolPreferredXSpan()
   {
    float result = 0;

    float lineSpan = firstLineIndent;

   
    for (int n = layoutPool.getViewCount(), i = 0; i < n; i++)
    {
       View v = layoutPool.getView(i);

       lineSpan += v.getPreferredSpan(X_AXIS);

       if (v.getBreakWeight(X_AXIS, 0, Integer.MAX_VALUE) >= ForcedBreakWeight)
       {
        result = Math.max(result, lineSpan);

        lineSpan = otherLinesIndent;
       }
    }

    result = Math.max(result, lineSpan);

    return result;
   }

   public float poolMinimumXSpan()
   {
    float result = 0;

    boolean lineHasView = false;
    float lineSpan = firstLineIndent;

    for (int n = layoutPool.getViewCount(), i = 0; i < n; i++)
    {
       View v = layoutPool.getView(i);

           
       if (v.getBreakWeight(X_AXIS, 0, Integer.MAX_VALUE) <= BadBreakWeight)
       {
        lineHasView = true;
        lineSpan += v.getPreferredSpan(X_AXIS);
       }
       else
       {
        if (lineHasView)
        {
           result = Math.max(lineSpan, result);
           
           lineHasView = false;    
           lineSpan = otherLinesIndent;
        }
               else
                  // here would have to add the minimum width of the
                  // fragments of the view
                  ;
       }
    }

    result = Math.max(result, lineSpan);

    return result;
   }

Christian


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.