001/*
002 * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003 *
004 * Redistribution and use in source and binary forms, with or without
005 * modification, are permitted provided that the following conditions are met:
006 *
007 *  o Redistributions of source code must retain the above copyright notice,
008 *    this list of conditions and the following disclaimer.
009 *
010 *  o Redistributions in binary form must reproduce the above copyright notice,
011 *    this list of conditions and the following disclaimer in the documentation
012 *    and/or other materials provided with the distribution.
013 *
014 *  o Neither the name of JGoodies Karsten Lentzsch nor the names of
015 *    its contributors may be used to endorse or promote products derived
016 *    from this software without specific prior written permission.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030
031package com.jgoodies.forms.builder;
032
033import java.awt.Component;
034
035import javax.swing.JComponent;
036import javax.swing.JLabel;
037import javax.swing.JPanel;
038import javax.swing.SwingConstants;
039import javax.swing.border.Border;
040
041import com.jgoodies.forms.factories.Borders;
042import com.jgoodies.forms.factories.ComponentFactory;
043import com.jgoodies.forms.factories.DefaultComponentFactory;
044import com.jgoodies.forms.layout.CellConstraints;
045import com.jgoodies.forms.layout.FormLayout;
046
047/**
048 * An general purpose panel builder that uses the {@link FormLayout}
049 * to lay out <code>JPanel</code>s. It provides convenience methods
050 * to set a default border and to add labels, titles and titled separators.<p>
051 *
052 * The PanelBuilder is the working horse for layouts when more specialized
053 * builders like the {@link ButtonBarBuilder} or {@link DefaultFormBuilder}
054 * are inappropriate.<p>
055 *
056 * The Forms tutorial includes several examples that present and compare
057 * different style to build with the PanelBuilder: static row numbers
058 * vs. row variable, explicit CellConstraints vs. builder cursor,
059 * static rows vs. dynamically added rows. Also, you may check out the
060 * Tips &amp; Tricks section of the Forms HTML documentation.<p>
061 *
062 * The text arguments passed to the methods <code>#addLabel</code>,
063 * <code>#addTitle</code>, and <code>#addSeparator</code> can contain
064 * an optional mnemonic marker. The mnemonic and mnemonic index
065 * are indicated by a single ampersand (<tt>&amp;</tt>). For example
066 * <tt>&quot;&amp;Save&quot;</tt>, or <tt>&quot;Save&nbsp;&amp;as&quot;</tt>.
067 * To use the ampersand itself duplicate it, for example
068 * <tt>&quot;Look&amp;&amp;Feel&quot;</tt>.<p>
069 *
070 * <strong>Example:</strong><br>
071 * This example creates a panel with 3 columns and 3 rows.
072 * <pre>
073 * FormLayout layout = new FormLayout(
074 *      "right:pref, 6dlu, 50dlu, 4dlu, default",  // columns
075 *      "pref, 3dlu, pref, 3dlu, pref");           // rows
076 *
077 * PanelBuilder builder = new PanelBuilder(layout);
078 * CellConstraints cc = new CellConstraints();
079 * builder.addLabel("&amp;Title",      cc.xy  (1, 1));
080 * builder.add(new JTextField(),   cc.xywh(3, 1, 3, 1));
081 * builder.addLabel("&amp;Price",      cc.xy  (1, 3));
082 * builder.add(new JTextField(),   cc.xy  (3, 3));
083 * builder.addLabel("&amp;Author",     cc.xy  (1, 5));
084 * builder.add(new JTextField(),   cc.xy  (3, 5));
085 * builder.add(new JButton("..."), cc.xy  (5, 5));
086 * return builder.getPanel();
087 * </pre>
088 *
089 * @author  Karsten Lentzsch
090 * @version $Revision$
091 *
092 * @see        com.jgoodies.forms.factories.ComponentFactory
093 * @see     I15dPanelBuilder
094 * @see     DefaultFormBuilder
095 */
096public class PanelBuilder extends AbstractFormBuilder {
097
098    /**
099     * Refers to a factory that is used to create labels,
100     * titles and paragraph separators.
101     */
102    private ComponentFactory componentFactory;
103
104    // Instance Creation ****************************************************
105
106    /**
107     * Constructs an instance of <code>PanelBuilder</code> for the given
108     * layout. Uses an instance of <code>JPanel</code> as layout container
109     * with the given layout as layout manager.
110     *
111     * @param layout  the FormLayout to use
112     */
113    public PanelBuilder(FormLayout layout) {
114        this(layout, new JPanel(null));
115    }
116
117    /**
118     * Constructs an instance of <code>PanelBuilder</code> for the given
119     * FormLayout and layout container.
120     *
121     * @param layout  the FormLayout to use
122     * @param panel   the layout container to build on
123     */
124    public PanelBuilder(FormLayout layout, JPanel panel) {
125        super(layout, panel);
126    }
127
128    /**
129     * Constructs an instance of <code>PanelBuilder</code> for the given
130     * panel and layout.
131     *
132     * @param panel   the layout container to build on
133     * @param layout  the form layout to use
134     *
135     * @deprecated Replaced by {@link #PanelBuilder(FormLayout, JPanel)}.
136     */
137    @Deprecated
138    public PanelBuilder(JPanel panel, FormLayout layout) {
139        super(layout, panel);
140    }
141
142    // Accessors ************************************************************
143
144    /**
145     * Returns the panel used to build the form.
146     *
147     * @return the panel used by this builder to build the form
148     */
149    public final JPanel getPanel() {
150        return (JPanel) getContainer();
151    }
152
153    // Borders **************************************************************
154
155    /**
156     * Sets the panel's border.
157     *
158     * @param border        the border to set
159     */
160    public final void setBorder(Border border) {
161        getPanel().setBorder(border);
162    }
163
164    /**
165     * Sets the default dialog border.
166     *
167     * @see Borders
168     */
169    public final void setDefaultDialogBorder() {
170        setBorder(Borders.DIALOG_BORDER);
171    }
172
173    // Adding Labels **********************************************************
174
175    /**
176     * Adds a textual label to the form using the default constraints.
177     *
178     * <pre>
179     * addLabel("Name");       // No Mnemonic
180     * addLabel("N&amp;ame");      // Mnemonic is 'a'
181     * addLabel("Save &amp;as");   // Mnemonic is the second 'a'
182     * addLabel("Look&amp;&amp;Feel"); // No mnemonic, text is "look&amp;feel"
183     * </pre>
184     *
185     * @param textWithMnemonic   the label's text -
186     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
187     * @return the new label
188     *
189     * @see ComponentFactory
190     */
191    public final JLabel addLabel(String textWithMnemonic) {
192        return addLabel(textWithMnemonic, cellConstraints());
193    }
194
195    /**
196     * Adds a textual label to the form using the specified constraints.
197     *
198     * <pre>
199     * addLabel("Name",       cc.xy(1, 1)); // No Mnemonic
200     * addLabel("N&amp;ame",      cc.xy(1, 1)); // Mnemonic is 'a'
201     * addLabel("Save &amp;as",   cc.xy(1, 1)); // Mnemonic is the second 'a'
202     * addLabel("Look&amp;&amp;Feel", cc.xy(1, 1)); // No mnemonic, text is "look&amp;feel"
203     * </pre>
204     *
205     * @param textWithMnemonic  the label's text -
206     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
207     * @param constraints       the label's cell constraints
208     * @return the new label
209     *
210     * @see ComponentFactory
211     */
212    public final JLabel addLabel(String textWithMnemonic,
213            CellConstraints constraints) {
214        JLabel label = getComponentFactory().createLabel(textWithMnemonic);
215        add(label, constraints);
216        return label;
217    }
218
219    /**
220     * Adds a textual label to the form using the specified constraints.
221     *
222     * <pre>
223     * addLabel("Name",       "1, 1"); // No Mnemonic
224     * addLabel("N&amp;ame",      "1, 1"); // Mnemonic is 'a'
225     * addLabel("Save &amp;as",   "1, 1"); // Mnemonic is the second 'a'
226     * addLabel("Look&amp;&amp;Feel", "1, 1"); // No mnemonic, text is "look&amp;feel"
227     * </pre>
228     *
229     * @param textWithMnemonic    the label's text -
230     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
231     * @param encodedConstraints  a string representation for the constraints
232     * @return the new label
233     *
234     * @see ComponentFactory
235     */
236    public final JLabel addLabel(String textWithMnemonic,
237            String encodedConstraints) {
238        return addLabel(textWithMnemonic,
239                new CellConstraints(encodedConstraints));
240    }
241
242    // Adding Label with related Component ************************************
243
244    /**
245     * Adds a label and component to the panel using the given cell constraints.
246     * Sets the given label as <i>the</i> component label using
247     * {@link JLabel#setLabelFor(java.awt.Component)}.
248     *
249     * <p><strong>Note:</strong> The {@link CellConstraints} objects for the label
250     * and the component must be different. Cell constraints are implicitly
251     * cloned by the <code>FormLayout</code> when added to the container.
252     * However, in this case you may be tempted to reuse a
253     * <code>CellConstraints</code> object in the same way as with many other
254     * builder methods that require a single <code>CellConstraints</code>
255     * parameter.
256     * The pitfall is that the methods <code>CellConstraints.xy*(...)</code>
257     * just set the coordinates but do <em>not</em> create a new instance.
258     * And so the second invocation of <code>xy*(...)</code> overrides
259     * the settings performed in the first invocation before the object
260     * is cloned by the <code>FormLayout</code>.</p>
261     *
262     * <p><strong>Wrong:</strong></p>
263     * <pre>
264     * CellConstraints cc = new CellConstraints();
265     * builder.add(
266     *     nameLabel,
267     *     cc.xy(1, 7),         // will be modified by the code below
268     *     nameField,
269     *     cc.xy(3, 7)          // sets the single instance to (3, 7)
270     * );
271     * </pre>
272     * <p><strong>Correct:</strong></p>
273     * <pre>
274     * // Using a single CellConstraints instance and cloning
275     * CellConstraints cc = new CellConstraints();
276     * builder.add(
277     *     nameLabel,
278     *     (CellConstraints) cc.xy(1, 7).clone(), // cloned before the next modification
279     *     nameField,
280     *     cc.xy(3, 7)                            // sets this instance to (3, 7)
281     * );
282     *
283     * // Using two CellConstraints instances
284     * CellConstraints cc1 = new CellConstraints();
285     * CellConstraints cc2 = new CellConstraints();
286     * builder.add(
287     *     nameLabel,
288     *     cc1.xy(1, 7),       // sets instance 1 to (1, 7)
289     *     nameField,
290     *     cc2.xy(3, 7)        // sets instance 2 to (3, 7)
291     * );
292     * </pre>
293     *
294     * @param label                 the label to add
295     * @param labelConstraints      the label's cell constraints
296     * @param component             the component to add
297     * @param componentConstraints  the component's cell constraints
298     * @return the added label
299     * @exception IllegalArgumentException if the same cell constraints instance
300     *     is used for the label and the component
301     *
302     * @see JLabel#setLabelFor(java.awt.Component)
303     * @see DefaultFormBuilder
304     */
305    public final JLabel add(JLabel label, CellConstraints labelConstraints,
306            Component component, CellConstraints componentConstraints) {
307        if (labelConstraints == componentConstraints) {
308            throw new IllegalArgumentException(
309                    "You must provide two CellConstraints instances, "
310                            + "one for the label and one for the component.\n"
311                            + "Consider using #clone(). See the JavaDocs for details.");
312        }
313
314        add(label, labelConstraints);
315        add(component, componentConstraints);
316        label.setLabelFor(component);
317        return label;
318    }
319
320    /**
321     * Adds a label and component to the panel using the given cell constraints.
322     * Sets the given label as <i>the</i> component label using
323     * {@link JLabel#setLabelFor(java.awt.Component)}.<p>
324     *
325     * <strong>Note:</strong> The {@link CellConstraints} objects for the label
326     * and the component must be different. Cell constraints are implicitly
327     * cloned by the <code>FormLayout</code> when added to the container.
328     * However, in this case you may be tempted to reuse a
329     * <code>CellConstraints</code> object in the same way as with many other
330     * builder methods that require a single <code>CellConstraints</code>
331     * parameter.
332     * The pitfall is that the methods <code>CellConstraints.xy*(...)</code>
333     * just set the coordinates but do <em>not</em> create a new instance.
334     * And so the second invocation of <code>xy*(...)</code> overrides
335     * the settings performed in the first invocation before the object
336     * is cloned by the <code>FormLayout</code>.<p>
337     *
338     * <strong>Wrong:</strong><pre>
339     * builder.addLabel(
340     *     "&amp;Name:",            // Mnemonic is 'N'
341     *     cc.xy(1, 7),         // will be modified by the code below
342     *     nameField,
343     *     cc.xy(3, 7)          // sets the single instance to (3, 7)
344     * );
345     * </pre>
346     * <strong>Correct:</strong><pre>
347     * // Using a single CellConstraints instance and cloning
348     * CellConstraints cc = new CellConstraints();
349     * builder.addLabel(
350     *     "&amp;Name:",
351     *     (CellConstraints) cc.xy(1, 7).clone(), // cloned before the next modification
352     *     nameField,
353     *     cc.xy(3, 7)                            // sets this instance to (3, 7)
354     * );
355     *
356     * // Using two CellConstraints instances
357     * CellConstraints cc1 = new CellConstraints();
358     * CellConstraints cc2 = new CellConstraints();
359     * builder.addLabel(
360     *     "&amp;Name:",           // Mnemonic is 'N'
361     *     cc1.xy(1, 7),       // sets instance 1 to (1, 7)
362     *     nameField,
363     *     cc2.xy(3, 7)        // sets instance 2 to (3, 7)
364     * );
365     * </pre>
366     *
367     * @param textWithMnemonic      the label's text -
368     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
369     * @param labelConstraints      the label's cell constraints
370     * @param component             the component to add
371     * @param componentConstraints  the component's cell constraints
372     * @return the added label
373     * @exception IllegalArgumentException if the same cell constraints instance
374     *     is used for the label and the component
375     *
376     * @see JLabel#setLabelFor(java.awt.Component)
377     * @see ComponentFactory
378     * @see DefaultFormBuilder
379     */
380    public final JLabel addLabel(String textWithMnemonic,
381            CellConstraints labelConstraints, Component component,
382            CellConstraints componentConstraints) {
383
384        if (labelConstraints == componentConstraints) {
385            throw new IllegalArgumentException(
386                    "You must provide two CellConstraints instances, "
387                            + "one for the label and one for the component.\n"
388                            + "Consider using #clone(). See the JavaDocs for details.");
389        }
390
391        JLabel label = addLabel(textWithMnemonic, labelConstraints);
392        add(component, componentConstraints);
393        label.setLabelFor(component);
394        return label;
395    }
396
397    // Adding Titles ----------------------------------------------------------
398
399    /**
400     * Adds a title label to the form using the default constraints.
401     *
402     * <pre>
403     * addTitle("Name");       // No mnemonic
404     * addTitle("N&amp;ame");      // Mnemonic is 'a'
405     * addTitle("Save &amp;as");   // Mnemonic is the second 'a'
406     * addTitle("Look&amp;&amp;Feel"); // No mnemonic, text is Look&amp;Feel
407     * </pre>
408     *
409     * @param textWithMnemonic   the title label's text -
410     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
411     * @return the added title label
412     *
413     * @see ComponentFactory
414     */
415    public final JLabel addTitle(String textWithMnemonic) {
416        return addTitle(textWithMnemonic, cellConstraints());
417    }
418
419    /**
420     * Adds a title label to the form using the specified constraints.
421     *
422     * <pre>
423     * addTitle("Name",       cc.xy(1, 1)); // No mnemonic
424     * addTitle("N&amp;ame",      cc.xy(1, 1)); // Mnemonic is 'a'
425     * addTitle("Save &amp;as",   cc.xy(1, 1)); // Mnemonic is the second 'a'
426     * addTitle("Look&amp;&amp;Feel", cc.xy(1, 1)); // No mnemonic, text is Look&amp;Feel
427     * </pre>
428     *
429     * @param textWithMnemonic   the title label's text -
430     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
431     * @param constraints        the separator's cell constraints
432     * @return the added title label
433     *
434     * @see ComponentFactory
435     */
436    public final JLabel addTitle(String textWithMnemonic,
437            CellConstraints constraints) {
438        JLabel titleLabel = getComponentFactory().createTitle(textWithMnemonic);
439        add(titleLabel, constraints);
440        return titleLabel;
441    }
442
443    /**
444     * Adds a title label to the form using the specified constraints.
445     *
446     * <pre>
447     * addTitle("Name",       "1, 1"); // No mnemonic
448     * addTitle("N&amp;ame",      "1, 1"); // Mnemonic is 'a'
449     * addTitle("Save &amp;as",   "1, 1"); // Mnemonic is the second 'a'
450     * addTitle("Look&amp;&amp;Feel", "1, 1"); // No mnemonic, text is Look&amp;Feel
451     * </pre>
452     *
453     * @param textWithMnemonic   the title label's text -
454     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
455     * @param encodedConstraints  a string representation for the constraints
456     * @return the added title label
457     *
458     * @see ComponentFactory
459     */
460    public final JLabel addTitle(String textWithMnemonic,
461            String encodedConstraints) {
462        return addTitle(textWithMnemonic,
463                new CellConstraints(encodedConstraints));
464    }
465
466    // Adding Separators ------------------------------------------------------
467
468    /**
469     * Adds a titled separator to the form that spans all columns.
470     *
471     * <pre>
472     * addSeparator("Name");       // No Mnemonic
473     * addSeparator("N&amp;ame");      // Mnemonic is 'a'
474     * addSeparator("Save &amp;as");   // Mnemonic is the second 'a'
475     * addSeparator("Look&amp;&amp;Feel"); // No mnemonic, text is "look&amp;feel"
476     * </pre>
477     *
478     * @param textWithMnemonic   the separator label's text -
479     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
480     * @return the added separator
481     */
482    public final JComponent addSeparator(String textWithMnemonic) {
483        return addSeparator(textWithMnemonic, getLayout().getColumnCount());
484    }
485
486    /**
487     * Adds a titled separator to the form using the specified constraints.
488     *
489     * <pre>
490     * addSeparator("Name",       cc.xy(1, 1)); // No Mnemonic
491     * addSeparator("N&amp;ame",      cc.xy(1, 1)); // Mnemonic is 'a'
492     * addSeparator("Save &amp;as",   cc.xy(1, 1)); // Mnemonic is the second 'a'
493     * addSeparator("Look&amp;&amp;Feel", cc.xy(1, 1)); // No mnemonic, text is "look&amp;feel"
494     * </pre>
495     *
496     * @param textWithMnemonic   the separator label's text -
497     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
498     * @param constraints  the separator's cell constraints
499     * @return the added separator
500     */
501    public final JComponent addSeparator(String textWithMnemonic,
502            CellConstraints constraints) {
503        int titleAlignment = isLeftToRight() ? SwingConstants.LEFT
504                : SwingConstants.RIGHT;
505        JComponent titledSeparator = getComponentFactory()
506                .createSeparator(textWithMnemonic, titleAlignment);
507        add(titledSeparator, constraints);
508        return titledSeparator;
509    }
510
511    /**
512     * Adds a titled separator to the form using the specified constraints.
513     *
514     * <pre>
515     * addSeparator("Name",       "1, 1"); // No Mnemonic
516     * addSeparator("N&amp;ame",      "1, 1"); // Mnemonic is 'a'
517     * addSeparator("Save &amp;as",   "1, 1"); // Mnemonic is the second 'a'
518     * addSeparator("Look&amp;&amp;Feel", "1, 1"); // No mnemonic, text is "look&amp;feel"
519     * </pre>
520     *
521     * @param textWithMnemonic   the separator label's text -
522     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
523     * @param encodedConstraints  a string representation for the constraints
524     * @return the added separator
525     */
526    public final JComponent addSeparator(String textWithMnemonic,
527            String encodedConstraints) {
528        return addSeparator(textWithMnemonic,
529                new CellConstraints(encodedConstraints));
530    }
531
532    /**
533     * Adds a titled separator to the form that spans the specified columns.
534     *
535     * <pre>
536     * addSeparator("Name",       3); // No Mnemonic
537     * addSeparator("N&amp;ame",      3); // Mnemonic is 'a'
538     * addSeparator("Save &amp;as",   3); // Mnemonic is the second 'a'
539     * addSeparator("Look&amp;&amp;Feel", 3); // No mnemonic, text is "look&amp;feel"
540     * </pre>
541     *
542     * @param textWithMnemonic   the separator label's text -
543     *     may contain an ampersand (<tt>&amp;</tt>) to mark a mnemonic
544     * @param columnSpan        the number of columns the separator spans
545     * @return the added separator
546     */
547    public final JComponent addSeparator(String textWithMnemonic,
548            int columnSpan) {
549        return addSeparator(textWithMnemonic,
550                createLeftAdjustedConstraints(columnSpan));
551    }
552
553    // Accessing the ComponentFactory *****************************************
554
555    /**
556     * Returns the builder's component factory. If no factory
557     * has been set before, it is lazily initialized using with an instance of
558     * {@link com.jgoodies.forms.factories.DefaultComponentFactory}.
559     *
560     * @return the component factory
561     *
562     * @see #setComponentFactory(ComponentFactory)
563     */
564    public final ComponentFactory getComponentFactory() {
565        if (componentFactory == null) {
566            componentFactory = DefaultComponentFactory.getInstance();
567        }
568        return componentFactory;
569    }
570
571    /**
572     * Sets a new component factory.
573     *
574     * @param newFactory   the component factory to be set
575     *
576     * @see #getComponentFactory()
577     */
578    public final void setComponentFactory(ComponentFactory newFactory) {
579        componentFactory = newFactory;
580    }
581
582}