> 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