This adds support for relative column widths (percent width attribute
values) for HTML tables. It also fixes a whole bunch of smaller layout
problems. Many tables should now be rendered reasonable well, like the
following:

http://kennke.org/~roman/google2.png

2006-11-10  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/text/ParagraphView.java
        (Row.getMaximumSize): Removed. This method is not necessary.
        * javax/swing/text/html/TableView.java
        (CellView): Moved attribute init to
setPropertiesFromAttributes().
        (setPropertiesFromAttributes): Fetch attributes here.
        (RowView.RowView): Documented.
        (RowView.getMaximumSpan): Overridden to restrict the max span
        in the Y direction.
        (RowView.layoutMajorAxis): Correctly layout the spans.
        (columnWidths): New field. Stores the width attributes of
        the columns.
        (calculateColumnRequirements): Added support for relative
         (== percent) width attributes.
        (calculateMajorAxisRequirements): Removed.
        (calculateMinorAxisRequirements): Removed unnecessary code.
        (getMaximumSpan): Overridden to restrict the table's width.
        (layoutColumns): Documented. Implement more clever table layout,
        i.e. for relative columns etc.
        (layoutMinorAxis): Don't mark rows invalid.
        (updateGrid): Added docs. Initialize column widths.

Cheers, Roman

Index: javax/swing/text/ParagraphView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/ParagraphView.java,v
retrieving revision 1.13
diff -u -1 -5 -r1.13 ParagraphView.java
--- javax/swing/text/ParagraphView.java	12 Oct 2006 15:12:56 -0000	1.13
+++ javax/swing/text/ParagraphView.java	10 Nov 2006 14:46:28 -0000
@@ -26,30 +26,33 @@
 As a special exception, the copyright holders of this library give you
 permission to link this library with independent modules to produce an
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package javax.swing.text;
 
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Rectangle;
 import java.awt.Shape;
 
 import javax.swing.SizeRequirements;
 import javax.swing.event.DocumentEvent;
 
 /**
  * A [EMAIL PROTECTED] FlowView} that flows it's children horizontally and boxes the rows
  * vertically.
  *
  * @author Roman Kennke ([EMAIL PROTECTED])
  */
 public class ParagraphView extends FlowView implements TabExpander
 {
   /**
    * A specialized horizontal <code>BoxView</code> that represents exactly
@@ -92,43 +95,30 @@
             break;
           case StyleConstants.ALIGN_CENTER:
           case StyleConstants.ALIGN_JUSTIFIED:
             align = 0.5F;
             break;
           case StyleConstants.ALIGN_LEFT:
           default:
             align = 0.0F;
           }
       else
         align = super.getAlignment(axis);
       return align;
     }
 
     /**
-     * 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;
             }
Index: javax/swing/text/html/TableView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/html/TableView.java,v
retrieving revision 1.3
diff -u -1 -5 -r1.3 TableView.java
--- javax/swing/text/html/TableView.java	8 Nov 2006 11:09:31 -0000	1.3
+++ javax/swing/text/html/TableView.java	10 Nov 2006 14:46:28 -0000
@@ -51,115 +51,143 @@
 /**
  * A view implementation that renders HTML tables.
  *
  * This is basically a vertical BoxView that contains the rows of the table
  * and the rows are horizontal BoxViews that contain the actual columns.
  */
 class TableView
   extends BoxView
   implements ViewFactory
 {
 
   /**
    * Represents a single table row.
    */
   class RowView
-    extends BoxView
+    extends BlockView
   {
+    /**
+     * Creates a new RowView.
+     *
+     * @param el the element for the row view
+     */
     RowView(Element el)
     {
       super(el, X_AXIS);
     }
 
     /**
+     * Overridden to make rows not resizable along the Y axis.
+     */
+    public float getMaximumSpan(int axis)
+    {
+      float span;
+      if (axis == Y_AXIS)
+        span = super.getPreferredSpan(axis);
+      else
+        span = super.getMaximumSpan(axis);
+      return span;
+    }
+
+    /**
      * Calculates the overall size requirements for the row along the
      * major axis. This will be the sum of the column requirements.
      */
     protected SizeRequirements calculateMajorAxisRequirements(int axis,
                                                             SizeRequirements r)
     {
       if (r == null)
         r = new SizeRequirements();
       r.minimum = totalColumnRequirements.minimum;
       r.preferred = totalColumnRequirements.preferred;
       r.maximum = totalColumnRequirements.maximum;
       r.alignment = 0.0F;
       return r;
     }
 
     /**
      * Lays out the columns in this row.
      */
     protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
                                    int spans[])
     {
       int numCols = offsets.length;
       int realColumn = 0;
       for (int i = 0; i < numCols; i++)
         {
           View v = getView(i);
           if (v instanceof CellView)
             {
               CellView cv = (CellView) v;
+              offsets[i] = columnOffsets[realColumn];
+              spans[i] = 0;
               for (int j = 0; j < cv.colSpan; j++, realColumn++)
                 {
-                  offsets[i] = columnOffsets[realColumn];
-                  spans[i] = columnSpans[realColumn];
+                  spans[i] += columnSpans[realColumn];
                 }
             }
         }
     }
   }
 
   /**
    * A view that renders HTML table cells (TD and TH tags).
    */
   class CellView
-    extends BoxView
+    extends BlockView
   {
 
     /**
      * The number of columns that this view spans.
      */
     int colSpan;
 
     /**
      * Creates a new CellView for the specified element.
      *
      * @param el the element for which to create the colspan
      */
     CellView(Element el)
     {
       super(el, Y_AXIS);
+    }
+
+    /**
+     * Overridden to fetch the columnSpan attibute.
+     */
+    protected void setPropertiesFromAttributes()
+    {
+      super.setPropertiesFromAttributes();
       colSpan = 1;
       AttributeSet atts = getAttributes();
       Object o = atts.getAttribute(HTML.Attribute.COLSPAN);
       if (o != null)
         {
           try
             {
               colSpan = Integer.parseInt(o.toString());
             }
           catch (NumberFormatException ex)
             {
               // Couldn't parse the colspan, assume 1.
               colSpan = 1;
             }
         }
     }
   }
 
+
   /**
    * The attributes of this view.
    */
   private AttributeSet attributes;
 
   /**
    * The column requirements.
    */
   private SizeRequirements[] columnRequirements;
 
   /**
    * The overall requirements across all columns.
    *
    * Package private to avoid accessor methods.
    */
@@ -168,30 +196,35 @@
   /**
    * The column layout, offsets.
    *
    * Package private to avoid accessor methods.
    */
   int[] columnOffsets;
 
   /**
    * The column layout, spans.
    *
    * Package private to avoid accessor methods.
    */
   int[] columnSpans;
 
   /**
+   * The widths of the columns that have been explicitly specified.
+   */
+  Length[] columnWidths;
+
+  /**
    * Indicates if the grid setup is ok.
    */
   private boolean gridValid;
 
   /**
    * Creates a new HTML table view for the specified element.
    *
    * @param el the element for the table view
    */
   public TableView(Element el)
   {
     super(el, Y_AXIS);
     totalColumnRequirements = new SizeRequirements();
   }
 
@@ -258,103 +291,100 @@
   private StyleSheet getStyleSheet()
   {
     HTMLDocument doc = (HTMLDocument) getDocument();
     return doc.getStyleSheet();
   }
 
   /**
    * Overridden to calculate the size requirements according to the
    * columns distribution.
    */
   protected SizeRequirements calculateMinorAxisRequirements(int axis,
                                                             SizeRequirements r)
   {
     updateGrid();
     calculateColumnRequirements();
-    if (r == null)
-      r = new SizeRequirements();
-    // The overall minor axis requirements is the sum of all the columns.
-    int numCols = columnRequirements.length;
-    long min = 0;
-    long pref = 0;
-    for (int i = 0; i < numCols; i++)
-      {
-        SizeRequirements col = columnRequirements[i];
-        min += col.minimum;
-        pref += col.preferred;
-      }
 
-    r.minimum = (int) min;
-    r.preferred = (int) pref;
-    r.maximum = r.preferred;
+    // Calculate the horizontal requirements according to the superclass.
+    // This will return the maximum of the row's widths.
+    r = super.calculateMinorAxisRequirements(axis, r);
 
     // Try to set the CSS width if it fits.
     AttributeSet atts = getAttributes();
     Length l = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
     if (l != null)
       {
         int width = (int) l.getValue();
         if (r.minimum < width)
           r.minimum = width;
       }
 
     // Apply the alignment.
     Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN);
     r.alignment = 0.0F;
     if (o != null)
       {
         String al = o.toString();
         if (al.equals("left"))
           r.alignment = 0.0F;
         else if (al.equals("center"))
           r.alignment = 0.5F;
         else if (al.equals("right"))
           r.alignment = 1.0F;
       }
 
-    totalColumnRequirements.minimum = r.minimum;
-    totalColumnRequirements.preferred = r.preferred;
-    totalColumnRequirements.maximum = r.maximum;
-
     return r;
   }
 
