001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.jdesktop.layout;
043:
044: import java.awt.*;
045: import java.lang.reflect.InvocationTargetException;
046: import java.lang.reflect.Method;
047: import java.util.Collections;
048: import java.util.Dictionary;
049: import java.util.Enumeration;
050: import java.util.HashMap;
051: import java.util.Map;
052:
053: /**
054: * Convenience class that can be used to determine the baseline of a
055: * particular component. The static method <code>getBaseline</code> uses the
056: * following algorithm to determine the baseline:
057: * <ol>
058: * <li>If the component has a <code>getBaseline(Component,int,int)</code>
059: * method, invoke it.
060: * <li>If there is a <code>UIManager</code> property of the name
061: * <code>Baseline.instance</code>, forward the call to that Baseline.
062: * <li>Otherwise use the built in support.
063: * </ol>
064: * <p>
065: * This class is primarily useful for JREs prior to 1.6. In 1.6 API for this
066: * was added directly to Component, Component and the
067: * appropriate ComponentUIs. When run on a JRE of 1.6 or greater this will directly
068: * call into the getBaseline method of Component.
069: *
070: * @version $Revision$
071: */
072: public class Baseline {
073: //
074: // Used by button and label baseline code, cached to avoid excessive
075: // garbage.
076: //
077: private static final Rectangle viewRect = new Rectangle();
078: private static final Rectangle textRect = new Rectangle();
079: private static final Rectangle iconRect = new Rectangle();
080:
081: //
082: // These come from TitleBorder. NOTE that these are NOT final in
083: // TitledBorder
084: //
085: private static final int EDGE_SPACING = 2;
086: private static final int TEXT_SPACING = 2;
087:
088: private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
089:
090: // Prototype label for calculating baseline of tables.
091: private static Label TABLE_LABEL;
092:
093: // Prototype label for calculating baseline of lists.
094: private static Label LIST_LABEL;
095:
096: // Prototype label for calculating baseline of trees.
097: private static Label TREE_LABEL;
098:
099: // Corresponds to com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel
100: private static Class CLASSIC_WINDOWS;
101: // Whether or not we've tried to load WindowsClassicLookAndFeel.
102: private static boolean checkedForClassic;
103:
104: // Map<Class,Method>
105: private static final Map BASELINE_MAP = Collections
106: .synchronizedMap(new HashMap());
107: private static Method COMPONENT_BASELINE_METHOD;
108:
109: static {
110: COMPONENT_BASELINE_METHOD = null;
111: try {
112: COMPONENT_BASELINE_METHOD = Component.class
113: .getMethod("getBaseline", new Class[] { int.class,
114: int.class });
115: } catch (NoSuchMethodException nsme) {
116: }
117: }
118:
119: /**
120: * Returns the baseline for the specified component, or -1 if the
121: * baseline can not be determined. The baseline is measured from
122: * the top of the component. This method returns the baseline based
123: * on the preferred size.
124: *
125: * @param component Component to calculate baseline for
126: * @return baseline for the specified component
127: */
128: public static int getBaseline(Component component) {
129: Dimension pref = component.getPreferredSize();
130: return getBaseline(component, pref.width, pref.height);
131: }
132:
133: private static Method getBaselineMethod(Component component) {
134: if (COMPONENT_BASELINE_METHOD != null) {
135: return COMPONENT_BASELINE_METHOD;
136: }
137: Class klass = component.getClass();
138: while (klass != null) {
139: if (BASELINE_MAP.containsKey(klass)) {
140: Method method = (Method) BASELINE_MAP.get(klass);
141: return method;
142: }
143: klass = klass.getSuperclass();
144: }
145: klass = component.getClass();
146: Method[] methods = klass.getMethods();
147: for (int i = methods.length - 1; i >= 0; i--) {
148: Method method = methods[i];
149: if ("getBaseline".equals(method.getName())) {
150: Class[] params = method.getParameterTypes();
151: if (params.length == 2 && params[0] == int.class
152: && params[1] == int.class) {
153: BASELINE_MAP.put(klass, method);
154: return method;
155: }
156: }
157: }
158: BASELINE_MAP.put(klass, null);
159: return null;
160: }
161:
162: private static int invokeBaseline(Method method, Component c,
163: int width, int height) {
164: int baseline = -1;
165: try {
166: baseline = ((Integer) method.invoke(c, new Object[] {
167: new Integer(width), new Integer(height) }))
168: .intValue();
169: } catch (IllegalAccessException iae) {
170: } catch (IllegalArgumentException iae2) {
171: } catch (InvocationTargetException ite2) {
172: }
173: return baseline;
174: }
175:
176: /**
177: * Returns the baseline for the specified component, or a value less
178: * than 0 if the baseline can not be determined. The baseline is measured
179: * from the top of the component.
180: *
181: * @param component Component to calculate baseline for
182: * @param width Width of the component to determine baseline for.
183: * @param height Height of the component to determine baseline for.
184: * @return baseline for the specified component
185: */
186: public static int getBaseline(Component component, int width,
187: int height) {
188: if (1 == 1)
189: return -1;
190: Method baselineMethod = getBaselineMethod(component);
191: if (baselineMethod != null) {
192: return invokeBaseline(baselineMethod, component, width,
193: height);
194: }
195: Object baselineImpl = null; //UIManager.get("Baseline.instance");
196: if (baselineImpl != null && (baselineImpl instanceof Baseline)) {
197: return ((Baseline) baselineImpl).getComponentBaseline(
198: component, width, height);
199: }
200: String uid = null;
201: if (component instanceof Button)
202: uid = "ButtonUI";
203: if (component instanceof Label)
204: uid = "LabelUI";
205: if (component instanceof TextField)
206: uid = "TextFieldUI";
207: int baseline = -1;
208: if (uid == "ButtonUI" || uid == "CheckBoxUI"
209: || uid == "RadioButtonUI" || uid == "ToggleButtonUI") {
210: baseline = getButtonBaseline((Button) component, height);
211: } else if (uid == "TextAreaUI") {
212: return getTextAreaBaseline((TextArea) component, height);
213: } else if (uid == "FormattedTextFieldUI"
214: || uid == "PasswordFieldUI" || uid == "TextFieldUI") {
215: baseline = getSingleLineTextBaseline(
216: (TextComponent) component, height);
217: } else if (uid == "LabelUI") {
218: baseline = getLabelBaseline((Label) component, height);
219: } else if (uid == "ListUI") {
220: baseline = getListBaseline((List) component, height);
221: } else if (uid == "PanelUI") {
222: baseline = getPanelBaseline((Panel) component, height);
223: } else if (uid == "ScrollPaneUI") {
224: baseline = getScrollPaneBaseline((ScrollPane) component,
225: height);
226: }
227: return Math.max(baseline, -1);
228: }
229:
230: private static Insets rotateInsets(Insets topInsets,
231: int targetPlacement) {
232: return new Insets(topInsets.top, topInsets.left,
233: topInsets.bottom, topInsets.right);
234: }
235:
236: private static int getTextAreaBaseline(TextArea text, int height) {
237: FontMetrics fm = text.getFontMetrics(text.getFont());
238: return 6 + fm.getAscent();
239: }
240:
241: private static int getListBaseline(List list, int height) {
242: int rowHeight = -1;//list.getFixedCellHeight();
243: if (LIST_LABEL == null) {
244: LIST_LABEL = new Label("X");
245: }
246: Label label = LIST_LABEL;
247: label.setFont(list.getFont());
248: // JList actually has much more complex behavior here.
249: // If rowHeight != -1 the rowHeight is either the max of all cell
250: // heights (layout orientation != VERTICAL), or is variable depending
251: // upon the cell. We assume a default size.
252: // We could theoretically query the real renderer, but that would
253: // not work for an empty model and the results may vary with
254: // the content.
255: if (rowHeight == -1) {
256: rowHeight = label.getPreferredSize().height;
257: }
258: return getLabelBaseline(label, rowHeight) + 6;
259: }
260:
261: private static int getScrollPaneBaseline(ScrollPane sp, int height) {
262: // Component view = sp.getViewport().getView();
263: // if (view instanceof Component) {
264: // int baseline = getBaseline((Component)view);
265: // if (baseline > 0) {
266: // return baseline + sp.getViewport().getY();
267: // }
268: // }
269: return -1;
270: }
271:
272: private static int getPanelBaseline(Panel panel, int height) {
273: return -1;
274: }
275:
276: private static int getLabelBaseline(Label label, int height) {
277: FontMetrics fm = label.getFontMetrics(label.getFont());
278:
279: resetRects(label, height);
280:
281: return textRect.y + fm.getAscent();
282: }
283:
284: /**
285: * Returns the baseline for single line text components, like
286: * <code>JTextField</code>.
287: */
288: private static int getSingleLineTextBaseline(
289: TextComponent textComponent, int h) {
290: // View rootView = textComponent.getUI().getRootView(textComponent);
291: // if (rootView.getViewCount() > 0) {
292: // Insets insets = textComponent.getInsets();
293: // int height = h - insets.top - insets.bottom;
294: // int y = insets.top;
295: // View fieldView = rootView.getView(0);
296: // int vspan = (int)fieldView.getPreferredSpan(View.Y_AXIS);
297: // if (height != vspan) {
298: // int slop = height - vspan;
299: // y += slop / 2;
300: // }
301: // FontMetrics fm = textComponent.getFontMetrics(
302: // textComponent.getFont());
303: // y += fm.getAscent();
304: // return y;
305: // }
306: return -1;
307: }
308:
309: /**
310: * Returns the baseline for buttons.
311: */
312: private static int getButtonBaseline(Button button, int height) {
313: FontMetrics fm = button.getFontMetrics(button.getFont());
314:
315: resetRects(button, height);
316:
317: String text = button.getLabel();
318: if (text != null && text.startsWith("<html>")) {
319: return -1;
320: }
321: return textRect.y + fm.getAscent();
322: }
323:
324: private static void resetRects(Component c, int height) {
325: Insets insets = new Insets(5, 5, 5, 5);//c.getInsets();
326: viewRect.x = insets.left;
327: viewRect.y = insets.top;
328: viewRect.width = c.getWidth() - (insets.right + viewRect.x);
329: viewRect.height = height - (insets.bottom + viewRect.y);
330: textRect.x = textRect.y = textRect.width = textRect.height = 0;
331: iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
332: }
333:
334: /**
335: * Creates an instance of Baseline. You typically don't create a
336: * Baseline. The constructor is provided by look and feels that wish
337: * to provide baseline support.
338: * <p>
339: * A custom look and feel that wants to provide <code>Baseline</code>
340: * support should put the instance in the defaults returned
341: * from <code>getDefaults</code>. If you want to override the
342: * baseline suport for a look and feel place the instance in the defaults
343: * returned from UIManager.getLookAndFeelDefaults(). Tthis will ensure
344: * that if the look and feel changes the appropriate baseline can be used.
345: */
346: protected Baseline() {
347: }
348:
349: /**
350: * Returns the baseline for the specified component, or -1 if the
351: * baseline can not be determined. The baseline is measured from
352: * the top of the component.
353: *
354: * @param component Component to calculate baseline for
355: * @param width Width of the component to determine baseline for.
356: * @param height Height of the component to determine baseline for.
357: * @return baseline for the specified component
358: */
359: public int getComponentBaseline(Component component, int width,
360: int height) {
361: return -1;
362: }
363: }
|