1 /*
2  * @(#)Quaqua16LayoutStyle.java  1.2 2007-12-18
3  *
4  * Copyright (c) 2007 Werner Randelshofer
5  * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
6  * All rights reserved.
7  *
8  * The copyright of this software is owned by Werner Randelshofer.
9  * You may not use, copy or modify this software, except in
10  * accordance with the license agreement you entered into with
11  * Werner Randelshofer. For details see accompanying license terms.
12  */
13 
14 package ch.randelshofer.quaqua;
15 
16 import java.awt.*;
17 import javax.swing.*;
18 import java.lang.reflect.*;
19 import java.util.*;
20 /**
21  * A Quaqua16LayoutStyle can be queried for the preferred gaps between two
22  * JComponents, or between a JComponent and a parent Container.
23  * <p>
24  * To compile this class, you need Java 1.6.
25  *
26  * @author  Werner Randelshofer
27  * @version 1.2 2007-12-18 Bumped minimal treshold for indentation up from 3
28  * to 8.
29  * <br>1.0 2007-07-23 Created.
30  */
31 public class Quaqua16LayoutStyle extends LayoutStyle {
32     private final static boolean DEBUG = false;
33 
34     /** Mini size style. */
35     private final static int MINI = 0;
36     /** Small size style. */
37     private final static int SMALL = 1;
38     /** Regular size style. */
39     private final static int REGULAR = 2;
40 
41     /**
42      * The containerGapDefinitions array defines the preferred insets (child gaps)
43      * of a parent container towards one of its child components.
44      *
45      * Note: As of now, we do not yet specify the preferred gap from a child
46      * to its parent. Therefore we may not be able to treat all special cases.
47      *
48      * This array is used to initialize the containerGaps HashMap.
49      *
50      * The array has the following structure, which is supposed to be a
51      * a compromise between legibility and code size.
52      * containerGapDefinitions[0..n] = preferred insets for some parent UI's
53      * containerGapDefinitions[][0..m-3] = name of parent UI,
54      *                                 optionally followed by a full stop and
55      *                                 a style name
56      * containerGapDefinitions[][m-2] = mini insets
57      * containerGapDefinitions[][m-1] = small insets
58      * containerGapDefinitions[][m] = regular insets
59      */
60     private final static Object[][] containerGapDefinitions = {
61         // Format:
62         // { list of parent UI's,
63         //   mini insets, small insets, regular insets }
64 
65         { "TabbedPaneUI",
66           new Insets(6,10,10,10), new Insets(6,10,10,12), new Insets(12,20,20,20)
67         },
68 
69         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_3.html#//apple_ref/doc/uid/TP30000360/DontLinkElementID_27
70         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_3.html#//apple_ref/doc/uid/TP30000360/DontLinkElementID_26
71         // note for small and mini size: leave 8 to 10 pixels on top
72         // note for regular size: leave only 12 pixel at top if tabbed pane UI
73         { "RootPaneUI",
74           new Insets(8,10,10,10), new Insets(8,10,10,12), new Insets(14,20,20,20)
75         },
76 
77         // These child gaps are used for all other components
78         { "default",
79           new Insets(8,10,10,10), new Insets(8,10,10,12), new Insets(14,20,20,20)
80         },
81     };
82 
83     /**
84      * The relatedGapDefinitions table defines the preferred gaps
85      * of one party of two related components.
86      *
87      * The effective preferred gap is the maximum of the preferred gaps of
88      * both parties.
89      *
90      * This array is used to initialize the relatedGaps HashMap.
91      *
92      * The array has the following structure, which is supposed to be a
93      * a compromise between legibility and code size.
94      * containerGapDefinitions[0..n] = preferred gaps for a party of a two related UI's
95      * containerGapDefinitions[][0..m-3] = name of UI
96      *                                 optionally followed by a full stop and
97      *                                 a style name
98      * containerGapDefinitions[][m-2] = mini insets
99      * containerGapDefinitions[][m-1] = small insets
100      * containerGapDefinitions[][m] = regular insets
101      */
102     private final static Object[][] relatedGapDefinitions = {
103         // Format:
104         // { list of UI's,
105         //   mini insets, small insets, regular insets }
106 
107         // Push Button:
108         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF104
109         { "ButtonUI", "ButtonUI.push", "ButtonUI.text",
110           "ToggleButtonUI.push", "ToggleButtonUI.text",
111           new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
112         },
113 
114         // Metal Button
115         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF187
116         { "ButtonUI.metal", "ToggleButtonUI.metal",
117           new Insets(8,8,8,8), new Insets(8,8,8,8), new Insets(12,12,12,12)
118         },
119 
120         // Bevel Button (Rounded and Square)
121         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF112
122         { "ButtonUI.bevel", "ButtonUI.toggle", "ButtonUI.square",
123           "ToggleButtonUI", "ToggleButtonUI.bevel", "ToggleButtonUI.square", "ToggleButtonUI.toggle",
124           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
125         },
126 
127         // Bevel Button (Rounded and Square)
128         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF112
129         { "ButtonUI.bevel.largeIcon", "ToggleButtonUI.bevel.largeIcon",
130           new Insets(8,8,8,8), new Insets(8,8,8,8), new Insets(8,8,8,8)
131         },
132 
133         // Icon Button
134         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF189
135         { "ButtonUI.icon",
136           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
137         },
138         { "ButtonUI.icon.largeIcon",
139           new Insets(8,8,8,8), new Insets(8,8,8,8), new Insets(8,8,8,8)
140         },
141 
142         // Round Button
143         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF191
144         { "ButtonUI.round", "ToggleButtonUI.round",
145           new Insets(12,12,12,12), new Insets(12,12,12,12), new Insets(12,12,12,12)
146         },
147 
148         // Help Button
149         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_2.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF193
150         { "ButtonUI.help",
151           new Insets(12,12,12,12), new Insets(12,12,12,12), new Insets(12,12,12,12)
152         },
153 
154         // Segmented Control
155         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF196
156         { "ButtonUI.toggleCenter", "ToggleButtonUI.toggleCenter",
157           new Insets(8,0,8,0), new Insets(10,0,10,0), new Insets(12,0,12,0)
158         },
159         { "ButtonUI.toggleEast", "ToggleButtonUI.toggleEast",
160           new Insets(8,0,8,8), new Insets(10,0,10,10), new Insets(12,0,12,12)
161         },
162         { "ButtonUI.toggleWest", "ToggleButtonUI.toggleWest",
163           new Insets(8,8,8,0), new Insets(10,10,10,0), new Insets(12,12,12,0)
164         },
165 
166         { "ButtonUI.toolBarTab", "ToggleButtonUI.toolBarTab",
167           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
168         },
169 
170         // Color Well Button
171         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF213
172         { "ButtonUI.colorWell", "ToggleButtonUI.colorWell",
173           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
174         },
175 
176         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF198
177         // FIXME - The following values are given in the AHIG.
178         // In reality, the values further below seem to be more appropriate.
179         // Which ones are right?
180         //{ "CheckBoxUI", new Insets(7, 5, 7, 5), new Insets(8, 6, 8, 6), new Insets(8, 8, 8, 8) },
181         { "CheckBoxUI",
182           new Insets(6, 5, 6, 5), new Insets(7, 6, 7, 6), new Insets(7, 6, 7, 6)
183         },
184 
185         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF198
186         { "ComboBoxUI",
187           new Insets(8, 5, 8, 5), new Insets(10, 6, 10, 6), new Insets(12, 8, 12, 8)
188         },
189 
190 
191         // There is no spacing given for labels in Apples Guidelines.
192         // We use the values here, which is the minimum of the spacing of all
193         // other components.
194         { "LabelUI",
195           new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8)
196         },
197 
198         // ? spacing not given
199         { "ListUI",
200           new Insets(5, 5, 5, 5), new Insets(6, 6, 6, 6), new Insets(6, 6, 6, 6)
201         },
202 
203         // ? spacing not given
204         { "PanelUI",
205           new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0)
206         },
207 
208         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_5.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF106
209         // ? spacing not given
210         { "ProgressBarUI",
211           new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
212         },
213 
214         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_3.html#//apple_ref/doc/uid/20000957-TP30000359-BIAHBFAD
215         { "RadioButtonUI",
216           new Insets(5, 5, 5, 5), new Insets(6, 6, 6, 6), new Insets(6, 6, 6, 6)
217         },
218 
219         //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_6.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF114
220         // ? spacing not given. We use the same like for text fields
221         { "ScrollPaneUI",
222           new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(8, 10, 8, 10)
223         },
224 
225         //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_8.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF214
226         // ? spacing not given
227         //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGLayout/chapter_19_section_2.html#//apple_ref/doc/uid/20000957-TP30000360-CHDEACGD
228         { "SeparatorUI",
229           new Insets(8, 8, 8, 8), new Insets(10, 10, 10, 10), new Insets(12, 12, 12, 12)
230         },
231 
232         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_4.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF115
233         { "SliderUI.horizontal",
234           new Insets(6,8,6,8), new Insets(6,10,6,10), new Insets(6,12,6,12)
235         },
236         { "SliderUI.vertical",
237           new Insets(8,8,8,8), new Insets(10,10,10,10), new Insets(12,12,12,12)
238         },
239 
240         //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_4.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF204
241         { "SpinnerUI",
242           new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(8, 10, 8, 10)
243         },
244 
245         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_7.html#//apple_ref/doc/uid/20000957-TP30000359-CHDDBIJE
246         // ? spacing not given
247         { "SplitPaneUI",
248           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
249         },
250         // http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_7.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF105
251         // ? spacing not given
252         { "TabbedPaneUI",
253           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
254         },
255         { "TableUI",
256           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
257         },
258         // ? spacing not given
259         { "TextAreaUI", "EditorPaneUI", "TextPaneUI",
260           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
261         },
262         //http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGControls/chapter_18_section_6.html#//apple_ref/doc/uid/20000957-TP30000359-TPXREF225
263         { "TextFieldUI", "FormattedTextFieldUI", "PasswordFieldUI",
264           new Insets(6, 8, 6, 8), new Insets(6, 8, 6, 8), new Insets(8, 10, 8, 10)
265         },
266 
267         // ? spacing not given
268         { "TreeUI",
269           new Insets(0,0,0,0), new Insets(0,0,0,0), new Insets(0,0,0,0)
270         },
271     };
272     private final static Object[][] unrelatedGapDefinitions = {
273         // UI, mini, small, regular
274         { "ButtonUI.help",
275           new Insets(24,24,24,24), new Insets(24,24,24,24), new Insets(24,24,24,24)
276         },
277         { "default",
278           new Insets(12, 12, 12, 12), new Insets(14, 14, 14, 14), new Insets(16, 16, 16, 16)
279         },
280     };
281     /**
282      * The indentGapDefinitions table defines the preferred indentation
283      * for components that are indented after the specified component.
284      *
285      * This array is used to initialize the indentGaps HashMap.
286      *
287      * The array has the following structure, which is supposed to be a
288      * a compromise between legibility and code size.
289      * indentGapDefinitions[0..n] = preferred gaps for a party of a two related UI's
290      * indentGapDefinitions[][0..m-3] = name of UI
291      *                                 optionally followed by a full stop and
292      *                                 a style name
293      * indentGapDefinitions[][m-2] = mini insets
294      * indentGapDefinitions[][m-1] = small insets
295      * indentGapDefinitions[][m] = regular insets
296      */
297     private final static Object[][] indentGapDefinitions = {
298         // UI, mini, small, regular
299         { "default",
300           new Insets(16, 16, 16, 16), new Insets(20, 20, 20, 20), new Insets(25, 25, 25, 25)
301         },
302     };
303 
304     /**
305      * Creates a hash map for the specified definitions array.
306      *
307      * Each entry of the hash map has the name of an UI (optionally followed by
308      * a full stop and a style name) as its key <String>
309      * and an array of Insets as its value <Insets[]>.
310      */
createMap(Object[][] definitions)311     private static HashMap createMap(Object[][] definitions) {
312         HashMap map = new HashMap();
313         for (int i=0; i < definitions.length; i++) {
314             int keys = 0;
315             while (keys < definitions[i].length
316             && (definitions[i][keys] instanceof String)) {
317                 keys++;
318             }
319             Insets[] values = new Insets[definitions[i].length - keys];
320             for (int j=keys; j < definitions[i].length; j++) {
321                 values[j-keys] = (Insets) definitions[i][j];
322             }
323             for (int j=0; j < keys; j++) {
324                 map.put(definitions[i][j], values);
325             }
326         }
327         return map;
328     }
329 
330     /**
331      * The relatedGapDefinitions table defines the preferred gaps
332      * of one party of two related components.
333      */
334     private final static HashMap relatedGaps = createMap(relatedGapDefinitions);
335     /**
336      * The unrelatedGapDefinitions table defines the preferred gaps
337      * of one party of two unrelated components.
338      */
339     private final static HashMap unrelatedGaps = createMap(unrelatedGapDefinitions);
340     /**
341      * The containerGapDefinitions array defines the preferred insets (child gaps)
342      * of a parent component towards one of its children.
343      */
344     private final static HashMap containerGaps = createMap(containerGapDefinitions);
345     /**
346      * The indentGapDefinitions table defines the preferred indentation
347      * for components that are indented after the specified component.
348      */
349     private final static HashMap indentGaps = createMap(indentGapDefinitions);
350 
351     /**
352      * Creates a new instance.
353      */
Quaqua16LayoutStyle()354     public Quaqua16LayoutStyle() {
355     }
356 
357     /**
358      * Returns the amount of space to use between two components.
359      * The return value indicates the distance to place
360      * <code>component2</code> relative to <code>component1</code>.
361      * For example, the following returns the amount of space to place
362      * between <code>component2</code> and <code>component1</code>
363      * when <code>component2</code> is placed vertically above
364      * <code>component1</code>:
365      * <pre>
366      *   int gap = getPreferredGap(component1, component2,
367      *                             LayoutStyle.RELATED,
368      *                             SwingConstants.NORTH, parent);
369      * </pre>
370      * The <code>type</code> parameter indicates the type
371      * of gap being requested.  It can be one of the following values:
372      * <table>
373      * <tr><td><code>RELATED</code>
374      *     <td>If the two components will be contained in
375      *         the same parent and are showing similar logically related
376      *         items, use <code>RELATED</code>.
377      * <tr><td><code>UNRELATED</code>
378      *     <td>If the two components will be
379      *          contained in the same parent but show logically unrelated items
380      *          use <code>UNRELATED</code>.
381      * <tr><td><code>INDENT</code>
382      *     <td>Used to obtain the preferred distance to indent a component
383      *         relative to another.  For example, if you want to horizontally
384      *         indent a JCheckBox relative to a JLabel use <code>INDENT</code>.
385      *         This is only useful for the horizontal axis.
386      * </table>
387      * <p>
388      * It's important to note that some look and feels may not distinguish
389      * between <code>RELATED</code> and <code>UNRELATED</code>.
390      * <p>
391      * The return value is not intended to take into account the
392      * current size and position of <code>component2</code> or
393      * <code>component1</code>.  The return value may take into
394      * consideration various properties of the components.  For
395      * example, the space may vary based on font size, or the preferred
396      * size of the component.
397      *
398      * @param component1 the <code>JComponent</code>
399      *               <code>component2</code> is being placed relative to
400      * @param component2 the <code>JComponent</code> being placed
401      * @param type how the two components are being placed
402      * @param position the position <code>component2</code> is being placed
403      *        relative to <code>component1</code>; one of
404      *        <code>SwingConstants.NORTH</code>,
405      *        <code>SwingConstants.SOUTH</code>,
406      *        <code>SwingConstants.EAST</code> or
407      *        <code>SwingConstants.WEST</code>
408      * @param parent the parent of <code>component2</code>; this may differ
409      *        from the actual parent and may be null
410      * @return the amount of space to place between the two components
411      * @throws IllegalArgumentException if <code>position</code> is not
412      *         one of <code>SwingConstants.NORTH</code>,
413      *         <code>SwingConstants.SOUTH</code>,
414      *         <code>SwingConstants.EAST</code> or
415      *         <code>SwingConstants.WEST</code>; <code>type</code> not one
416      *         of <code>INDENT</code>, <code>RELATED</code>
417      *         or <code>UNRELATED</code>; or <code>component1</code> or
418      *         <code>component2</code> is null
419      */
getPreferredGap(JComponent component1, JComponent component2, javax.swing.LayoutStyle.ComponentPlacement type, int position, Container parent)420     public int getPreferredGap(JComponent component1, JComponent component2,
421         javax.swing.LayoutStyle.ComponentPlacement type, int position, Container parent) {
422         int result;
423 
424         if (type == javax.swing.LayoutStyle.ComponentPlacement.INDENT) {
425             // Compute gap
426             int sizeStyle = getSizeStyle(component1);
427             Insets vgap = getVisualIndent(component1);
428             Insets pgap = getPreferredGap(component1, javax.swing.LayoutStyle.ComponentPlacement.INDENT, sizeStyle);
429             switch (position) {
430                 case SwingConstants.NORTH :
431                     result = (vgap.bottom > 8) ? vgap.bottom : pgap.bottom;
432                     break;
433                 case SwingConstants.SOUTH :
434                     result = (vgap.top > 8) ? vgap.top : pgap.top;
435                     break;
436                 case SwingConstants.EAST :
437                     result = (vgap.left > 8) ? vgap.left : pgap.left;
438                     break;
439                 case SwingConstants.WEST :
440                 default :
441                     result = (vgap.right > 8) ? vgap.right : pgap.right;
442                     break;
443             }
444 
445             // Compensate for visual margin
446             Insets visualMargin2 = getVisualMargin(component2);
447             switch (position) {
448                 case SwingConstants.NORTH :
449                     result -= visualMargin2.bottom;
450                     break;
451                 case SwingConstants.SOUTH :
452                     result -= visualMargin2.top;
453                     break;
454                 case SwingConstants.EAST :
455                     result -= visualMargin2.left;
456                     break;
457                 case SwingConstants.WEST :
458                     result -= visualMargin2.right;
459                 default :
460                     break;
461             }
462         } else {
463 
464             // If the two components have different size styles, we use the
465             // smaller size style to determine the gap
466             int sizeStyle = Math.min(getSizeStyle(component1), getSizeStyle(component2));
467             Insets gap1 = getPreferredGap(component1, type, sizeStyle);
468             Insets gap2 = getPreferredGap(component2, type, sizeStyle);
469 
470             // The AHIG defines the minimal spacing for a component
471             // therefore we use the larger of the two gap values.
472             switch (position) {
473                 case SwingConstants.NORTH :
474                     result = Math.max(gap1.top, gap2.bottom);
475                     break;
476                 case SwingConstants.SOUTH :
477                     result = Math.max(gap1.bottom, gap2.top);
478                     break;
479                 case SwingConstants.EAST :
480                     result = Math.max(gap1.right, gap2.left);
481                     break;
482                 case SwingConstants.WEST :
483                 default :
484                     result = Math.max(gap1.left, gap2.right);
485                     break;
486             }
487 
488             // Compensate for visual margin
489             Insets visualMargin1 = getVisualMargin(component1);
490             Insets visualMargin2 = getVisualMargin(component2);
491 
492             switch (position) {
493                 case SwingConstants.NORTH :
494                     result -= visualMargin1.top + visualMargin2.bottom;
495                     break;
496                 case SwingConstants.SOUTH :
497                     result -= visualMargin1.bottom + visualMargin2.top;
498                     break;
499                 case SwingConstants.EAST :
500                     result -= visualMargin1.right + visualMargin2.left;
501                     break;
502                 case SwingConstants.WEST :
503                     result -= visualMargin1.left + visualMargin2.right;
504                 default :
505                     break;
506             }
507         }
508 
509         return result;
510     }
511 
getPreferredGap(JComponent component, javax.swing.LayoutStyle.ComponentPlacement type, int sizeStyle)512     private Insets getPreferredGap(JComponent component, javax.swing.LayoutStyle.ComponentPlacement type, int sizeStyle) {
513         Insets gap = null;
514 
515         HashMap gapMap;
516 
517         switch (type) {
518             case INDENT :
519                 gapMap = indentGaps; break;
520             case RELATED :
521                 gapMap = relatedGaps; break;
522             case UNRELATED :
523             default :
524                 gapMap = unrelatedGaps; break;
525         }
526 
527         String uid = component.getUIClassID();
528         String style = null;
529         if (uid.equals("ButtonUI") || uid.equals("ToggleButtonUI")) {
530             style = (String) component.getClientProperty("Quaqua.Button.style");
531             if (style == null) {
532                 style = (String) component.getClientProperty("JButton.buttonType");
533             }
534         } else if (uid.equals("ProgressBarUI")) {
535             style = (((JProgressBar) component).getOrientation() == JProgressBar.HORIZONTAL)
536             ? "horizontal"
537             : "vertical";
538         } else if (uid.equals("SliderUI")) {
539             style = (((JSlider) component).getOrientation() == JSlider.HORIZONTAL)
540             ? "horizontal"
541             : "vertical";
542         } else if (uid.equals("TabbedPaneUI")) {
543             switch (((JTabbedPane) component).getTabPlacement()) {
544                 case JTabbedPane.TOP : style = "top"; break;
545                 case JTabbedPane.LEFT : style = "left"; break;
546                 case JTabbedPane.BOTTOM : style = "bottom"; break;
547                 case JTabbedPane.RIGHT : style = "right"; break;
548             }
549         }
550         String key = (style == null) ? uid : uid+"."+style;
551         Insets[] gaps = (Insets[]) gapMap.get(key);
552         if (gaps == null) {
553             gaps = (Insets[]) gapMap.get(uid);
554         }
555         if (gaps == null) {
556             gaps = (Insets[]) gapMap.get("default");
557         }
558 
559         return (gaps == null) ? new Insets(0,0,0,0) : gaps[sizeStyle];
560     }
561 
562     /**
563      * Returns the amount of space to position a component inside its
564      * parent.
565      *
566      * @param component the <code>Component</code> being positioned
567      * @param position the position <code>component</code> is being placed
568      *        relative to its parent; one of
569      *        <code>SwingConstants.NORTH</code>,
570      *        <code>SwingConstants.SOUTH</code>,
571      *        <code>SwingConstants.EAST</code> or
572      *        <code>SwingConstants.WEST</code>
573      * @param parent the parent of <code>component</code>; this may differ
574      *        from the actual parent and may be null
575      * @return the amount of space to place between the component and specified
576      *         edge
577      * @throws IllegalArgumentException if <code>position</code> is not
578      *         one of <code>SwingConstants.NORTH</code>,
579      *         <code>SwingConstants.SOUTH</code>,
580      *         <code>SwingConstants.EAST</code> or
581      *         <code>SwingConstants.WEST</code>;
582      *         or <code>component</code> is null
583      */
getContainerGap(JComponent component, int position, Container parent)584     public int getContainerGap(JComponent component, int position,
585     Container parent) {
586         int result;
587 
588         int sizeStyle = Math.min(getSizeStyle(component), getSizeStyle(parent));
589 
590         // Compute gap
591         Insets gap = getContainerGap(parent, sizeStyle);
592 
593         switch (position) {
594             case SwingConstants.NORTH :
595                 result = gap.top;
596                 break;
597             case SwingConstants.SOUTH :
598                 result = gap.bottom;
599                 break;
600             case SwingConstants.EAST :
601                 result = gap.right;
602                 break;
603             case SwingConstants.WEST :
604             default :
605                 result = gap.left;
606                 break;
607         }
608 
609         // Compensate for visual margin
610         Insets visualMargin = getVisualMargin(component);
611         switch (position) {
612             case SwingConstants.NORTH :
613                 result -= visualMargin.top;
614                 break;
615             case SwingConstants.SOUTH :
616                 result -= visualMargin.bottom;
617                 // Radio buttons in Quaqua are 1 pixel too high, in order
618                 // to align their baselines with other components, when no
619                 // baseline aware layout manager is used.
620                 if (component instanceof JRadioButton) {
621                     result--;
622                 }
623                 break;
624             case SwingConstants.EAST :
625                 result -= visualMargin.left;
626                 break;
627             case SwingConstants.WEST :
628                 result -= visualMargin.right;
629             default :
630                 break;
631         }
632 
633         return result;
634     }
635 
getContainerGap(Container container, int sizeStyle)636     private Insets getContainerGap(Container container, int sizeStyle) {
637         Insets gap = null;
638 
639         HashMap gapMap = containerGaps;
640 
641         String uid;
642         if (container instanceof JComponent) {
643             uid = ((JComponent) container).getUIClassID();
644         } else if (container instanceof Dialog) {
645             uid = "Dialog";
646         } else if (container instanceof Frame) {
647             uid = "Frame";
648         } else if (container instanceof java.applet.Applet) {
649             uid = "Applet";
650         } else if (container instanceof Panel) {
651             uid = "Panel";
652         } else {
653             uid = "default";
654         }
655 
656         String style = null;
657         // FIXME insert style code here for JInternalFrame with palette style
658         String key = (style == null) ? uid : uid+"."+style;
659 
660         Insets[] gaps = (Insets[]) gapMap.get(key);
661         if (gaps == null) {
662             gaps = (Insets[]) gapMap.get(uid);
663         }
664         if (gaps == null) {
665             gaps = (Insets[]) gapMap.get("default");
666         }
667         if (gaps == null) {
668             if (DEBUG) System.out.println("AquaLayoutStyle.noGaps for "+uid);
669         }
670         return (gaps == null) ? new Insets(0,0,0,0) : gaps[sizeStyle];
671     }
672 
getVisualMargin(JComponent component)673     private Insets getVisualMargin(JComponent component) {
674         try {
675             Method getUI = component.getClass().getMethod("getUI", new Class[0]);
676             Object ui = getUI.invoke(component, new Object[0]);
677             if (ui instanceof VisuallyLayoutable) {
678                 Dimension size = component.getPreferredSize();
679                 Rectangle visualBounds = ((VisuallyLayoutable) ui).getVisualBounds(component, VisuallyLayoutable.COMPONENT_BOUNDS, size.width, size.height);
680                 return new Insets(visualBounds.y, visualBounds.x, size.height - visualBounds.y - visualBounds.height, size.width - visualBounds.x - visualBounds.width);
681             }
682         } catch (NoSuchMethodException e) {
683             // This can happen for subclasses of JComponent, which do
684             // not have an UI delegate.
685         // Fall through.
686         } catch (Exception e) {
687             InternalError error = new InternalError();
688             //error.initCause(e);
689             throw error;
690         }
691         return new Insets(0,0,0,0);
692     }
693 
getVisualIndent(JComponent component)694     private Insets getVisualIndent(JComponent component) {
695         try {
696             Method getUI = component.getClass().getMethod("getUI", new Class[0]);
697             Object ui = getUI.invoke(component, new Object[0]);
698             if (ui instanceof VisuallyLayoutable) {
699                 Dimension size = component.getPreferredSize();
700                 Rectangle visualBounds = ((VisuallyLayoutable) ui).getVisualBounds(component, VisuallyLayoutable.TEXT_BOUNDS, size.width, size.height);
701                 return new Insets(visualBounds.y, visualBounds.x, size.height - visualBounds.y - visualBounds.height, size.width - visualBounds.x - visualBounds.width);
702             }
703         } catch (NoSuchMethodException e) {
704             // This can happen for subclasses of JComponent, which do
705             // not have an UI delegate.
706         // Fall through
707         } catch (Exception e) {
708             InternalError error = new InternalError();
709            // error.initCause(e);
710             throw error;
711         }
712         return new Insets(0,0,0,0);
713     }
714     /**
715      * Returns the size style of a specified component.
716      *
717      * @return REGULAR, SMALL or MINI.
718      */
getSizeStyle(Component c)719     private int getSizeStyle(Component c) {
720         // Aqua components have a different style depending on the
721         // font size used.
722         // 13 Point = Regular
723         // 11 Point = Small
724         //  9 Point = Mini
725         int fontSize = c.getFont().getSize();
726         return (fontSize >= 13) ? REGULAR : ((fontSize > 9) ? SMALL : MINI);
727     }
728 }
729