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); 
     }
   }
 }

Reply via email to