+  /**
+   * Overridden to perform the table layout before calling the super
+   * implementation.
+   */
   protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, 
                                  int[] spans)
   {
     updateGrid();
-
-    // Mark all the rows invalid.
-    int count = getViewCount();
-    for (int i = 0; i < count; i++)
-      ((RowView) getView(i)).layoutChanged(axis);
-    // Layout columns.
     layoutColumns(targetSpan);
- 
     super.layoutMinorAxis(targetSpan, axis, offsets, spans);
   }
 
+  /**
+   * Calculates the size requirements for the columns.
+   */
   private void calculateColumnRequirements()
   {
     int numRows = getViewCount();
+    totalColumnRequirements.minimum = 0;
+    totalColumnRequirements.preferred = 0;
+    totalColumnRequirements.maximum = 0;
+
+    // In this first pass we find out a suitable total width to fit in
+    // all columns of all rows.
     for (int r = 0; r < numRows; r++)
       {
         RowView rowView = (RowView) getView(r);
         int numCols = rowView.getViewCount();
+
+        // We collect the normal (non-relative) column requirements in the
+        // total variable and the relative requirements in the relTotal
+        // variable. In the end we create the maximum of both to get the
+        // real requirements.
+        SizeRequirements total = new SizeRequirements();
+        SizeRequirements relTotal = new SizeRequirements();
+        float totalPercent = 0.F;
         for (int c = 0; c < numCols; )
           {
-            // TODO: Handle column span > 0.
             View v = rowView.getView(c);
             if (v instanceof CellView)
               {
                 CellView cellView = (CellView) v;
                 int colSpan = cellView.colSpan;
                 if (colSpan > 1)
                   {
                     int cellMin = (int) cellView.getMinimumSpan(X_AXIS);
                     int cellPref = (int) cellView.getPreferredSpan(X_AXIS);
                     int cellMax = (int) cellView.getMaximumSpan(X_AXIS);
                     int currentMin = 0;
                     int currentPref = 0;
                     long currentMax = 0;
                     for (int i = 0; i < colSpan; i++)
                       {
@@ -364,73 +394,252 @@
                         currentMax += req.maximum;
                       }
                     int deltaMin = cellMin - currentMin;
                     int deltaPref = cellPref - currentPref;
                     int deltaMax = (int) (cellMax - currentMax);
                     // Distribute delta.
                     for (int i = 0; i < colSpan; i++)
                       {
                         SizeRequirements req = columnRequirements[c + i];
                         if (deltaMin > 0)
                           req.minimum += deltaMin / colSpan;
                         if (deltaPref > 0)
                           req.preferred += deltaPref / colSpan;
                         if (deltaMax > 0)
                           req.maximum += deltaMax / colSpan;
+                        if (columnWidths[c + i] == null
+                            || ! columnWidths[c + i].isPercentage())
+                          {
+                            total.minimum += req.minimum;
+                            total.preferred += req.preferred;
+                            total.maximum += req.maximum;
+                          }
+                        else
+                          {
+                            relTotal.minimum =
+                              Math.max(relTotal.minimum,
+                                     (int) (req.minimum
+                                            * columnWidths[c + i].getValue()));
+                            relTotal.preferred =
+                              Math.max(relTotal.preferred,
+                                     (int) (req.preferred
+                                            * columnWidths[c + i].getValue()));
+                            relTotal.maximum =
+                              Math.max(relTotal.maximum,
+                                     (int) (req.maximum
+                                            * columnWidths[c + i].getValue()));
+                            totalPercent += columnWidths[c + i].getValue();
+                          }
                       }
                   }
                 else
                   {
                     // Shortcut for colSpan == 1.
                     SizeRequirements req = columnRequirements[c];
                     req.minimum = Math.max(req.minimum,
-                                        (int) cellView.getMinimumSpan(X_AXIS));
+                                           (int) cellView.getMinimumSpan(X_AXIS));
                     req.preferred = Math.max(req.preferred,
-                                      (int) cellView.getPreferredSpan(X_AXIS));
+                                             (int) cellView.getPreferredSpan(X_AXIS));
                     req.maximum = Math.max(req.maximum,
-                                        (int) cellView.getMaximumSpan(X_AXIS));
+                                           (int) cellView.getMaximumSpan(X_AXIS));
+                    if (columnWidths[c] == null
+                        || ! columnWidths[c].isPercentage())
+                      {
+                        total.minimum += columnRequirements[c].minimum;
+                        total.preferred += columnRequirements[c].preferred;
+                        total.maximum += columnRequirements[c].maximum;
+                      }
+                    else
+                      {
+                        relTotal.minimum =
+                          Math.max(relTotal.minimum,
+                                 (int) (req.minimum
+                                        / columnWidths[c].getValue()));
+                        relTotal.preferred =
+                          Math.max(relTotal.preferred,
+                                 (int) (req.preferred
+                                        / columnWidths[c].getValue()));
+                        relTotal.maximum =
+                          Math.max(relTotal.maximum,
+                                 (int) (req.maximum
+                                        / columnWidths[c].getValue()));
+                        totalPercent += columnWidths[c].getValue();
+                      }
                   }
                 c += colSpan;
               }
           }
