Hi

This is what I've got so far with my my baseline implementation - how
does it look?

Basic architecture
- VerticalAlignment gets a new constant BASELINE
- ConstrainedVisual has a new method
     public int getBaseline(int width);
  I had to put the method at a higher level than Component because I
also need to call it on
  Skin and Renderer instances. 
- ComponentSkin has a static flag for debugging support for baselines,
  which draws red lines on the components to make it easier to see where
the problems are.
  To turn it on, pass
    -Dpivot.wtk.debugBaseline=true
  on the command line
 
TODO
- I'm unsure how best to calculate a baseline for TextAreaSkin
- TablePaneSkin still needs a getBaseline() implementation
- FlowPaneSkin - It is possible to end up with illegal combinations e.g.
  verticalAlignment==BASELINE && orientation==VERTICAL. But how to flag
these kinds of errors?
 
 
Regards, Noel.
Index: wtk/src/pivot/wtk/Component.java
===================================================================
--- wtk/src/pivot/wtk/Component.java    (revision 782639)
+++ wtk/src/pivot/wtk/Component.java    (working copy)
@@ -1164,6 +1164,16 @@
     }
 
     /**
+     * Returns the component's baseline for a given width.
+     * 
+     * @return The component's baseline relative to the origin of the parent
+     *         container. -1 indicates that no baseline exists.
+     */
+    public int getBaseline(int width) {
+        return skin.getBaseline(width);
+    }
+
+    /**
      * Returns the component's bounding area.
      *
      * @return
Index: wtk/src/pivot/wtk/ConstrainedVisual.java
===================================================================
--- wtk/src/pivot/wtk/ConstrainedVisual.java    (revision 782639)
+++ wtk/src/pivot/wtk/ConstrainedVisual.java    (working copy)
@@ -49,7 +49,15 @@
      * for no constraint.
      */
     public int getPreferredHeight(int width);
-
+    
+    /**
+     * Returns the baseline for a given width.
+     * 
+     * @return The baseline relative to the origin of the parent
+     *         container. -1 indicates that no baseline exists.
+     */
+    public int getBaseline(int width);
+    
     /**
      * Returns the visual's unconstrained preferred size.
      */
Index: wtk/src/pivot/wtk/VerticalAlignment.java
===================================================================
--- wtk/src/pivot/wtk/VerticalAlignment.java    (revision 782639)
+++ wtk/src/pivot/wtk/VerticalAlignment.java    (working copy)
@@ -27,6 +27,11 @@
     CENTER,
 
     /**
+     * align the components along their baselines
+     */
+    BASELINE,
+    
+    /**
      * Stretch the contents to fill the available space.
      */
     JUSTIFY;
Index: wtk/src/pivot/wtk/content/TableViewMultiCellRenderer.java
===================================================================
--- wtk/src/pivot/wtk/content/TableViewMultiCellRenderer.java   (revision 
782639)
+++ wtk/src/pivot/wtk/content/TableViewMultiCellRenderer.java   (working copy)
@@ -286,6 +286,20 @@
 
         return preferredHeight;
     }
