Here comes another update on javax.swing.text. Basically this resolves the problem with removing characters in styled text components (observed in BeanShell).

2006-08-05  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/plaf/basic/BasicTextUI.java
        (modelToView): Read-lock the document. Set size of the
        root view before fetching the model-to-view mapping.
        (getViewIndex): Check of the position is inside the range and
        return -1 if this is not the case.
        (getViewAtPosition(int,Rectangle): Update child allocation for valid
        view index.
        (getViewIndexAtPosition(int)): Delegate the index search to
        the element since we have a 1:1 mapping between elements and
        views here.
        * javax/swing/text/DefaultCaret.java
        (appear): Ignore BadLocationException.
        (paint): Ignore BadLocationException.
        * javax/swing/text/FlowView.java
        (changedUpdate): Also notify the layoutPool view.
        (removeUpdate): Also notify the layoutPool view.
        * javax/swing/text/ParagraphView.java
        (Row.getViewIndexAtPosition): Overridden to search linearily
        through the view instead of relying on a 1:1 model to view
        mapping.
        * javax/swing/text/View.java
        (removeUpdate): Clear ElementChange object if updateChildren
        returns false.
        (forwardUpdate): Special handle some boundary cases.

/Roman
Index: javax/swing/plaf/basic/BasicTextUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicTextUI.java,v
retrieving revision 1.88
diff -u -1 -2 -r1.88 BasicTextUI.java
--- javax/swing/plaf/basic/BasicTextUI.java	5 Aug 2006 12:13:19 -0000	1.88
+++ javax/swing/plaf/basic/BasicTextUI.java	7 Aug 2006 11:15:15 -0000
@@ -1178,28 +1178,47 @@
    *        <code>Position.Bias.Forward</code>
    *
    * @return a rectangle that gives the location of the document position
    *         inside the view coordinate space
    *
    * @throws BadLocationException if <code>pos</code> is invalid
    * @throws IllegalArgumentException if b is not one of the above listed
    *         valid values
    */
   public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias)
     throws BadLocationException
   {
-    Rectangle r = getVisibleEditorRect();
-    
-    return (r != null) ? rootView.modelToView(pos, r, bias).getBounds()
-                       : null;
+    // We need to read-lock here because we depend on the document
+    // structure not beeing changed in between.
+    Document doc = textComponent.getDocument();
+    if (doc instanceof AbstractDocument)
+      ((AbstractDocument) doc).readLock();
+    Rectangle rect = null;
+    try
+      {
+        Rectangle r = getVisibleEditorRect();
+        if (r != null)
+          {
+            rootView.setSize(r.width, r.height);
+            Shape s = rootView.modelToView(pos, r, bias);
+            if (s != null)
+              rect = s.getBounds();
+          }
+      }
+    finally
+      {
+        if (doc instanceof AbstractDocument)
+          ((AbstractDocument) doc).readUnlock();
+      }
+    return rect;
   }
 
   /**
    * Maps a point in the <code>View</code> coordinate space to a position
    * inside a document model.
    *
    * @param t the text component
    * @param pt the point to be mapped
    *
    * @return the position inside the document model that corresponds to
    *     <code>pt</code>
    */