+
+        // Update the total requirements as follows:
+        // 1. Multiply the absolute requirements with 1 - totalPercent. This
+        //    gives the total requirements based on the wishes of the absolute
+        //    cells.
+        // 2. Take the maximum of this value and the total relative
+        //    requirements. Now we should have enough space for whatever cell
+        //    in this column.
+        // 3. Take the maximum of this value and the previous maximum value.
+        total.minimum *= 1.F / (1.F - totalPercent);
+        total.preferred *= 1.F / (1.F - totalPercent);
+        total.maximum *= 1.F / (1.F - totalPercent);
+
+        int rowTotalMin = Math.max(total.minimum, relTotal.minimum);
+        int rowTotalPref = Math.max(total.preferred, relTotal.preferred);
+        int rowTotalMax = Math.max(total.maximum, relTotal.maximum);
+        totalColumnRequirements.minimum =
+          Math.max(totalColumnRequirements.minimum, rowTotalMin);
+        totalColumnRequirements.preferred =
+          Math.max(totalColumnRequirements.preferred, rowTotalPref);
+        totalColumnRequirements.maximum =
+          Math.max(totalColumnRequirements.maximum, rowTotalMax);
+      }
+
+    // Now we know what we want and can fix up the actual relative
+    // column requirements.
+    int numCols = columnRequirements.length;
+    for (int i = 0; i < numCols; i++)
+      {
+        if (columnWidths[i] != null)
+          {
+            columnRequirements[i].minimum = (int)
+              columnWidths[i].getValue(totalColumnRequirements.minimum);
+            columnRequirements[i].preferred = (int)
+              columnWidths[i].getValue(totalColumnRequirements.preferred);
+            columnRequirements[i].maximum = (int)
+              columnWidths[i].getValue(totalColumnRequirements.maximum);
+          }
       }
   }
 
