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