Index: javax/swing/text/CompositeView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/CompositeView.java,v
retrieving revision 1.19
diff -u -1 -2 -r1.19 CompositeView.java
--- javax/swing/text/CompositeView.java	5 Aug 2006 12:13:21 -0000	1.19
+++ javax/swing/text/CompositeView.java	7 Aug 2006 11:15:16 -0000
@@ -212,25 +212,25 @@
    *
    * @throws BadLocationException if <code>pos</code> is invalid
    * @throws IllegalArgumentException if b is not one of the above listed
    *         valid values
    */
   public Shape modelToView(int pos, Shape a, Position.Bias bias)
     throws BadLocationException
   {
     boolean backward = bias == Position.Bias.Backward;
     int testpos = backward ? Math.max(0, pos - 1) : pos;
 
     Shape ret = null;
-    if (!backward || testpos >= getStartOffset())
+    if (! backward || testpos >= getStartOffset())
       {
         int childIndex = getViewIndexAtPosition(testpos);
         if (childIndex != -1 && childIndex < getViewCount())
           {
             View child = getView(childIndex);
             if (child != null && testpos >= child.getStartOffset()
                 && testpos < child.getEndOffset())
               {
                 Shape childAlloc = getChildAllocation(childIndex, a);
                 if (childAlloc != null)
                   {
                     ret = child.modelToView(pos, childAlloc, bias);
@@ -387,25 +387,28 @@
    * model location.
    *
    * @param pos the model location for which to determine the child view index
    * @param b the bias to be applied to <code>pos</code>
    *
    * @return the index of the child view that represents the specified
    *         model location
    */
   public int getViewIndex(int pos, Position.Bias b)
   {
     if (b == Position.Bias.Backward && pos != 0)
       pos -= 1;
-    return getViewIndexAtPosition(pos);
+    int i = -1;
+    if (pos >= getStartOffset() && pos < getEndOffset())
+      i = getViewIndexAtPosition(pos);
+    return i;
   }
 
   /**
    * Returns <code>true</code> if the specified point lies before the
    * given <code>Rectangle</code>, <code>false</code> otherwise.
    *
    * &quot;Before&quot; is typically defined as being to the left or above.
    *
    * @param x the X coordinate of the point
    * @param y the Y coordinate of the point
    * @param r the rectangle to test the point against
    *
@@ -455,53 +458,50 @@
   /**
    * Returns the child <code>View</code> that contains the given model
    * position. The given <code>Rectangle</code> gives the parent's allocation
    * and is changed to the child's allocation on exit.
    *
    * @param pos the model position to query the child <code>View</code> for
    * @param a the parent allocation on entry and the child allocation on exit
    *
    * @return the child view at the given model position
    */
   protected View getViewAtPosition(int pos, Rectangle a)
   {
+    View view = null;
     int i = getViewIndexAtPosition(pos);
-    View view = children[i];
-    childAllocation(i, a);
+    if (i >= 0 && i < getViewCount() && a != null)
+      {
+        view = getView(i);
+        childAllocation(i, a);
+      }
     return view;
   }
 
   /**
    * Returns the index of the child <code>View</code> for the given model
    * position.
    *
    * @param pos the model position for whicht the child <code>View</code> is
    *        queried
    *
    * @return the index of the child <code>View</code> for the given model
    *         position
    */
   protected int getViewIndexAtPosition(int pos)
   {
-    int index = -1;
-    for (int i = 0; i < children.length; i++)
-      {
-        if (children[i].getStartOffset() <= pos
-            && children[i].getEndOffset() > pos)
-          {
-            index = i;
-            break;
-          }
-      }
-    return index;
+    // We have a 1:1 mapping of elements to views here, so we forward
+    // this to the element.
+    Element el = getElement();
+    return el.getElementIndex(pos);
   }
 
   /**
    * Returns the allocation that is given to this <code>CompositeView</code>
    * minus this <code>CompositeView</code>'s insets.
    *
    * Also this translates from an immutable allocation to a mutable allocation
    * that is typically reused and further narrowed, like in
    * [EMAIL PROTECTED] #childAllocation}.
    *
    * @param a the allocation given to this <code>CompositeView</code>
    *
Index: javax/swing/text/DefaultCaret.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/DefaultCaret.java,v
retrieving revision 1.44
diff -u -1 -2 -r1.44 DefaultCaret.java
--- javax/swing/text/DefaultCaret.java	26 Jul 2006 19:23:01 -0000	1.44
+++ javax/swing/text/DefaultCaret.java	7 Aug 2006 11:15:17 -0000
@@ -902,28 +902,28 @@
     // Make sure the dot has a sane position.
     dot = Math.min(dot, textComponent.getDocument().getLength());
     dot = Math.max(dot, 0);
 
     Rectangle rect = null;
 
     try
       {
         rect = textComponent.modelToView(dot);
       }
     catch (BadLocationException e)
       {
-    	AssertionError ae;
-    	ae = new AssertionError("Unexpected bad caret location: " + dot);
-    	ae.initCause(e);
-    	throw ae;
+        // Let's ignore that. This shouldn't really occur. But if it
+        // does (it seems that this happens when the model is mutating),
+        // it causes no real damage. Uncomment this for debugging.
+        // e.printStackTrace();
       }
 
     if (rect == null)
       return;
 
     // Check if paint has possibly been called directly, without a previous
     // call to damage(). In this case we need to do some cleanup first.
     if ((x != rect.x) || (y != rect.y))
       {
         repaint(); // Erase previous location of caret.
         x = rect.x;
         y = rect.y;
@@ -1139,28 +1139,28 @@
 
         // Draw the caret in the new position.
         visible = true;
 
         Rectangle area = null;
 	int dot = getDot();
         try
           {
             area = getComponent().modelToView(dot);
           }
         catch (BadLocationException e)
           {
-	    AssertionError ae;
-	    ae = new AssertionError("Unexpected bad caret location: " + dot);
-	    ae.initCause(e);
-	    throw ae;
+            // Let's ignore that. This shouldn't really occur. But if it
+            // does (it seems that this happens when the model is mutating),
+            // it causes no real damage. Uncomment this for debugging.
+            // e.printStackTrace();
           }
         if (area != null)
           damage(area);
       }
     repaint();
   }  
 
   /**
    * Returns <code>true</code> if this <code>Caret</code> is blinking,
    * and <code>false</code> if not. The returned value is independent of
    * the visiblity of this <code>Caret</code> as returned by [EMAIL PROTECTED] #isVisible()}.
    *
Index: javax/swing/text/FlowView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/FlowView.java,v
retrieving revision 1.14
diff -u -1 -2 -r1.14 FlowView.java
--- javax/swing/text/FlowView.java	5 Aug 2006 12:13:21 -0000	1.14
+++ javax/swing/text/FlowView.java	7 Aug 2006 11:15:17 -0000
@@ -515,39 +515,41 @@
 
   /**
    * Receice notification that some content has been removed from the region
    * that this view is responsible for. This calls
    * [EMAIL PROTECTED] FlowStrategy#removeUpdate}.
    *
    * @param changes the document event describing the changes
    * @param a the current allocation of the view
    * @param vf the view factory that is used for creating new child views
    */
   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
   {
+    layoutPool.removeUpdate(changes, a, vf);
     strategy.removeUpdate(this, changes, getInsideAllocation(a));
     layoutDirty = true;
   }
 
   /**
    * Receice notification that some attributes changed in the region
    * that this view is responsible for. This calls
    * [EMAIL PROTECTED] FlowStrategy#changedUpdate}.
    *
    * @param changes the document event describing the changes
    * @param a the current allocation of the view
    * @param vf the view factory that is used for creating new child views
    */
   public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
   {
+    layoutPool.changedUpdate(changes, a, vf);
     strategy.changedUpdate(this, changes, getInsideAllocation(a));
     layoutDirty = true;
   }
 
   /**
    * Returns the index of the child <code>View</code> for the given model
    * position.
    *
    * This is implemented to iterate over the children of this
    * view (the rows) and return the index of the first view that contains
    * the given position.
    *
Index: javax/swing/text/ParagraphView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/ParagraphView.java,v
retrieving revision 1.8
diff -u -1 -2 -r1.8 ParagraphView.java
--- javax/swing/text/ParagraphView.java	5 Aug 2006 12:13:21 -0000	1.8
+++ javax/swing/text/ParagraphView.java	7 Aug 2006 11:15:17 -0000
@@ -78,24 +78,44 @@
      * Allows rows to span the whole parent view.
      */
     public float getMaximumSpan(int axis)
     {
       float max;
       if (axis == X_AXIS)
         max = Float.MAX_VALUE;
       else
         max = super.getMaximumSpan(axis);
       return max;
     }
 
+    /**
+     * Overridden because child views are not necessarily laid out in model
+     * order.
+     */
+    protected int getViewIndexAtPosition(int pos)
+    {
+      int index = -1;
+      if (pos >= getStartOffset() && pos < getEndOffset())
+        {
+          int nviews = getViewCount();
+          for (int i = 0; i < nviews && index == -1; i++)
+            {
+              View child = getView(i);
+              if (pos >= child.getStartOffset() && pos < child.getEndOffset())
+                index = i;
+            }
+        }
+      return index;
+    }
+
     protected void loadChildren(ViewFactory vf)
     {
       // Do nothing here. The children are added while layouting.
     }
   }
 
   /**
    * The indentation of the first line of the paragraph.
    */
   protected int firstLineIndent;
 
   /**
Index: javax/swing/text/View.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/View.java,v
retrieving revision 1.33
diff -u -1 -2 -r1.33 View.java
--- javax/swing/text/View.java	5 Aug 2006 14:14:11 -0000	1.33
+++ javax/swing/text/View.java	7 Aug 2006 11:15:19 -0000
@@ -392,25 +392,28 @@
    * repair its layout, reschedule layout or do nothing at all.</li>
    * </ul>
    *
    * @param ev the DocumentEvent that describes the change
    * @param shape the shape of the view
    * @param vf the ViewFactory for creating child views
    */
   public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
   {
     Element el = getElement();
     DocumentEvent.ElementChange ec = ev.getChange(el);
     if (ec != null)
-        updateChildren(ec, ev, vf);
+      {
+        if (! updateChildren(ec, ev, vf))
+          ec = null;
+      }
     forwardUpdate(ec, ev, shape, vf);
     updateLayout(ec, ev, shape);
   }
 
   /**
    * Receive notification about a change update to the text model.
    *
    * The default implementation of this method does the following:
    * <ul>
    * <li>Call [EMAIL PROTECTED] #updateChildren} if the element that this view is
    * responsible for has changed. This makes sure that the children can
    * correctly represent the model.<li>
@@ -484,45 +487,84 @@
    * @param ev the DocumentEvent describing the changes to the model
    * @param shape the current allocation of the view
    * @param vf the ViewFactory used to create new Views
    *
    * @since 1.3
    */
   protected void forwardUpdate(DocumentEvent.ElementChange ec,
                                DocumentEvent ev, Shape shape, ViewFactory vf)
   {
     int count = getViewCount();
     if (count > 0)
       {
+        // Determine start index.
         int startOffset = ev.getOffset();
-        int endOffset = startOffset + ev.getLength();
         int startIndex = getViewIndex(startOffset, Position.Bias.Backward);
-        int endIndex = getViewIndex(endOffset, Position.Bias.Forward);
-        int index = -1;
-        int addLength = -1;
-        if (ec != null)
+
+        // For REMOVE events we have to forward the event to the last element,
+        // for the case that an Element has been removed that represente
+        // the offset.
+        if (startIndex == -1 && ev.getType() == DocumentEvent.EventType.REMOVE
+            && startOffset >= getEndOffset())
+          {
+            startIndex = getViewCount() - 1;
+          }
+
+        // When startIndex is on a view boundary, forward event to the
+        // previous view too.
+        if (startIndex >= 0)
+          {
+            View v = getView(startIndex);
+            if (v != null)
+              {
+                if (v.getStartOffset() == startOffset && startOffset > 0)
+                  startIndex = Math.max(0, startIndex - 1);
+              }
+          }
+        startIndex = Math.max(0, startIndex);
+
+        // Determine end index.
+        int endIndex = startIndex;
+        if (ev.getType() != DocumentEvent.EventType.REMOVE)
+          {
+            endIndex = getViewIndex(startOffset + ev.getLength(),
+                                    Position.Bias.Forward);
+            if (endIndex < 0)
+              endIndex = getViewCount() - 1;
+          }
+
+        // Determine hole that comes from added elements (we don't forward
+        // the event to newly added views.
+        int startAdded = endIndex + 1;
+        int endAdded = startAdded;
+        Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
+        if (added != null && added.length > 0)
           {
-            index = ec.getIndex();
-            addLength = ec.getChildrenAdded().length;
+            startAdded = ec.getIndex();
+            endAdded = startAdded + added.length - 1;
           }
 
-        if (startIndex >= 0 && endIndex >= 0)
+        // Forward event to all views between startIndex and endIndex,
+        // and leave out all views in the hole.
+        for (int i = startIndex; i <= endIndex; i++)
           {
-            for (int i = startIndex; i <= endIndex; i++)
+            // Skip newly added child views.
+            if (! (i >= startAdded && i <= endAdded))
               {
-                // Skip newly added child views.
-                if (index >= 0 && i >= index && i < (index+addLength))
-                  continue;
                 View child = getView(i);
-                forwardUpdateToView(child, ev, shape, vf);
+                if (child != null)
+                  {
+                    Shape childAlloc = getChildAllocation(i, shape);
+                    forwardUpdateToView(child, ev, childAlloc, vf);
+                  }
               }
           }
       }
   }
 
   /**
    * Forwards an update event to the given child view. This calls
    * [EMAIL PROTECTED] #insertUpdate}, [EMAIL PROTECTED] #removeUpdate} or [EMAIL PROTECTED] #changedUpdate},
    * depending on the type of document event.
    *
    * @param view the View to forward the event to
    * @param ev the DocumentEvent to forward

Reply via email to