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 }