+  /**
+   * Lays out the columns.
+   *
+   * @param targetSpan the target span into which the table is laid out
+   */
   private void layoutColumns(int targetSpan)
   {
-    // TODO: Could be done better I suppose.
-    SizeRequirements.calculateTiledPositions(targetSpan,
-                                             totalColumnRequirements,
-                                             columnRequirements, columnOffsets,
-                                             columnSpans);
+    // Set the spans to the preferred sizes. Determine the space
+    // that we have to adjust the sizes afterwards.
+    long sumPref = 0;
+    int n = columnRequirements.length;
+    for (int i = 0; i < n; i++)
+      {
+        SizeRequirements col = columnRequirements[i];
+        if (columnWidths[i] != null)
+          columnSpans[i] = (int) columnWidths[i].getValue(targetSpan);
+        else
+          columnSpans[i] = col.preferred;
+        sumPref += columnSpans[i];
+      }
+
+    // Try to adjust the spans so that we fill the targetSpan.
+    long diff = targetSpan - sumPref;
+    float factor = 0.0F;
+    int[] diffs = null;
+    if (diff != 0)
+      {
+        long total = 0;
+        diffs = new int[n];
+        for (int i = 0; i < n; i++)
+          {
+            // Only adjust the width if we haven't set a column width here.
+            if (columnWidths[i] == null)
+              {
+                SizeRequirements col = columnRequirements[i];
+                int span;
+                if (diff < 0)
+                  {
+                    span = col.minimum;
+                    diffs[i] = columnSpans[i] - span;
+                  }
+                else
+                  {
+                    span = col.maximum;
+                    diffs[i] = span - columnSpans[i];
+                  }
+                total += span;
+              }
+            else
+              total += columnSpans[i];
+          }
+
+        float maxAdjust = Math.abs(total - sumPref);
+        factor = diff / maxAdjust;
+        factor = Math.min(factor, 1.0F);
+        factor = Math.max(factor, -1.0F);
+      }
+
+    // Actually perform adjustments.
+    int totalOffs = 0;
+    for (int i = 0; i < n; i++)
+      {
+        columnOffsets[i] = totalOffs;
+        if (diff != 0)
+          {
+            float adjust = factor * diffs[i];
+            columnSpans[i] += Math.round(adjust);
+          }
+        // Avoid overflow here.
+        totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i],
+                                    Integer.MAX_VALUE);
+      }
   }
 
