This fixes some issues in WrappedPlainView as pointed out by Intel's
testsuite.
2006-08-28 Roman Kennke <[EMAIL PROTECTED]>
* javax/swing/text/BoxView.java
(calculateMinorAxisRequirements): Initialize max size
with Integer.MAX_VALUE.
* javax/swing/text/Utilities.java
(getBreakLocation): For simple chars, scan the text directly.
* javax/swing/text/WrappedPlainView.java
(tabBase): New field.
(tabSize): New field.
(calculateBreakPosition): Use Utilities. Fixed for correct
break calculation.
(changedUpdate): Update children directly.
(insertUpdate): Update children directly. Notify children.
(removeUpdate): Update children directly. Notify children.
(updateChildren): New helper method.
(nextTabStop): Fixed to return correct results.
(paint): Update tabBase.
(updateMetrics): Update tab size.
/Roman
Index: javax/swing/text/BoxView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/BoxView.java,v
retrieving revision 1.23
diff -u -1 -2 -r1.23 BoxView.java
--- javax/swing/text/BoxView.java 28 Aug 2006 15:52:05 -0000 1.23
+++ javax/swing/text/BoxView.java 28 Aug 2006 21:40:32 -0000
@@ -572,25 +572,25 @@
* @return the size requirements for this <code>BoxView</code> along
* the specified axis
*/
protected SizeRequirements calculateMinorAxisRequirements(int axis,
SizeRequirements sr)
{
SizeRequirements res = sr;
if (res == null)
res = new SizeRequirements();
res.minimum = 0;
res.preferred = 0;
- res.maximum = 0;
+ res.maximum = Integer.MAX_VALUE;
res.alignment = 0.5F;
int n = getViewCount();
for (int i = 0; i < n; i++)
{
View child = getView(i);
res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum);
res.preferred = Math.max((int) child.getPreferredSpan(axis),
res.preferred);
res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum);
}
return res;
Index: javax/swing/text/Utilities.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/Utilities.java,v
retrieving revision 1.37
diff -u -1 -2 -r1.37 Utilities.java
--- javax/swing/text/Utilities.java 24 Aug 2006 18:34:52 -0000 1.37
+++ javax/swing/text/Utilities.java 28 Aug 2006 21:40:32 -0000
@@ -524,46 +524,57 @@
* @param metrics the font metrics used for calculating the break point
* @param x0 starting view location representing the start of the text
* @param x the target view location
* @param e the TabExpander used for expanding tabs (if this is null tabs
* are expanded to 1 space)
* @param startOffset the offset in the Document of the start of the text
* @return the offset at which we should break the text
*/
public static final int getBreakLocation(Segment s, FontMetrics metrics,
int x0, int x, TabExpander e,
int startOffset)
{
- int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false);
- BreakIterator breaker = BreakIterator.getWordInstance();
- breaker.setText(s);
-
- // If startOffset and s.offset differ then we need to use
- // that difference two convert the offset between the two metrics.
- int shift = startOffset - s.offset;
-
+ int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset,
+ false);
+ int breakLoc = mark;
// If mark is equal to the end of the string, just use that position.
- if (mark >= shift + s.count)
- return mark;
-
- // Try to find a word boundary previous to the mark at which we
- // can break the text.
- int preceding = breaker.preceding(mark + 1 + s.offset);
-
- if (preceding != 0)
- return preceding + shift;
-
- // If preceding is 0 we couldn't find a suitable word-boundary so
- // just break it on the character boundary
- return mark;
+ if (mark < s.count - 1)
+ {
+ for (int i = s.offset + mark; i >= s.offset; i--)
+ {
+ char ch = s.array[i];
+ if (ch < 256)
+ {
+ // For ASCII simply scan backwards for whitespace.
+ if (Character.isWhitespace(ch))
+ {
+ breakLoc = i - s.offset + 1;
+ break;
+ }
+ }
+ else
+ {
+ // Only query BreakIterator for complex chars.
+ BreakIterator bi = BreakIterator.getLineInstance();
+ bi.setText(s);
+ int pos = bi.preceding(i + 1);
+ if (pos > s.offset)
+ {
+ breakLoc = breakLoc - s.offset;
+ }
+ break;
+ }
+ }
+ }
+ return breakLoc;
}
/**
* Returns the paragraph element in the text component <code>c</code> at
* the specified location <code>offset</code>.
*
* @param c the text component
* @param offset the offset of the paragraph element to return
*
* @return the paragraph element at <code>offset</code>
*/
public static final Element getParagraphElement(JTextComponent c, int offset)
Index: javax/swing/text/WrappedPlainView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/WrappedPlainView.java,v
retrieving revision 1.23
diff -u -1 -2 -r1.23 WrappedPlainView.java
--- javax/swing/text/WrappedPlainView.java 15 Aug 2006 23:43:18 -0000 1.23
+++ javax/swing/text/WrappedPlainView.java 28 Aug 2006 21:40:33 -0000
@@ -74,25 +74,35 @@
/** A ViewFactory that creates WrappedLines **/
ViewFactory viewFactory = new WrappedLineCreator();
/** The start of the selected text **/
int selectionStart;
/** The end of the selected text **/
int selectionEnd;
/** The height of the line (used while painting) **/
int lineHeight;
-
+
+ /**
+ * The base offset for tab calculations.
+ */
+ private int tabBase;
+
+ /**
+ * The tab size.
+ */
+ private int tabSize;
+
/**
* The instance returned by [EMAIL PROTECTED] #getLineBuffer()}.
*/
private transient Segment lineBuffer;
public WrappedPlainView (Element elem)
{
this (elem, false);
}
public WrappedPlainView (Element elem, boolean wordWrap)
{
@@ -112,28 +122,31 @@
}
/**
* Returns the next tab stop position after a given reference position.
*
* This implementation ignores the <code>tabStop</code> argument.
*
* @param x the current x position in pixels
* @param tabStop the position within the text stream that the tab occured at
*/
public float nextTabStop(float x, int tabStop)
{
- JTextComponent host = (JTextComponent)getContainer();
- float tabSizePixels = getTabSize()
- * host.getFontMetrics(host.getFont()).charWidth('m');
- return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
+ int next = (int) x;
+ if (tabSize != 0)
+ {
+ int numTabs = ((int) x - tabBase) / tabSize;
+ next = tabBase + (numTabs + 1) * tabSize;
+ }
+ return next;
}
/**
* Returns the tab size for the Document based on
* PlainDocument.tabSizeAttribute, defaulting to 8 if this property is
* not defined
*
* @return the tab size.
*/
protected int getTabSize()
{
Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
@@ -265,62 +278,50 @@
/**
* Calculates the break position for the text between model positions
* p0 and p1. Will break on word boundaries or character boundaries
* depending on the break argument given in construction of this
* WrappedPlainView. Used by the nested WrappedLine class to determine
* when to start the next logical line.
* @param p0 the start model position
* @param p1 the end model position
* @return the model position at which to break the text
*/
protected int calculateBreakPosition(int p0, int p1)
{
- Container c = getContainer();
-
- int li = getLeftInset();
- int ti = getTopInset();
-
- Rectangle alloc = new Rectangle(li, ti,
- getWidth()-getRightInset()-li,
- getHeight()-getBottomInset()-ti);
-
- // Mimic a behavior observed in the RI.
- if (alloc.isEmpty())
- return 0;
-
- updateMetrics();
-
+ Segment s = new Segment();
try
{
- getDocument().getText(p0, p1 - p0, getLineBuffer());
+ getDocument().getText(p0, p1 - p0, s);
}
- catch (BadLocationException ble)
+ catch (BadLocationException ex)
{
- // this shouldn't happen
- throw new InternalError("Invalid offsets p0: " + p0 + " - p1: " + p1);
+ assert false : "Couldn't load text";
}
-
+ int width = getWidth();
+ int pos;
if (wordWrap)
- return p0 + Utilities.getBreakLocation(lineBuffer, metrics, alloc.x,
- alloc.x + alloc.width, this, 0);
+ pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase,
+ tabBase + width, this, p0);
else
- return p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x,
- alloc.x + alloc.width, this, 0,
- true);
+ pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase,
+ tabBase + width, this, p0,
+ false);
+ return pos;
}
void updateMetrics()
{
Container component = getContainer();
metrics = component.getFontMetrics(component.getFont());
+ tabSize = getTabSize()* metrics.charWidth('m');
}
/**
* Determines the preferred span along the given axis. Implemented to
* cache the font metrics and then call the super classes method.
*/
public float getPreferredSpan (int axis)
{
updateMetrics();
return super.getPreferredSpan(axis);
}
@@ -341,71 +342,114 @@
public float getMaximumSpan (int axis)
{
updateMetrics();
return super.getMaximumSpan(axis);
}
/**
* Called when something was inserted. Overridden so that
* the view factory creates WrappedLine views.
*/
public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
{
- super.insertUpdate(e, a, viewFactory);
+ // Update children efficiently.
+ updateChildren(e, a);
- // No repaint needed, as this is done by the WrappedLine instances.
+ // Notify children.
+ Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
+ : null;
+ View v = getViewAtPosition(e.getOffset(), r);
+ if (v != null)
+ v.insertUpdate(e, r, f);
}
/**
* Called when something is removed. Overridden so that
* the view factory creates WrappedLine views.
*/
public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
{
- super.removeUpdate(e, a, viewFactory);
-
- // No repaint needed, as this is done by the WrappedLine instances.
+ // Update children efficiently.
+ updateChildren(e, a);
+
+ // Notify children.
+ Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
+ : null;
+ View v = getViewAtPosition(e.getOffset(), r);
+ if (v != null)
+ v.removeUpdate(e, r, f);
}
/**
* Called when the portion of the Document that this View is responsible
* for changes. Overridden so that the view factory creates
* WrappedLine views.
*/
public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
{
- super.changedUpdate(e, a, viewFactory);
-
- // No repaint needed, as this is done by the WrappedLine instances.
+ // Update children efficiently.
+ updateChildren(e, a);
}
-
+
+ /**
+ * Helper method. Updates the child views in response to
+ * insert/remove/change updates. This is here to be a little more efficient
+ * than the BoxView implementation.
+ *
+ * @param ev the document event
+ * @param a the shape
+ */
+ private void updateChildren(DocumentEvent ev, Shape a)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ Element[] removed = ec.getChildrenRemoved();
+ Element[] added = ec.getChildrenAdded();
+ View[] addedViews = new View[added.length];
+ for (int i = 0; i < added.length; i++)
+ addedViews[i] = new WrappedLine(added[i]);
+ replace(ec.getIndex(), removed.length, addedViews);
+ if (a != null)
+ {
+ preferenceChanged(null, true, true);
+ getContainer().repaint();
+ }
+ }
+ updateMetrics();
+ }
+
class WrappedLineCreator implements ViewFactory
{
// Creates a new WrappedLine
public View create(Element elem)
{
return new WrappedLine(elem);
}
}
/**
* Renders the <code>Element</code> that is associated with this
* <code>View</code>. Caches the metrics and then calls
* super.paint to paint all the child views.
*
* @param g the <code>Graphics</code> context to render to
* @param a the allocated region for the <code>Element</code>
*/
public void paint(Graphics g, Shape a)
{
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ tabBase = r.x;
+
JTextComponent comp = (JTextComponent)getContainer();
// Ensure metrics are up-to-date.
updateMetrics();
selectionStart = comp.getSelectionStart();
selectionEnd = comp.getSelectionEnd();
selectedColor = comp.getSelectedTextColor();
unselectedColor = comp.getForeground();
disabledColor = comp.getDisabledTextColor();
selectedColor = comp.getSelectedTextColor();
lineHeight = metrics.getHeight();
@@ -425,25 +469,24 @@
preferenceChanged(null, true, true);
super.setSize(width, height);
}
class WrappedLine extends View
{
/** Used to cache the number of lines for this View **/
int numLines = 1;
public WrappedLine(Element elem)
{
super(elem);
- determineNumLines();
}
/**
* Renders this (possibly wrapped) line using the given Graphics object
* and on the given rendering surface.
*/
public void paint(Graphics g, Shape s)
{
Rectangle rect = s.getBounds();
int end = getEndOffset();
int currStart = getStartOffset();
@@ -487,55 +530,47 @@
count++;
}
if (count != numLines)
{
numLines = count;
preferenceChanged(this, false, true);
}
}
-
+
/**
* Calculates the number of logical lines that the Element
* needs to be displayed and updates the variable numLines
* accordingly.
*/
- void determineNumLines()
+ private int determineNumLines()
{
- numLines = 0;
+ int nLines = 0;
int end = getEndOffset();
- if (end == 0)
- return;
-
- int breakPoint;
for (int i = getStartOffset(); i < end;)
{
- numLines ++;
+ nLines++;
// careful: check that there's no off-by-one problem here
// depending on which position calculateBreakPosition returns
- breakPoint = calculateBreakPosition(i, end);
+ int breakPoint = calculateBreakPosition(i, end);
- if (breakPoint == 0)
- return;
-
- // If breakPoint is equal to the current index no further
- // line is needed and we can end the loop.
if (breakPoint == i)
- break;
+ i = breakPoint + 1;
else
i = breakPoint;
}
+ return nLines;
}
/**
* Determines the preferred span for this view along the given axis.
*
* @param axis the axis (either X_AXIS or Y_AXIS)
*
* @return the preferred span along the given axis.
* @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS
*/
public float getPreferredSpan(int axis)
{
@@ -639,25 +674,25 @@
int currLineStart = getStartOffset();
// Although calling modelToView with the last possible offset will
// cause a BadLocationException in CompositeView it is allowed
// to return that offset in viewToModel.
int end = getEndOffset();
int lineHeight = metrics.getHeight();
if (y < rect.y)
return currLineStart;
if (y > rect.y + rect.height)
- return end;
+ return end - 1;
// Note: rect.x and rect.width do not represent the width of painted
// text but the area where text *may* be painted. This means the width
// is most of the time identical to the component's width.
while (currLineStart != end)
{
int currLineEnd = calculateBreakPosition(currLineStart, end);
// If we're at the right y-position that means we're on the right
// logical line and we should look for the character
if (y >= rect.y && y < rect.y + lineHeight)
@@ -700,67 +735,61 @@
* <p>If the number of lines in the document has changed, just repaint
* the whole thing (note, could improve performance by not repainting
* anything above the changes). If the number of lines hasn't changed,
* just repaint the given Rectangle.</p>
*
* <p>Note that the <code>Rectangle</code> argument may be <code>null</code>
* when the allocation area is empty.</code>
*
* @param a the Rectangle to repaint if the number of lines hasn't changed
*/
void updateDamage (Rectangle a)
{
- // If the allocation area is empty we can't do anything useful.
- // As determining the number of lines is impossible in that state we
- // reset it to an invalid value which can then be recalculated at a
- // later point.
- if (a == null || a.isEmpty())
+ int nLines = determineNumLines();
+ if (numLines != nLines)
{
- numLines = 1;
- return;
+ numLines = nLines;
+ preferenceChanged(this, false, true);
+ getContainer().repaint();
}
-
- int oldNumLines = numLines;
- determineNumLines();
-
- if (numLines != oldNumLines)
- preferenceChanged(this, false, true);
- else
+ else if (a != null)
getContainer().repaint(a.x, a.y, a.width, a.height);
}
/**
* This method is called when something is inserted into the Document
* that this View is displaying.
*
* @param changes the DocumentEvent for the changes.
* @param a the allocation of the View
* @param f the ViewFactory used to rebuild
*/
public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f)
{
- updateDamage((Rectangle)a);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ updateDamage(r);
}
/**
* This method is called when something is removed from the Document
* that this View is displaying.
*
* @param changes the DocumentEvent for the changes.
* @param a the allocation of the View
* @param f the ViewFactory used to rebuild
*/
public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f)
{
// Note: This method is not called when characters from the
// end of the document are removed. The reason for this
// can be found in the implementation of View.forwardUpdate:
// The document event will denote offsets which do not exist
// any more, getViewIndex() will therefore return -1 and this
// makes View.forwardUpdate() skip this method call.
// However this seems to cause no trouble and as it reduces the
// number of method calls it can stay this way.
- updateDamage((Rectangle)a);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ updateDamage(r);
}
}
}