001    package util.swing;
002    
003    import java.awt.Component;
004    import java.awt.Container;
005    import java.awt.Dimension;
006    import java.awt.Insets;
007    import java.awt.LayoutManager;
008    
009    import javax.swing.BorderFactory;
010    import javax.swing.JComponent;
011    import javax.swing.JLabel;
012    import javax.swing.JPanel;
013    
014    /**
015     * A Swing Panel realising a standard option panel layout. That means, option
016     * titles are displayed right in front of their corresponding components. All
017     * alignment is done automatically.
018     * 
019     * @author Thomas Ryssel
020     * @since v3.3
021     */
022    public class JOptionPanel extends JPanel {
023    
024            /**
025             * ID for Serialization.
026             */
027            private static final long serialVersionUID = -7494273537708600891L;
028            
029            /**
030             * The title text (will be shown using TitledBorder).
031             */
032            private String m_sText = null;
033            
034            /**
035             * Whether or not initialization is finished. This is mainly used to
036             * guard the LayoutManager once it is set.
037             */
038            private boolean m_fInitDone = false;
039            
040            /**
041             * A LayoutManager for positioning components in the way wished for.
042             * This could also be done using several components and - for example -
043             * BoxLayout. But there are some disadvantages concerning exact
044             * positioning. Also here we can correctly handle "filling cases", e.g.
045             * components using all the space that is possibly left (both horizontally
046             * and vertically). Alignment (e.g. putting all stuff in the center) can
047             * be done quite easily that way, too.
048             * 
049             * @author Thomas Ryssel
050             */
051            private class OptionLayout implements LayoutManager {
052    
053                    /**
054                     * The horizontal gap (between titles and components).
055                     */
056                    private int m_nHgap;
057                    
058                    /**
059                     * The vertical gap.
060                     */
061                    private int n_nVgap = 0;
062                    
063                    /**
064                     * The horizontal alignment.
065                     */
066                    private int m_nHorizontalAlign;
067                    
068                    /**
069                     * The vertical alignment.
070                     */
071                    private int m_nVerticalAlign;
072                    
073                    /**
074                     * Create a new OptionLayout.
075                     * 
076                     * @param hgap   Horizontal gap.
077                     * @param vgap   Vertical gap.
078                     * @param horiz  Horizontal alignment.
079                     * @param vert   Vertical alignment.
080                     */
081                    public OptionLayout(int hgap, int vgap, int horiz, int vert) {
082                            this.n_nVgap = vgap;
083                            this.m_nHgap = hgap;
084                            this.m_nHorizontalAlign = horiz;
085                            this.m_nVerticalAlign = vert;
086                    }
087                    
088                    /**
089                     * Add a special layout component. Not used in this implementation.
090                     */
091                    public void addLayoutComponent(String name, Component comp) {
092                    }
093    
094                    /**
095                     * Remove a special layout component. Not used in this implementation.
096                     */
097                    public void removeLayoutComponent(Component comp) {
098                    }
099    
100                    /**
101                     * Get the preferred Dimension of the layout.
102                     */
103                    public Dimension preferredLayoutSize(Container parent) {
104                            synchronized (parent.getTreeLock()) {
105                                    Insets ins = parent.getInsets();
106                                    int n = parent.getComponentCount() / 2;
107                                    int wl = 0, wr = 0, w;
108                                    int h = 0;
109                                    int count = parent.getComponentCount();
110                                    for (int i = 0; i < count; i += 2) {
111                                            int lcw = parent.getComponent(i).getPreferredSize().width;
112                                            if (lcw > wl) {
113                                                    wl = lcw;
114                                            }
115                                    }
116                                    for (int i = 0; i < count; i += 2) {
117                                            int rcw = parent.getComponent(i + 1).getPreferredSize().width;
118                                            if (rcw == Integer.MAX_VALUE && parent.getSize().width > 0) {
119                                                    rcw = parent.getSize().width - wl - m_nHgap;
120                                            }
121                                            if (rcw > wr) {
122                                                    wr = rcw;
123                                            }
124                                            
125                                            if (parent.getComponent(i + 1).getPreferredSize().height == Integer.MAX_VALUE) {
126                                                    h += parent.getSize().height;
127                                            } else {
128                                                    h += parent.getComponent(i + 1).getPreferredSize().height;
129                                            }
130                                    }
131    
132                                    h += (n - 1) * n_nVgap + ins.top + ins.bottom;
133                                    w = wl + wr + m_nHgap + ins.left + ins.right; 
134                                    
135                                    return new Dimension(w, h);
136                            }
137                    }
138    
139                    /**
140                     * Get the minimum Dimension of the layout.
141                     */
142                    public Dimension minimumLayoutSize(Container parent) {
143                            return preferredLayoutSize(parent);
144                    }
145    
146                    /**
147                     * Do the actual layout work.
148                     */
149                    public void layoutContainer(Container parent) {
150                            synchronized (parent.getTreeLock()) {
151                                    Insets ins = parent.getInsets();
152                                    int topoffset = 0, leftoffset = 0;
153                                    Dimension pref = preferredLayoutSize(parent);
154                                    
155                                    
156                                    switch (m_nHorizontalAlign) {
157                                            case LEFT:
158                                                    leftoffset = 0;
159                                                    break;
160                                            case CENTER:
161                                                    if (parent.getSize().width > pref.width) {
162                                                            leftoffset = (parent.getSize().width - pref.width - ins.left - ins.right) / 2;
163                                                    }
164                                                    break;
165                                            case RIGHT:
166                                                    if (parent.getSize().width > pref.width) {
167                                                            leftoffset = parent.getSize().width - pref.width - ins.right - m_nHgap; 
168                                                    }
169                                                    break;
170                                    }
171                                    switch (m_nVerticalAlign) {
172                                            case TOP:
173                                                    topoffset = 0;
174                                                    break;
175                                            case CENTER:
176                                                    if (parent.getSize().height > pref.height) {
177                                                            topoffset = (parent.getSize().height - pref.height - ins.top - ins.bottom) / 2;
178                                                    }
179                                                    break;
180                                            case BOTTOM:
181                                                    if (parent.getSize().height > pref.height) {
182                                                            topoffset = parent.getSize().height - pref.height - ins.bottom;
183                                                    }
184                                                    break;
185                                    }
186                                    
187                                    int leftsize = 0;
188                                    int top = ins.top + topoffset;
189                                    int heightleft = parent.getSize().height - ins.top - ins.bottom;
190                                    for (int i = 0; i < parent.getComponentCount(); i += 2) {
191                                            Component comp = parent.getComponent(i);
192                                            if (comp.getPreferredSize().width > leftsize) {
193                                                    leftsize = comp.getPreferredSize().width;
194                                            }
195                                    }
196                                    int rightsize = parent.getSize().width - leftsize - ins.left - 
197                                                                ins.right - m_nHgap;
198                                    for (int i = 0; i < parent.getComponentCount(); i += 2) {
199                                            Component compleft  = parent.getComponent(i);
200                                            Component compright = parent.getComponent(i + 1);
201                                            int height = compright.getPreferredSize().height;
202                                            if (height == Integer.MAX_VALUE) {
203                                                    height = heightleft;
204                                                    heightleft = 0;
205                                            } else {
206                                                    heightleft -= height + n_nVgap;
207                                            }
208                                            compleft.setBounds(ins.left, top, leftsize + leftoffset, compleft.getPreferredSize().height);
209                                            
210                                            if (compright.getPreferredSize().width == Integer.MAX_VALUE) {
211                                                    compright.setBounds(m_nHgap + ins.left + leftsize + leftoffset, top, rightsize, height);
212                                            } else {
213                                                    compright.setBounds(m_nHgap + ins.left + leftsize + leftoffset, top, compright.getPreferredSize().width, height);
214                                            }
215                                            top += height + n_nVgap;
216                                            if (heightleft == 0) {
217                                                    break;
218                                            }
219                                    }
220                                    
221                                    
222                            }
223                    }
224            }
225            
226            
227            /**
228             * Create a new JOptionPanel without title and horizontal and vertical alignment 
229             * set to LEFT and TOP, respectively. Horizontal gap will be 16 (pixels) and 
230             * vertical gap will be set to 4 (pixels). 
231             */
232            public JOptionPanel() {
233                    this(null);
234            }
235    
236            /**
237             * Create a new JOptionPanel with horizontal and vertical alignment set to
238             * LEFT and TOP, respectively. Horizontal gap will be 16 (pixels) and vertical
239             * gap will be set to 4 (pixels). 
240             * 
241             * @param text       The title text of the panel. Can be set to <code>null</code>
242             *                   to indicate that there is no title.
243             */
244            public JOptionPanel(String text) {
245                    this(text, 16, 4);
246            }
247            
248            /**
249             * Create a new JOptionPanel with horizontal and vertical alignment set to
250             * LEFT and TOP, respectively.
251             * 
252             * @param text       The title text of the panel. Can be set to <code>null</code>
253             *                   to indicate that there is no title.
254             * @param hgap       Horizontal gap (between titles and components).
255             * @param vgap       Vertical gap.
256             */
257            public JOptionPanel(String text, int hgap, int vgap) {
258                    this(text, hgap, vgap, LEFT, TOP);
259            }
260            
261            /**
262             * Create a new JOptionPanel.
263             * 
264             * @param text       The title text of the panel. Can be set to <code>null</code>
265             *                   to indicate that there is no title.
266             * @param hgap       Horizontal gap (between titles and components).
267             * @param vgap       Vertical gap.
268             * @param horizAlign Horizontal aligment. Can be either {@link #LEFT}, {@link #RIGHT}
269             *                   or {@link #CENTER}.
270             * @param vertAlign  Vertical alignment. Can be either {@link #TOP}, {@link #BOTTOM}
271             *                   or {@link #CENTER}.
272             */
273            public JOptionPanel(String text, int hgap, int vgap, int horizAlign, int vertAlign) {
274                    setText(text);
275                    setLayout(new OptionLayout(hgap, vgap, horizAlign, vertAlign));
276                    m_fInitDone = true;
277            }
278            
279            /**
280             * Set the option panel's title text.
281             * 
282             * @param text The title text of the panel. Can be set to <code>null</code>
283             *             to indicate that there is no title.
284             */
285            public void setText(String text) {
286                    this.m_sText = text;
287                    if (text != null) {
288                            setBorder(BorderFactory.createTitledBorder(this.m_sText));
289                    } else {
290                            setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
291                    }
292            }
293            
294            /**
295             * Get the option panel's title text.
296             * 
297             * @return The title text of the panel.
298             */
299            public String getText() {
300                    return m_sText;
301            }
302            
303            /**
304             * Add the given component under the given title. The layout will be done
305             * according to the preferred dimensions of the components. 
306             * 
307             * <p>If the component's preferred width is set to <code>Integer.MAX_VALUE</code>, 
308             * it will take up all possibly available horizontal space. If the component's 
309             * preferred height is set to <code>Integer.MAX_VALUE</code>, it will take up 
310             * all vertical space that is left. Note that there cannot be any other components 
311             * underneath such a component. 
312             * 
313             * <p>Components are always shown in the order they have been added (from top
314             * to bottom).</p>
315             * 
316             * @param title     The option's title. If it is no empty string, a colon (":") 
317             *                  will be added automatically.
318             * @param component The component representing the option in however way.
319             */
320            public void addOption(String title, JComponent component) {
321                    JLabel label = new JLabel("", JLabel.RIGHT);
322                    if (!title.equals("")) {
323                            label.setText(" " + title + ":");
324                    }
325                    add(label);
326                    add(component);
327            }
328            
329            /**
330             * Add a "display only" option. the <code>text</code>'s <code>toString()</code>
331             * method will be used to construct a <code>JLabel</code> that will be displayed.
332             * That is merely a convenience method that allows you to simply display information.
333             * 
334             * <p>For example, you could do something like: <code>addOption("File", "test.txt");</code>.
335             * 
336             * @param title The option's title. If it is no empty string, a colon (":") 
337             *              will be added automatically.
338             * @param text  The object representing the text to show.
339             */
340            public void addOption(String title, Object text) {
341                    addOption(title, new JLabel(text.toString()));
342            }
343            
344            
345            /**
346             * Get the n-th option component (in the order they were added).
347             * 
348             * @param n Number of the component to retrieve.
349             * 
350             * @return The component requested.
351             */
352            public Component getOptionComponent(int n) {
353                    return getComponent(n * 2 + 1);
354            }
355            
356            /**
357             * Set layout manager. Overridden so that it cannot be used anymore
358             * after initialization.
359             */
360            public void setLayout(LayoutManager mgr) {
361                    if (!m_fInitDone) {
362                            super.setLayout(mgr);
363                            return;
364                    }
365                    throw new UnsupportedOperationException("This does not allow to change the" +
366                                    " layout manager. Choose JPanel instead");
367            }
368            
369            /**
370             * Constant representing center alignment.
371             */
372            public static final int CENTER = 0;
373            
374            /**
375             * Constant representing left alignment.
376             */
377            public static final int LEFT   = 1;
378            
379            /**
380             * Constant representing right alignment.
381             */
382            public static final int RIGHT  = 2;
383            
384            /**
385             * Constant representing top alignment.
386             */
387            public static final int TOP    = 3;
388            
389            /**
390             * Constant representing bottom alignment.
391             */
392            public static final int BOTTOM = 4;
393    }