+    
+    public int getBaseline(int width) {
+        // Our baseline is the maximum of all our possible renderers'
+        // baseline
+        int baseline = defaultRenderer.getBaseline(width);
+
+        for (Class<?> key : cellRenderers) {
+            TableView.CellRenderer renderer = cellRenderers.get(key);
+            baseline = Math.max(baseline,
+                renderer.getBaseline(width));
+        }
+
+        return baseline;
+    }
 
     public Dimensions getPreferredSize() {
         return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
Index: wtk/src/pivot/wtk/skin/CardPaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/CardPaneSkin.java    (revision 782639)
+++ wtk/src/pivot/wtk/skin/CardPaneSkin.java    (working copy)
@@ -244,6 +244,27 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        int baseline = -1;
+
+        CardPane cardPane = (CardPane)getComponent();
+
+        if (sizeToSelection) {
+            Component selectedCard = cardPane.getSelectedCard();
+
+            if (selectedCard != null) {
+                baseline = selectedCard.getBaseline(width);
+            }
+        } else {
+            for (Component card : cardPane) {
+                baseline = Math.max(baseline, card.getBaseline(width));
+            }
+        }
+
+        return baseline;
+    }
 
     public Dimensions getPreferredSize() {
         Dimensions preferredSize;
Index: wtk/src/pivot/wtk/skin/ComponentSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/ComponentSkin.java   (revision 782639)
+++ wtk/src/pivot/wtk/skin/ComponentSkin.java   (working copy)
@@ -16,7 +16,11 @@
  */
 package pivot.wtk.skin;
 
+import java.awt.Color;
+import java.awt.Graphics2D;
+
 import pivot.wtk.ApplicationContext;
+import pivot.wtk.Bounds;
 import pivot.wtk.Component;
 import pivot.wtk.ComponentKeyListener;
 import pivot.wtk.ComponentLayoutListener;
@@ -29,9 +33,10 @@
 import pivot.wtk.Cursor;
 import pivot.wtk.Dimensions;
 import pivot.wtk.Direction;
+import pivot.wtk.GraphicsUtilities;
 import pivot.wtk.Keyboard;
 import pivot.wtk.Mouse;
-import pivot.wtk.Bounds;
+import pivot.wtk.Orientation;
 import pivot.wtk.Point;
 import pivot.wtk.Skin;
 import pivot.wtk.Tooltip;
@@ -73,6 +78,16 @@
 
     public static final int SHOW_TOOLTIP_TIMEOUT = 1000;
 
+    /** if true, draw red lines over components to indicate where the 
baselines are */
+    protected static boolean debugBaseline = false; 
+    static {
+        try {
+            debugBaseline = 
Boolean.parseBoolean(System.getProperty("pivot.wtk.debugBaseline"));
+        } catch (Exception ex) {
+            // ignore exception when running in applet
+        }
+    }
+    
     public int getWidth() {
         return width;
     }
@@ -81,6 +96,11 @@
         return height;
     }
 
+    
+    public int getBaseline(int width) {
+       return -1;
+    }
+    
     public void setSize(int width, int height) {
         this.width = width;
         this.height = height;
@@ -289,4 +309,13 @@
             component.repaint(x, y, width, height);
         }
     }
+
+    protected final void drawBaselineDebug(Graphics2D graphics) {
+        int width = getWidth();
+        int baseline = getBaseline(width);
+        if (baseline != -1) {
+            graphics.setPaint(Color.RED);
+            GraphicsUtilities.drawLine(graphics, 0, baseline, width, 
Orientation.HORIZONTAL);
+        }
+    }
 }
Index: wtk/src/pivot/wtk/skin/FlowPaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/FlowPaneSkin.java    (revision 782639)
+++ wtk/src/pivot/wtk/skin/FlowPaneSkin.java    (working copy)
@@ -16,6 +16,8 @@
  */
 package pivot.wtk.skin;
 
+import java.awt.Graphics2D;
+
 import pivot.collections.Dictionary;
 import pivot.wtk.Component;
 import pivot.wtk.Dimensions;
@@ -180,9 +182,11 @@
             // Preferred height is the maximum preferred height of all
             // components, plus padding
             int maxComponentHeight = 0;
+            int maxComponentBaseline = 0;
+            int maxComponentBelowBaseline = 0;
 
             // Determine the fixed and total preferred widths, if necessary
-            int totalSpacing = 0;
+            int totalSpacingWidth = 0;
             int totalPreferredWidth = 0;
 
             if (horizontalAlignment == HorizontalAlignment.JUSTIFY
@@ -199,7 +203,7 @@
                 }
 
                 if (displayableComponentCount > 1) {
-                    totalSpacing = spacing * (displayableComponentCount - 1);
+                    totalSpacingWidth = spacing * (displayableComponentCount - 
1);
                 }
             }
 
@@ -213,22 +217,51 @@
                         && width != -1) {
                         int preferredWidth = component.getPreferredWidth(-1);
 
-                        if (width > totalSpacing
-                            && preferredWidth > totalSpacing) {
+                        if (width > totalSpacingWidth
+                            && preferredWidth > totalSpacingWidth) {
                             double widthScale = (double)preferredWidth
                                 / (double)totalPreferredWidth;
 
                             componentWidth = (int)Math.round((double)(width
-                                - totalSpacing) * widthScale);
+                                - totalSpacingWidth) * widthScale);
                         }
                     }
 
