1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * ident        "%Z%%M% %I%     %E% SMI"
  24  *
  25  * Copyright (c) 1996-2001 by Sun Microsystems, Inc.
  26  * All rights reserved.
  27  */
  28 package com.sun.dhcpmgr.ui;
  29 
  30 import java.awt.*;
  31 import java.util.*;
  32 
  33 /**
  34  * <CODE>FieldLayout</CODE> treats components as a list of
  35  * labeled fields, where each label is placed on the left
  36  * edge of the container with its associated field to its
  37  * right.<P>
  38  *
  39  * Two kinds of components may be added: "Label" and "Field."
  40  * Labels and Fields must be added in pairs, because there is
  41  * a one-to-one correspondence between them.<P>
  42  *
  43  * When a <CODE>Field</CODE> is added, it is associated with the
  44  * last <CODE>Label</CODE> added.<P>
  45  */
  46 public class FieldLayout implements LayoutManager {
  47     public static final String LABEL = "Label";
  48     public static final String LABELTOP = "LabelTop";
  49     public static final String FIELD = "Field";
  50     
  51     class Row {
  52         Component label;
  53         Component field;
  54         double yRatio;
  55         boolean center;
  56         
  57         public Row() {
  58             label = null;
  59             field = null;
  60             yRatio = 1;
  61             center = true;
  62         }
  63     }
  64     
  65     Vector rows;
  66     int hgap;
  67     int vgap;
  68 
  69     /**
  70      * Constructs a new <CODE>FieldLayout</CODE> with a centered alignment.
  71      */
  72     public FieldLayout() {
  73         this(5, 5);
  74     }
  75 
  76     /**
  77      * Constructs a new <CODE>FieldLayout</CODE> with the specified gap values.
  78      * @param <VAR>hgap</VAR> The horizontal gap variable.
  79      * @param <VAR>vgap</VAR> The vertical gap variable.
  80      */
  81     public FieldLayout(int hgap, int vgap) {
  82         this.hgap = hgap;
  83         this.vgap = vgap;
  84         rows = new Vector();
  85     }
  86 
  87     /**
  88      * Adds the specified component to the layout.
  89      * @param <VAR>name</VAR> The name of the component.
  90      * @param <VAR>comp</VAR> The component to be added.
  91      */
  92     public void addLayoutComponent(String name, Component comp) {
  93         if (LABEL.equals(name)) {
  94             Row r = new Row();
  95             r.label = comp;
  96             r.center = true;
  97             rows.addElement(r);
  98         } else if (LABELTOP.equals(name)) {
  99             Row r = new Row();
 100             r.label = comp;
 101             r.center = false;
 102             rows.addElement(r);
 103         } else if (FIELD.equals(name)) {
 104             ((Row)rows.lastElement()).field = comp;
 105         }
 106     }
 107 
 108     /**
 109      * Removes the specified component from the layout.
 110      * @param <VAR>comp</VAR> The component to remove.
 111      */
 112     public void removeLayoutComponent(Component comp) {
 113         Enumeration en = rows.elements();
 114         while (en.hasMoreElements()) {
 115             Row r = (Row)en.nextElement();
 116             if (comp == r.label || comp == r.field) {
 117                 rows.removeElement(r);
 118                 return;
 119             }
 120         }
 121     }
 122 
 123     /**
 124      * Returns the preferred dimensions for this layout given the components
 125      * in the specified target container.
 126      * @param <VAR>target</VAR> The component that needs to be laid out.
 127      * @see java.awt.Container
 128      * @see #minimumLayoutSize
 129      */
 130     public Dimension preferredLayoutSize(Container target) {
 131         Dimension dim = new Dimension(0, 0);
 132         int widestLabel = 0, widestField = 0;
 133 
 134         Enumeration en = rows.elements();
 135         while (en.hasMoreElements()) {
 136             Row r = (Row)en.nextElement();
 137             if (!r.label.isVisible() || !r.field.isVisible()) {
 138                 continue;
 139             }
 140             Dimension ld = r.label.getPreferredSize();
 141             widestLabel = Math.max(widestLabel, ld.width);
 142             Dimension fd = r.field.getPreferredSize();
 143             widestField = Math.max(widestField, fd.width);
 144             dim.height += Math.max(ld.height, fd.height) + vgap;
 145         }
 146         dim.width = widestLabel + hgap + widestField;
 147         Insets insets = target.getInsets();
 148         dim.width  += insets.left + insets.right + hgap*2;
 149         dim.height += insets.top + insets.bottom + vgap;
 150         return dim;
 151     }
 152 
 153     /**
 154      * Returns the minimum dimensions needed to layout the components
 155      * contained in the specified target container.
 156      * @param <VAR>target</VAR> The component that needs to be laid out.
 157      * @see #preferredLayoutSize
 158      */
 159     public Dimension minimumLayoutSize(Container target) {
 160         Dimension dim = new Dimension(0, 0);
 161         int widestLabel = 0, widestField = 0;
 162 
 163         Enumeration en = rows.elements();
 164         while (en.hasMoreElements()) {
 165             Row r = (Row)en.nextElement();
 166             if (!r.label.isVisible() || !r.field.isVisible()) {
 167                 continue;
 168             }
 169             Dimension ld = r.label.getMinimumSize();
 170             widestLabel = Math.max(widestLabel, ld.width);
 171             Dimension fd = r.field.getMinimumSize();
 172             widestField = Math.max(widestField, fd.width);
 173             dim.height += Math.max(ld.height, fd.height) + vgap;
 174         }
 175         dim.width = widestLabel + hgap + widestField;
 176         Insets insets = target.getInsets();
 177         dim.width  += insets.left + insets.right + hgap*2;
 178         dim.height += insets.top + insets.bottom + vgap;
 179         return dim;
 180     }
 181 
 182     /**
 183      * Performs the layout of the container.  Components are treated
 184      * either as labels or fields. Labels go on the left (right-aligned),
 185      * with their associated fields placed immediately to their right.
 186      * @param <VAR>target</VAR> The specified component being laid out.
 187      * @see java.awt.Container
 188      */
 189     public void layoutContainer(Container target) {
 190         Insets insets = target.getInsets();
 191         Dimension dim = target.getSize();
 192         int x = 0, y = insets.top, offset = 0;
 193         int widestLabel = 0;
 194         int ySlop = 0;
 195 
 196         // Compute whether preferred sizes will fit
 197         Dimension pDim = preferredLayoutSize(target);
 198         boolean usingPreferred = true;
 199         if ((pDim.height > (dim.height - insets.top - insets.bottom)) ||
 200             (pDim.width > (dim.width - insets.left - insets.right))) {
 201             usingPreferred = false;
 202             // Compute leftover vertical space
 203             pDim = minimumLayoutSize(target);
 204             ySlop = dim.height - insets.top - insets.bottom - pDim.height;
 205             if (ySlop < 0) {
 206                 ySlop = 0;
 207             }
 208         }
 209 
 210         /*
 211          * Find widest label.  Our policy on horizontal space is that labels
 212          * are fully satisfied and fields get whatever's left.
 213          * For vertical space, if there's any leftovers then allocate it
 214          * in proportion to demand, which we'll define as the ratio between
 215          * preferred size and minimum size.
 216          */
 217         double sumRatios = 0;
 218         Enumeration en = rows.elements();
 219         while (en.hasMoreElements()) {
 220             Row r = (Row)en.nextElement();
 221             if (r.label.isVisible() && r.field.isVisible()) {
 222                 Dimension d = usingPreferred ? r.label.getPreferredSize() :
 223                     r.label.getMinimumSize();
 224                 widestLabel = Math.max(widestLabel, d.width);
 225                 if (!usingPreferred) {
 226                     double lRatio = r.label.getPreferredSize().getHeight() /
 227                         r.label.getMinimumSize().getHeight();
 228                     double fRatio = r.field.getPreferredSize().getHeight() /
 229                         r.field.getMinimumSize().getHeight();
 230                     r.yRatio = Math.max(lRatio, fRatio);
 231                 }
 232                 // If there is no demand, then adjust ratio to zero
 233                 if (r.yRatio == 1.0) {
 234                     r.yRatio = 0;
 235                 }
 236                 sumRatios += r.yRatio;
 237             }
 238         }
 239 
 240         // lay out rows, right-aligning labels
 241         en = rows.elements();
 242         while (en.hasMoreElements()) {
 243             Row r = (Row)en.nextElement();
 244             Component l = r.label;
 245             Component f = r.field;
 246             // Skip the row if both aren't visible
 247             if (!l.isVisible() || !f.isVisible())
 248                 continue;
 249             Dimension ld = usingPreferred ? l.getPreferredSize() :
 250                 l.getMinimumSize();
 251             Dimension fd = usingPreferred ? f.getPreferredSize() :
 252                 f.getMinimumSize();
 253 
 254             int rowHeight = Math.max(ld.height, fd.height) +
 255                 (int)(ySlop * r.yRatio / sumRatios);
 256 
 257             x = insets.left;
 258             /*
 259              * If the field is visible, move it right to line up with
 260              * the widest line.
 261              */
 262             x += Math.max(widestLabel - ld.width, 0);
 263             offset = 0;
 264             if (r.center) {
 265                 // center label on field
 266                 offset = Math.max(0, (rowHeight-ld.height)/2);
 267             }
 268             int labelHeight = rowHeight;
 269             /*
 270              * If label doesn't look like it wants extra space, don't give it;
 271              * otherwise, JLabels will get drawn centered even if user
 272              * specified it as a top alignment when doing the layout.
 273              */
 274             if (l.getPreferredSize().height == l.getMinimumSize().height) {
 275                 labelHeight = ld.height;
 276             }
 277             l.setBounds(x, y+offset, ld.width, labelHeight);
 278             x = insets.left + widestLabel + hgap;
 279             int w = dim.width-x-hgap;
 280             f.setBounds(x, y, w, rowHeight);
 281             y += rowHeight + vgap;
 282         }
 283 
 284     }
 285 
 286     /**
 287      * Returns the <CODE>String</CODE> representation of this
 288      * <CODE>FieldLayout</CODE>'s values.
 289      */
 290     public String toString() {
 291         return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + "]";
 292     }
 293 }