+  /**
+   * Updates the arrays that contain the row and column data in response
+   * to a change to the table structure.
+   */
   private void updateGrid()
   {
     if (! gridValid)
       {
         int maxColumns = 0;
         int numRows = getViewCount();
         for (int r = 0; r < numRows; r++)
           {
             RowView rowView = (RowView) getView(r);
             int numCols = rowView.getViewCount();
             maxColumns = Math.max(numCols, maxColumns);
           }
+        columnWidths = new Length[maxColumns];
+        for (int r = 0; r < numRows; r++)
+          {
+            RowView rowView = (RowView) getView(r);
+            int numCols = rowView.getViewCount();
+            int colIndex = 0;
+            for (int c = 0; c < numCols; c++)
+              {
+                View v = rowView.getView(c);
+                if (v instanceof CellView)
+                  {
+                    CellView cv = (CellView) v;
+                    Object o =
+                      cv.getAttributes().getAttribute(CSS.Attribute.WIDTH);
+                    if (o != null && columnWidths[colIndex] == null
+                        && o instanceof Length)
+                      columnWidths[colIndex]= (Length) o;
+                    colIndex += cv.colSpan;
+                  }
+              }
+          }
         columnRequirements = new SizeRequirements[maxColumns];
         for (int i = 0; i < maxColumns; i++)
           columnRequirements[i] = new SizeRequirements();
         columnOffsets = new int[maxColumns];
         columnSpans = new int[maxColumns];
 
         gridValid = true;
       }
   }
 
-  protected SizeRequirements calculateMajorAxisRequirements(int axis,
-                                                            SizeRequirements r)
+  /**
+   * Overridden to restrict the table width to the preferred size.
+   */
+  public float getMaximumSpan(int axis)
   {
-    r = super.calculateMajorAxisRequirements(axis, r);
-    r.maximum = r.preferred;
-    return r;
+    float span;
+    if (axis == X_AXIS)
+      span = super.getPreferredSpan(axis);
+    else
+      span = super.getMaximumSpan(axis);
+    return span;
   }
 }

Reply via email to