+                    int componentPreferredHeight = component
+                            .getPreferredHeight(componentWidth);
                     maxComponentHeight = Math.max(maxComponentHeight,
-                        component.getPreferredHeight(componentWidth));
+                            componentPreferredHeight);
+
+                    if (verticalAlignment == VerticalAlignment.BASELINE) {
+                        int componentBaseline = component
+                                .getBaseline(componentWidth);
+                        if (componentBaseline != -1) {
+                            maxComponentBaseline = Math.max(
+                                    maxComponentBaseline, componentBaseline);
+                            int componentHeightBelowBaseline = 
componentPreferredHeight
+                                    - componentBaseline;
+                            maxComponentBelowBaseline = Math.max(
+                                    maxComponentBelowBaseline,
+                                    componentHeightBelowBaseline);
+                        } else {
+                            // if no baseline exists, treat the bottom of the
+                            // component as baseline
+                            maxComponentBaseline = Math.max(
+                                    maxComponentBaseline,
+                                    componentPreferredHeight);
+                            maxComponentBelowBaseline = Math.max(
+                                    maxComponentBelowBaseline, 0);
+                        }
+                    }
                 }
             }
 
-            preferredHeight += maxComponentHeight;
+            if (verticalAlignment == VerticalAlignment.BASELINE) {
+                preferredHeight += maxComponentBaseline;
+                preferredHeight += maxComponentBelowBaseline;
+            } else {
+                preferredHeight += maxComponentHeight;
+            }
         }
 
         // Include top and bottom padding values
@@ -248,6 +281,8 @@
                 // Preferred width is the sum of the preferred widths of all
                 // components, plus spacing and padding
                 int displayableComponentCount = 0;
+                       int maxBaseline = 0;
+                       int maxBelowBaseline = 0;
 
                 for (int i = 0, n = flowPane.getLength(); i < n; i++) {
                     Component component = flowPane.get(i);
@@ -255,10 +290,35 @@
                     if (component.isDisplayable()) {
                         Dimensions preferredSize = 
component.getPreferredSize();
                         preferredWidth += preferredSize.width;
-                        preferredHeight = Math.max(preferredSize.height, 
preferredHeight);
-                        displayableComponentCount++;
+
+                    if (verticalAlignment == VerticalAlignment.BASELINE) {
+                        int componentBaseline = component
+                                .getBaseline(preferredSize.width);
+                        if (componentBaseline != -1) {
+                            maxBaseline = Math.max(maxBaseline, maxBaseline);
+                            int componentHeightBelowBaseline = 
preferredSize.height
+                                    - componentBaseline;
+                            maxBelowBaseline = Math.max(maxBelowBaseline,
+                                    componentHeightBelowBaseline);
+                        } else {
+                            // if no baseline exists, treat the bottom of the
+                            // component as baseline
+                            maxBaseline = Math.max(maxBaseline,
+                                    preferredSize.height);
+                            maxBelowBaseline = Math.max(maxBelowBaseline, 0);
+                        }
+                    } else {
+                        preferredHeight = Math.max(preferredSize.height,
+                                preferredHeight);
                     }
+
+                    displayableComponentCount++;
                 }
+            }
+
+            if (verticalAlignment == VerticalAlignment.BASELINE) {
+                preferredHeight = maxBaseline + maxBelowBaseline;
+            }
 
                 // Include spacing
                 if (displayableComponentCount > 1) {
@@ -309,7 +369,7 @@
 
         Orientation orientation = flowPane.getOrientation();
         if (orientation == Orientation.HORIZONTAL) {
-            int preferredWidth = getPreferredWidth(-1);
+            int preferredWidth = getPreferredWidth(height);
 
             // Determine the fixed width (used in scaling components
             // when justified horizontally)
@@ -348,7 +408,13 @@
             }
 
             componentX += padding.left;
-
+            
+            int baseline = 0;
+            // only compute baseline if we need it
+            if (verticalAlignment == VerticalAlignment.BASELINE) {
+                baseline = getBaseline(width);
+            }
+            
             // Lay out the components
             for (int i = 0; i < n; i++) {
                 Component component = flowPane.get(i);
@@ -389,8 +455,17 @@
                             componentHeight = preferredComponentSize.height;
                         }
                     }
-
+            
+                    // Determine the component y-coordinate
                     switch (verticalAlignment) {
+                           case BASELINE: {
+                             // adjust component Y so that all the baselines 
line up
+                           int componentBaseline = component
+                                 .getBaseline(componentWidth);
+                               componentY = padding.top + (baseline - 
componentBaseline);
+                             break;
+                       }
+            
                         case TOP: {
                             componentY = padding.top;
                             break;
@@ -423,7 +498,7 @@
                 }
             }
         } else {
-            int preferredHeight = getPreferredHeight(-1);
+            int preferredHeight = getPreferredHeight(width);
 
             // Determine the fixed height (used in scaling components
             // when justified vertically)
@@ -539,6 +614,103 @@
         }
     }
 
+    @Override
+    public int getBaseline(int width) {
+        int baseline = -1;
+
+        // Include padding in constraint
+        if (width != -1) {
+            width = Math.max(width - (padding.left + padding.right), 0);
+        }
+
+        FlowPane flowPane = (FlowPane) getComponent();
+        int n = flowPane.getLength();
+
+        Orientation orientation = flowPane.getOrientation();
+        if (orientation == Orientation.VERTICAL) {
+            // baseline is the baseline of the first component
+            for (int i = 0; i < n; i++) {
+                Component component = flowPane.get(i);
+
+                if (component.isDisplayable()) {
+                    baseline = component.getBaseline(width);
+                    break;
+                }
+            }
+        } else {
+            // baseline is the maximum baseline of all
+            // components, plus padding
+
+            // Determine the fixed and total preferred widths, if necessary
+            int totalSpacingWidth = 0;
+            int totalPreferredWidth = 0;
+
+            if (horizontalAlignment == HorizontalAlignment.JUSTIFY
+                    && width != -1) {
+                int displayableComponentCount = 0;
+
+                for (int i = 0; i < n; i++) {
+                    Component component = flowPane.get(i);
+
+                    if (component.isDisplayable()) {
+                        totalPreferredWidth += component.getPreferredWidth(-1);
+                        displayableComponentCount++;
+                    }
+                }
+
+                if (displayableComponentCount > 1) {
+                    totalSpacingWidth = spacing
+                            * (displayableComponentCount - 1);
+                }
+            }
+
+            for (int i = 0; i < n; i++) {
+                Component component = flowPane.get(i);
+
+                if (component.isDisplayable()) {
+                    int componentWidth = -1;
+
+                    if (horizontalAlignment == HorizontalAlignment.JUSTIFY
+                            && width != -1) {
+                        int preferredWidth = component.getPreferredWidth(-1);
+
+                        if (width > totalSpacingWidth
+                                && preferredWidth > totalSpacingWidth) {
+                            double widthScale = (double) preferredWidth
+                                    / (double) totalPreferredWidth;
+
+                            componentWidth = (int) Math
+                                    .round((double) (width - totalSpacingWidth)
+                                            * widthScale);
+                        }
+                    }
+
+                    int componentBaseline = component
+                            .getBaseline(componentWidth);
+                    if (componentBaseline != -1) {
+                        baseline = Math.max(baseline, componentBaseline);
+                    }
+                }
+            }
+
+        }
+
+        // Include top and bottom padding values
+        if (baseline != -1) {
+            baseline += padding.top;
+        }
+
+        return baseline;
+    }
+
+    @Override
+    public void paint(Graphics2D graphics) {
+        super.paint(graphics);
+        if (debugBaseline) {
+            drawBaselineDebug(graphics);
+        }
+    }
+
     public HorizontalAlignment getHorizontalAlignment() {
         return horizontalAlignment;
     }
@@ -564,6 +736,11 @@
         return verticalAlignment;
     }
 
+    /**
+     * @TODO It is possible to end up with impossible combinations e.g.
+     *       verticalAlignment==BASELINE && orientation==VERTICAL. But how to
+     *       flag these kinds of errors?
+     */
     public void setVerticalAlignment(VerticalAlignment verticalAlignment) {
         if (verticalAlignment == null) {
             throw new IllegalArgumentException("verticalAlignment is null.");
Index: wtk/src/pivot/wtk/skin/ImageViewSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/ImageViewSkin.java   (revision 782639)
+++ wtk/src/pivot/wtk/skin/ImageViewSkin.java   (working copy)
@@ -292,6 +292,12 @@
         if (verticalAlignment == null) {
             throw new IllegalArgumentException("verticalAlignment is null.");
         }
+        if (verticalAlignment != VerticalAlignment.JUSTIFY 
+                && verticalAlignment != VerticalAlignment.CENTER
+                && verticalAlignment != VerticalAlignment.BOTTOM
+                && verticalAlignment != VerticalAlignment.TOP) {
+            throw new IllegalStateException(verticalAlignment + " is not 
supported");
+        }
 
         this.verticalAlignment = verticalAlignment;
         layout();
Index: wtk/src/pivot/wtk/skin/LabelSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/LabelSkin.java       (revision 782639)
+++ wtk/src/pivot/wtk/skin/LabelSkin.java       (working copy)
@@ -182,6 +182,52 @@
     public void layout() {
         // No-op
     }
+    
+    @Override
+    public int getBaseline(int width) {
+      Label label = (Label)getComponent();
+       
+      /* calculate the baseline of the text */
+      int baseline = -1;
+
+      String text = label.getText();
+      if (text == null) {
+          text = "";
+      }
+
+      if (wrapText
+          && text.length() > 0) {
+          int contentWidth = label.getWidth() - (padding.left + padding.right);
+
+          AttributedString attributedText = new AttributedString(text);
+          attributedText.addAttribute(TextAttribute.FONT, font);
+
+          AttributedCharacterIterator aci = attributedText.getIterator();
+          LineBreakMeasurer lbm = new LineBreakMeasurer(aci, 
fontRenderContext);
+
+          if (lbm.getPosition() < aci.getEndIndex()) {
+              LineMetrics lm = font.getLineMetrics(text, fontRenderContext);
+              baseline = (int)Math.ceil(lm.getAscent()-2);
+          } else {
+              // for multi-line labels, treat the baseline as being the 
baseline of the first line of text
+              int offset = lbm.nextOffset(contentWidth);
+
+              LineMetrics lm = font.getLineMetrics(aci,
+                  lbm.getPosition(), offset, fontRenderContext);
+
+              baseline = (int) Math.ceil(lm.getAscent()-2);
+          }
+      } else {
+          LineMetrics lm = font.getLineMetrics(text, fontRenderContext);
+          baseline = (int)Math.ceil(lm.getAscent()-2);
+      }
+
+      if (baseline!=-1) {
+       baseline += padding.top;
+      }
+
+      return baseline;
+    }
 
     public void paint(Graphics2D graphics) {
         int width = getWidth();
@@ -190,6 +236,10 @@
         Label label = (Label)getComponent();
         String text = label.getText();
 
+        if (debugBaseline) {
+            drawBaselineDebug(graphics);
+        }
+        
         if (text != null
             && text.length() > 0) {
             if (fontRenderContext.isAntiAliased()) {
@@ -445,8 +495,10 @@
             throw new IllegalArgumentException("verticalAlignment is null.");
         }
 
-        if (verticalAlignment == VerticalAlignment.JUSTIFY) {
-            throw new IllegalArgumentException(VerticalAlignment.JUSTIFY + " 
is not supported");
+        if (verticalAlignment != VerticalAlignment.TOP
+                && verticalAlignment != VerticalAlignment.BOTTOM
+                && verticalAlignment != VerticalAlignment.CENTER) {
+            throw new IllegalArgumentException(verticalAlignment + " is not 
supported");
         }
 
         this.verticalAlignment = verticalAlignment;
Index: wtk/src/pivot/wtk/skin/StackPaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/StackPaneSkin.java   (revision 782639)
+++ wtk/src/pivot/wtk/skin/StackPaneSkin.java   (working copy)
@@ -50,6 +50,19 @@
         return preferredHeight;
     }
 
+    @Override
+    public int getBaseline(int width) {
+        int baseline = 0;
+        StackPane stackPane = (StackPane)getComponent();
+
+        for (Component component : stackPane) {
+            baseline = Math.max(baseline,
+                component.getBaseline(width));
+        }
+
+        return baseline;
+    }
+    
     public Dimensions getPreferredSize() {
         int preferredWidth = 0;
         int preferredHeight = 0;
Index: wtk/src/pivot/wtk/skin/TablePaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/TablePaneSkin.java   (revision 782639)
+++ wtk/src/pivot/wtk/skin/TablePaneSkin.java   (working copy)
@@ -38,6 +38,8 @@
 /**
  * Table pane skin.
  *
+ * TODO (NoelG) implement baseline alignment for rows
+ * 
  * @author tvolkert
  */
 public class TablePaneSkin extends ContainerSkin implements TablePane.Skin,
@@ -414,7 +416,7 @@
 
         return preferredHeight;
     }
-
+    
     @Override
     public Dimensions getPreferredSize() {
         // TODO Optimize by performing calculations here
Index: wtk/src/pivot/wtk/skin/TextAreaSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/TextAreaSkin.java    (revision 782639)
+++ wtk/src/pivot/wtk/skin/TextAreaSkin.java    (working copy)
@@ -72,6 +72,8 @@
 /**
  * Text area skin.
  *
+ * TODO (NoelG) override getBaseline() and calculate a real baseline
+ * 
  * @author gbrown
  */
 public class TextAreaSkin extends ComponentSkin implements TextArea.Skin,
Index: wtk/src/pivot/wtk/skin/terra/TerraCheckboxSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraCheckboxSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraCheckboxSkin.java (working copy)
@@ -114,6 +114,27 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        Checkbox checkbox = (Checkbox)getComponent();
+        Button.DataRenderer dataRenderer = checkbox.getDataRenderer();
+
+        int baseline = -1;
+
+        Object buttonData = checkbox.getButtonData();
+        if (buttonData != null) {
+            if (width != -1) {
+                width = Math.max(width - (CHECKBOX_SIZE + spacing), 0);
+            }
+
+            dataRenderer.render(checkbox.getButtonData(), checkbox, false);
+
+            baseline = dataRenderer.getBaseline(width);
+        }
+
+        return baseline;
+    }
 
     public Dimensions getPreferredSize() {
         Checkbox checkbox = (Checkbox)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraFormSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraFormSkin.java     (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraFormSkin.java     (working copy)
@@ -30,6 +30,7 @@
 import pivot.wtk.MessageType;
 import pivot.wtk.Separator;
 import pivot.wtk.Theme;
+import pivot.wtk.VerticalAlignment;
 import pivot.wtk.media.Image;
 import pivot.wtk.skin.ContainerSkin;
 
@@ -46,6 +47,7 @@
 
     private boolean rightAlignLabels = true;
     private HorizontalAlignment fieldAlignment = HorizontalAlignment.LEFT;
+    private VerticalAlignment verticalAlignment = VerticalAlignment.BASELINE;
     private int horizontalSpacing = 6;
     private int verticalSpacing = 6;
     private int flagImageOffset = 4;
@@ -176,8 +178,26 @@
                 if (field.isDisplayable()) {
                     Label label = labels.get(sectionIndex).get(fieldIndex);
 
-                    int preferredRowHeight = 
Math.max(label.getPreferredHeight(-1),
-                        Math.max(field.getPreferredHeight(fieldWidth), 
FLAG_IMAGE_SIZE));
+                    int preferredRowHeight = 0;
+                    if (verticalAlignment==VerticalAlignment.BASELINE) {
+                        int labelBaseLine = label.getBaseline(-1);
+                        int fieldBaseLine = field.getBaseline(-1);
+                        if (labelBaseLine!=-1 && fieldBaseLine!=-1) {
+                            int fieldPrefferedHeight = 
field.getPreferredHeight(fieldWidth);
+                            int labelPreferredHeight = 
label.getPreferredHeight(-1);
+                            int baseline = Math.max(labelBaseLine, 
fieldBaseLine);
+                            int belowBaseline = Math.max(fieldPrefferedHeight 
- fieldBaseLine, labelPreferredHeight - labelBaseLine);
+                            preferredRowHeight = Math.max(preferredRowHeight, 
baseline + belowBaseline);
+                        } else {
+                            // if they don't both have baselines, default to 
non-baseline behaviour
+                            preferredRowHeight = Math.max(preferredRowHeight, 
field.getPreferredHeight(fieldWidth));
+                            preferredRowHeight = Math.max(preferredRowHeight, 
label.getPreferredHeight(-1));
+                        }
+                    } else {
+                        preferredRowHeight = Math.max(preferredRowHeight, 
field.getPreferredHeight(fieldWidth));
+                        preferredRowHeight = Math.max(preferredRowHeight, 
label.getPreferredHeight(-1));
+                    }
+                    preferredRowHeight = Math.max(preferredRowHeight, 
FLAG_IMAGE_SIZE);
                     preferredHeight += preferredRowHeight;
 
                     if (fieldIndex > 0) {
@@ -243,7 +263,7 @@
                 separator.setLocation(0, rowY);
                 rowY += separator.getHeight();
             }
-
+            
             for (int fieldIndex = 0, fieldCount = section.getLength();
                 fieldIndex < fieldCount; fieldIndex++) {
                 Component field = section.get(fieldIndex);
@@ -274,9 +294,34 @@
                     int rowHeight = Math.max(label.getHeight(),
                         Math.max(field.getHeight(), FLAG_IMAGE_SIZE));
 
+                    int fieldY;
+                    int labelY;
+                    int flagImageY;
+                    if (verticalAlignment == VerticalAlignment.BASELINE) {
+                        int labelBaseLine = 
label.getBaseline(label.getWidth());
+                        int fieldBaseLine = field.getBaseline(fieldSize.width);
+                        if (labelBaseLine!=-1 && fieldBaseLine!=-1) {
+                            int baseline = Math.max(labelBaseLine, 
fieldBaseLine);
+                            labelY  = rowY + (baseline - labelBaseLine);
+                            fieldY = rowY + (baseline - fieldBaseLine);
+                            // make the bottom of the flag line up with 
baseline
+                            flagImageY = rowY + (flagImageView.getHeight() - 
baseline);
+                        } else {
+                            // if they don't both have baselines, default to 
non-baseline behaviour
+                            fieldY = rowY;
+                            labelY = rowY;
+                            flagImageY = fieldY + (rowHeight - 
flagImageView.getHeight()) / 2;
+                        }
+                    } else {
+                        fieldY = rowY;
+                        labelY = rowY;
+                        flagImageY = fieldY + (rowHeight - 
flagImageView.getHeight()) / 2;
+                    }
+
+
                     // Set the row component locations
                     int labelX = rightAlignLabels ? maximumLabelWidth - 
label.getWidth() : 0;
-                    label.setLocation(labelX, rowY);
+                    label.setLocation(labelX, labelY);
 
                     int fieldX = 0;
                     switch(fieldAlignment) {
@@ -301,9 +346,9 @@
                         }
                     }
 
-                    field.setLocation(fieldX, rowY);
+                    field.setLocation(fieldX, fieldY);
                     flagImageView.setLocation(fieldX + field.getWidth() + 
flagImageOffset,
-                        rowY + (rowHeight - flagImageView.getHeight()) / 2);
+                            flagImageY);
 
                     // Update the row y-coordinate
                     rowY += rowHeight + verticalSpacing;
Index: wtk/src/pivot/wtk/skin/terra/TerraLinkButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraLinkButtonSkin.java       (revision 
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraLinkButtonSkin.java       (working copy)
@@ -61,6 +61,16 @@
 
         return dataRenderer.getPreferredHeight(width);
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        LinkButton linkButton = (LinkButton)getComponent();
+
+        Button.DataRenderer dataRenderer = linkButton.getDataRenderer();
+        dataRenderer.render(linkButton.getButtonData(), linkButton, false);
+
+        return dataRenderer.getBaseline(width);
+    }
 
     public Dimensions getPreferredSize() {
         LinkButton linkButton = (LinkButton)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraListButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraListButtonSkin.java       (revision 
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraListButtonSkin.java       (working copy)
@@ -211,6 +211,19 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        ListButton listButton = (ListButton)getComponent();
+        Button.DataRenderer dataRenderer = listButton.getDataRenderer();
+
+        dataRenderer.render(listButton.getButtonData(), listButton, false);
+
+        int baseline = dataRenderer.getBaseline(width)
+            + padding.top + 1;
+
+        return baseline;
+    }
 
     public Dimensions getPreferredSize() {
         // TODO Optimize by performing calcuations locally
Index: wtk/src/pivot/wtk/skin/terra/TerraListViewSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraListViewSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraListViewSkin.java (working copy)
@@ -139,6 +139,25 @@
 
         return preferredHeight;
     }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public int getBaseline(int width) {
+        int baseline = -1;
+
+        ListView listView = (ListView)getComponent();
+        List<Object> listData = (List<Object>)listView.getListData();
+
+        ListView.ItemRenderer renderer = listView.getItemRenderer();
+
+        if (listData.getLength()>0) {
+            Object item = listData.get(0);
+            renderer.render(item, listView, false, false, false, false);
+            baseline = renderer.getBaseline(width);
+        }
+
+        return baseline;
+    }
 
     public void layout() {
         // No-op
Index: wtk/src/pivot/wtk/skin/terra/TerraPushButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraPushButtonSkin.java       (revision 
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraPushButtonSkin.java       (working copy)
@@ -139,6 +139,37 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        int baseline = -1;
+
+        PushButton pushButton = (PushButton)getComponent();
+        Button.DataRenderer dataRenderer = pushButton.getDataRenderer();
+
+        dataRenderer.render(pushButton.getButtonData(), pushButton, false);
+
+        // Include padding in constraint
+        int contentWidth = width;
+        if (contentWidth != -1) {
+            contentWidth = Math.max(contentWidth - (padding.left + 
padding.right + 2), 0);
+        }
+
+        int preferredHeight1 = dataRenderer.getPreferredHeight(contentWidth)
+            + padding.top + padding.bottom + 2;
+        baseline = dataRenderer.getBaseline(contentWidth)
+            + padding.top + 1;
+
+        // Adjust for preferred aspect ratio
+        if (!Float.isNaN(preferredAspectRatio)
+            && preferredAspectRatio >= 1
+            && (float)width / (float)preferredHeight1 < preferredAspectRatio) {
+            int preferredHeight2 = (int)((float)width / preferredAspectRatio);
+            baseline = (int) (preferredHeight1 / (float)preferredHeight2 * 
baseline);
+        }
+
+        return baseline;
+    }
 
     public Dimensions getPreferredSize() {
         PushButton pushButton = (PushButton)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraRadioButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraRadioButtonSkin.java      (revision 
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraRadioButtonSkin.java      (working copy)
@@ -98,6 +98,22 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        RadioButton radioButton = (RadioButton)getComponent();
+        Button.DataRenderer dataRenderer = radioButton.getDataRenderer();
+
+        dataRenderer.render(radioButton.getButtonData(), radioButton, false);
+
+        if (width != -1) {
+            width = Math.max(width - (BUTTON_DIAMETER + spacing), 0);
+        }
+
+        int baseline = dataRenderer.getBaseline(width);
+
+        return baseline;
+    }
 
     public Dimensions getPreferredSize() {
         RadioButton radioButton = (RadioButton)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraSheetSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraSheetSkin.java    (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraSheetSkin.java    (working copy)
@@ -161,6 +161,29 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        int baseline = -1;
+
+        Sheet sheet = (Sheet)getComponent();
+        Component content = sheet.getContent();
+
+        if (content != null
+            && content.isDisplayable()) {
+            if (width != -1) {
+                width = Math.max(width - (padding.left + padding.right + 2), 
0);
+            }
+
+            baseline = content.getPreferredHeight(width);
+        }
+
+        if (baseline != -1) {
+            baseline += padding.top + 1;
+        }
+
+        return baseline;
+    }
 
     @Override
     public Dimensions getPreferredSize() {
Index: wtk/src/pivot/wtk/skin/terra/TerraSpinnerSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraSpinnerSkin.java  (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraSpinnerSkin.java  (working copy)
@@ -584,6 +584,21 @@
 
         return preferredHeight;
     }
+    
+    @Override
+    public int getBaseline(int width) {
+        if (width >= 0) {
+            // Subtract the button and border width from width constraint
+            Dimensions upButtonPreferredSize = upButton.getPreferredSize();
+            Dimensions downButtonPreferredSize = downButton.getPreferredSize();
+            int buttonWidth = Math.max(upButtonPreferredSize.width,
+                downButtonPreferredSize.width);
+
+            width = Math.max(width - buttonWidth - 2, 0);
+        }
+        int baseline = spinnerContent.getBaseline(width) + 1;
+        return baseline;
+    }
 
     public void layout() {
         int width = getWidth();
Index: wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java        (revision 
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java        (working copy)
@@ -27,7 +27,6 @@
 import java.awt.font.TextHitInfo;
 import java.awt.font.TextLayout;
 import java.awt.geom.Rectangle2D;
-// import java.text.AttributedCharacterIterator;
 
 import pivot.collections.Dictionary;
 import pivot.wtk.ApplicationContext;
@@ -310,13 +309,14 @@
         super.uninstall();
     }
 
+    // TODO Localize?
+    private static final String testString = 
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
+    
     public int getPreferredWidth(int height) {
         TextInput textInput = (TextInput)getComponent();
         int textSize = textInput.getTextSize();
 
-        // TODO Localize?
         // TODO Recalculate only when font changes
-        String testString = 
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
 
         Rectangle2D testStringBounds = font.getStringBounds(testString, 
fontRenderContext);
         int averageCharWidth = (int)Math.round((testStringBounds.getWidth() / 
testString.length()));
@@ -340,6 +340,21 @@
         // No-op
     }
 
+    @Override
+    public int getBaseline(int width) {
+      /* calculate the baseline of the text */
+      int baseline = -1;
+      
+      LineMetrics lm = font.getLineMetrics(testString, fontRenderContext);
+      baseline = (int)Math.ceil(lm.getAscent()-2);
+      
+      if (baseline!=-1) {
+       baseline += padding.top + 1;
+      }
+
+      return baseline;
+    }
+    
     public void paint(Graphics2D graphics) {
         TextInput textInput = (TextInput)getComponent();
 
@@ -380,6 +395,10 @@
         graphics.setPaint(borderColor);
         GraphicsUtilities.drawRect(graphics, 0, 0, width, height);
 
+        if (debugBaseline) {
+            drawBaselineDebug(graphics);
+        }
+        
         // Paint the content
         String text = getText();
 

Reply via email to