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;
034import java.util.ResourceBundle;
035
036import javax.swing.JComponent;
037import javax.swing.JLabel;
038import javax.swing.JPanel;
039
040import com.jgoodies.forms.factories.FormFactory;
041import com.jgoodies.forms.layout.ConstantSize;
042import com.jgoodies.forms.layout.FormLayout;
043import com.jgoodies.forms.layout.RowSpec;
044
045/**
046 * Provides a means to build form-oriented panels quickly and consistently
047 * using the {@link FormLayout}. This builder combines frequently used
048 * panel building steps: add a new row, add a label, proceed to the next
049 * data column, then add a component.<p>
050 *
051 * The extra value lies in the <code>#append</code> methods that
052 * append gap rows and component rows if necessary and then add
053 * the given components. They are built upon the superclass behavior
054 * <code>#appendRow</code> and the set of <code>#add</code> methods.
055 * A set of component appenders allows to add a textual label and
056 * associated component in a single step.<p>
057 *
058 * This builder can map resource keys to internationalized (i15d) texts
059 * when creating text labels, titles and titled separators. Therefore
060 * you must specify a <code>ResourceBundle</code> in the constructor.
061 * The builder methods throw an <code>IllegalStateException</code> if one
062 * of the mapping builder methods is invoked and no bundle has been set.<p>
063 *
064 * You can configure the build process by setting a leading column,
065 * enabling the row grouping and by modifying the gaps between normal
066 * lines and between paragraphs. The leading column will be honored
067 * if the cursor proceeds to the next row. All appended components
068 * start in the specified lead column, except appended separators that
069 * span all columns.<p>
070 *
071 * It is tempting to use the DefaultFormBuilder all the time and
072 * to let it add rows automatically. Use a simpler style if it increases
073 * the code readability. Explicit row specifications and cell constraints
074 * make your layout easier to understand - but harder to maintain.
075 * See also the accompanying tutorial sources and the Tips &amp; Tricks
076 * that are part of the Forms documentation.<p>
077 *
078 * Sometimes a form consists of many standardized rows but has a few
079 * rows that require a customization. The DefaultFormBuilder can do everything
080 * that the superclasses {@link com.jgoodies.forms.builder.AbstractFormBuilder}
081 * and {@link com.jgoodies.forms.builder.PanelBuilder} can do;
082 * among other things: appending new rows and moving the cursor.
083 * Again, ask yourself if the DefaultFormBuilder is the appropriate builder.
084 * As a rule of thumb you should have more components than builder commands.
085 * There are different ways to add custom rows. Find below example code
086 * that presents and compares the pros and cons of three approaches.<p>
087 *
088 * The texts used in methods <code>#append(String, ...)</code> and
089 * <code>#appendTitle(String)</code> as well as the localized texts used in
090 * methods <code>#appendI15d</code> and <code>#appendI15dTitle</code>
091 * can contain an optional mnemonic marker. The mnemonic and mnemonic index
092 * are indicated by a single ampersand (<tt>&amp;</tt>).
093 * For example <tt>&quot;&amp;Save&quot;</tt>, or
094 * <tt>&quot;Save&nbsp;&amp;as&quot;</tt>. To use the ampersand itself,
095 * duplicate it, for example <tt>&quot;Look&amp;&amp;Feel&quot;</tt>.<p>
096 *
097 * <strong>Example:</strong>
098 * <pre>
099 * public void build() {
100 *     FormLayout layout = new FormLayout(
101 *         "right:max(40dlu;pref), 3dlu, 80dlu, 7dlu, " // 1st major column
102 *       + "right:max(40dlu;pref), 3dlu, 80dlu",        // 2nd major column
103 *         "");                                         // add rows dynamically
104 *     DefaultFormBuilder builder = new DefaultFormBuilder(layout);
105 *     builder.setDefaultDialogBorder();
106 *
107 *     builder.appendSeparator("Flange");
108 *
109 *     builder.append("Identifier", identifierField);
110 *     builder.nextLine();
111 *
112 *     builder.append("PTI [kW]",   new JTextField());
113 *     builder.append("Power [kW]", new JTextField());
114 *
115 *     builder.append("s [mm]",     new JTextField());
116 *     builder.nextLine();
117 *
118 *     builder.appendSeparator("Diameters");
119 *
120 *     builder.append("da [mm]",    new JTextField());
121 *     builder.append("di [mm]",    new JTextField());
122 *
123 *     builder.append("da2 [mm]",   new JTextField());
124 *     builder.append("di2 [mm]",   new JTextField());
125 *
126 *     builder.append("R [mm]",     new JTextField());
127 *     builder.append("D [mm]",     new JTextField());
128 *
129 *     builder.appendSeparator("Criteria");
130 *
131 *     builder.append("Location",   buildLocationComboBox());
132 *     builder.append("k-factor",   new JTextField());
133 *
134 *     builder.appendSeparator("Bolts");
135 *
136 *     builder.append("Material",   ViewerUIFactory.buildMaterialComboBox());
137 *     builder.nextLine();
138 *
139 *     builder.append("Numbers",    new JTextField());
140 *     builder.nextLine();
141 *
142 *     builder.append("ds [mm]",    new JTextField());
143 * }
144 * </pre><p>
145 *
146 * <strong>Custom Row Example:</strong>
147 * <pre>
148 * public JComponent buildPanel() {
149 *     initComponents();
150 *
151 *     FormLayout layout = new FormLayout(
152 *             "right:pref, 3dlu, default:grow",
153 *             "");
154 *     DefaultFormBuilder builder = new DefaultFormBuilder(layout);
155 *     builder.setDefaultDialogBorder();
156 *     builder.setRowGroupingEnabled(true);
157 *
158 *     CellConstraints cc = new CellConstraints();
159 *
160 *     // In this approach, we add a gap and a custom row.
161 *     // The advantage of this approach is, that we can express
162 *     // the row spec and comment area cell constraints freely.
163 *     // The disadvantage is the misalignment of the leading label.
164 *     // Also the row's height may be inconsistent with other rows.
165 *     builder.appendSeparator("Single Custom Row");
166 *     builder.append("Name", name1Field);
167 *     builder.appendRow(builder.getLineGapSpec());
168 *     builder.appendRow(new RowSpec("top:31dlu")); // Assumes line is 14, gap is 3
169 *     builder.nextLine(2);
170 *     builder.append("Comment");
171 *     builder.add(new JScrollPane(comment1Area),
172 *                 cc.xy(builder.getColumn(), builder.getRow(), "fill, fill"));
173 *     builder.nextLine();
174 *
175 *     // In this approach, we append a standard row with gap before it.
176 *     // The advantage is, that the leading label is aligned well.
177 *     // The disadvantage is that the comment area now spans
178 *     // multiple cells and is slightly less flexible.
179 *     // Also the row's height may be inconsistent with other rows.
180 *     builder.appendSeparator("Standard + Custom Row");
181 *     builder.append("Name", name2Field);
182 *     builder.append("Comment");
183 *     builder.appendRow(new RowSpec("17dlu")); // Assumes line is 14, gap is 3
184 *     builder.add(new JScrollPane(comment2Area),
185 *                 cc.xywh(builder.getColumn(), builder.getRow(), 1, 2));
186 *     builder.nextLine(2);
187 *
188 *     // In this approach, we append two standard rows with associated gaps.
189 *     // The advantage is, that the leading label is aligned well,
190 *     // and the height is consistent with other rows.
191 *     // The disadvantage is that the comment area now spans
192 *     // multiple cells and is slightly less flexible.
193 *     builder.appendSeparator("Two Standard Rows");
194 *     builder.append("Name", name3Field);
195 *     builder.append("Comment");
196 *     builder.nextLine();
197 *     builder.append("");
198 *     builder.nextRow(-2);
199 *     builder.add(new JScrollPane(comment3Area),
200 *                 cc.xywh(builder.getColumn(), builder.getRow(), 1, 3));
201 *
202 *     return builder.getPanel();
203 * }
204 * </pre><p>
205 *
206 * TODO: Consider adding a method for appending a component that spans the
207 * remaining columns in the current row. Method name candidates are
208 * <code>#appendFullSpan</code> and <code>#appendRemaining</code>.
209 *
210 * @author        Karsten Lentzsch
211 * @version $Revision$
212 * @since 1.0.3
213 *
214 * @see        com.jgoodies.forms.builder.AbstractFormBuilder
215 * @see        com.jgoodies.forms.factories.FormFactory
216 * @see        com.jgoodies.forms.layout.FormLayout
217 */
218public final class DefaultFormBuilder extends I15dPanelBuilder {
219
220    /**
221     * Holds the row specification that is reused to describe
222     * the constant gaps between component lines.
223     */
224    private RowSpec lineGapSpec = FormFactory.LINE_GAP_ROWSPEC;
225
226    /**
227     * Holds the row specification that describes the constant gaps
228     * between paragraphs.
229     */
230    private RowSpec paragraphGapSpec = FormFactory.PARAGRAPH_GAP_ROWSPEC;
231
232    /**
233     * Holds the offset of the leading column - often 0 or 1.
234     *
235     * @see #getLeadingColumnOffset()
236     * @see #setLeadingColumnOffset(int)
237     * @see #getLeadingColumn()
238     */
239    private int leadingColumnOffset = 0;
240
241    /**
242     * Determines whether new data rows are being grouped or not.
243     *
244     * @see #isRowGroupingEnabled()
245     * @see #setRowGroupingEnabled(boolean)
246     */
247    private boolean rowGroupingEnabled = false;
248
249    // Instance Creation ****************************************************
250
251    /**
252     * Constructs an instance of <code>DefaultFormBuilder</code> for the given
253     * layout.
254     *
255     * @param layout        the <code>FormLayout</code> to be used
256     */
257    public DefaultFormBuilder(FormLayout layout) {
258        this(new JPanel(null), layout);
259    }
260
261    /**
262     * Constructs an instance of <code>DefaultFormBuilder</code> for the given
263     * panel and layout.
264     *
265     * @param layout    the <code>FormLayout</code> to be used
266     * @param panel     the layout container
267     */
268    public DefaultFormBuilder(FormLayout layout, JPanel panel) {
269        this(panel, layout, null);
270    }
271
272    /**
273     * Constructs an instance of <code>DefaultFormBuilder</code> for the given
274     * layout and resource bundle.
275     *
276     * @param layout    the <code>FormLayout</code> to be used
277     * @param bundle    the <code>ResourceBundle</code> used to lookup i15d
278     * strings
279     */
280    public DefaultFormBuilder(FormLayout layout, ResourceBundle bundle) {
281        this(new JPanel(null), layout, bundle);
282    }
283
284    /**
285     * Constructs an instance of <code>DefaultFormBuilder</code> for the given
286     * panel, layout and resource bundle.
287     *
288     * @param layout    the <code>FormLayout</code> to be used
289     * @param panel     the layout container
290     * @param bundle    the <code>ResourceBundle</code> used to lookup i15d
291     * strings
292     */
293    public DefaultFormBuilder(FormLayout layout, ResourceBundle bundle,
294            JPanel panel) {
295        super(layout, bundle, panel);
296    }
297
298    /**
299     * Constructs an instance of <code>DefaultFormBuilder</code> for the given
300     * panel and layout.
301     *
302     * @param panel     the layout container
303     * @param layout    the <code>FormLayout</code> to be used
304     *
305     * @deprecated Replaced by {@link #DefaultFormBuilder(FormLayout, JPanel)}.
306     */
307    @Deprecated
308    public DefaultFormBuilder(JPanel panel, FormLayout layout) {
309        this(layout, null, panel);
310    }
311
312    /**
313     * Constructs an instance of <code>DefaultFormBuilder</code> for the given
314     * panel, layout and resource bundle.
315     *
316     * @param panel     the layout container
317     * @param layout    the <code>FormLayout</code> to be used
318     * @param bundle    the <code>ResourceBundle</code> used to lookup i15d
319     * strings
320     *
321     * @deprecated Replaced by {@link #DefaultFormBuilder(FormLayout, ResourceBundle, JPanel)}.
322     */
323    @Deprecated
324    public DefaultFormBuilder(JPanel panel, FormLayout layout,
325            ResourceBundle bundle) {
326        super(layout, bundle, panel);
327    }
328
329    // Settings Gap Sizes ***************************************************
330
331    /**
332     * Returns the row specification that is used to separate component lines.
333     *
334     * @return the <code>RowSpec</code> that is used to separate lines
335     */
336    public RowSpec getLineGapSpec() {
337        return lineGapSpec;
338    }
339
340    /**
341     * Sets the size of gaps between component lines using the given
342     * constant size.<p>
343     *
344     * <strong>Examples:</strong><pre>
345     * builder.setLineGapSize(Sizes.ZERO);
346     * builder.setLineGapSize(Sizes.DLUY9);
347     * builder.setLineGapSize(Sizes.pixel(1));
348     * </pre>
349     *
350     * @param lineGapSize   the <code>ConstantSize</code> that describes
351     *     the size of the gaps between component lines
352     */
353    public void setLineGapSize(ConstantSize lineGapSize) {
354        RowSpec rowSpec = FormFactory.createGapRowSpec(lineGapSize);
355        this.lineGapSpec = rowSpec;
356    }
357
358    /**
359     * Sets the size of gaps between paragraphs using the given
360     * constant size.<p>
361     *
362     * <strong>Examples:</strong><pre>
363     * builder.setParagraphGapSize(Sizes.DLUY14);
364     * builder.setParagraphGapSize(Sizes.dluY(22));
365     * builder.setParagraphGapSize(Sizes.pixel(42));
366     * </pre>
367     *
368     * @param paragraphGapSize   the <code>ConstantSize</code> that describes
369     *     the size of the gaps between paragraphs
370     */
371    public void setParagraphGapSize(ConstantSize paragraphGapSize) {
372        RowSpec rowSpec = FormFactory.createGapRowSpec(paragraphGapSize);
373        this.paragraphGapSpec = rowSpec;
374    }
375
376    /**
377     * Returns the offset of the leading column, often 0 or 1.
378     *
379     * @return the offset of the leading column
380     */
381    public int getLeadingColumnOffset() {
382        return leadingColumnOffset;
383    }
384
385    /**
386     * Sets the offset of the leading column, often 0 or 1.
387     *
388     * @param columnOffset  the new offset of the leading column
389     */
390    public void setLeadingColumnOffset(int columnOffset) {
391        this.leadingColumnOffset = columnOffset;
392    }
393
394    /**
395     * Returns whether new data rows are being grouped or not.
396     *
397     * @return true indicates grouping enabled, false disabled
398     */
399    public boolean isRowGroupingEnabled() {
400        return rowGroupingEnabled;
401    }
402
403    /**
404     * Enables or disables the grouping of new data rows.
405     *
406     * @param enabled  indicates grouping enabled, false disabled
407     */
408    public void setRowGroupingEnabled(boolean enabled) {
409        rowGroupingEnabled = enabled;
410    }
411
412    // Filling Columns ******************************************************
413
414    /**
415     * Adds a component to the panel using the default constraints
416     * with a column span of 1. Then proceeds to the next data column.
417     *
418     * @param component        the component to add
419     */
420    public void append(Component component) {
421        append(component, 1);
422    }
423
424    /**
425     * Adds a component to the panel using the default constraints with
426     * the given columnSpan. Proceeds to the next data column.
427     *
428     * @param component the component to append
429     * @param columnSpan    the column span used to add
430     */
431    public void append(Component component, int columnSpan) {
432        ensureCursorColumnInGrid();
433        ensureHasGapRow(lineGapSpec);
434        ensureHasComponentLine();
435
436        add(component, createLeftAdjustedConstraints(columnSpan));
437        nextColumn(columnSpan + 1);
438    }
439
440    /**
441     * Adds two components to the panel; each component will span a single
442     * data column. Proceeds to the next data column.
443     *
444     * @param c1    the first component to add
445     * @param c2    the second component to add
446     */
447    public void append(Component c1, Component c2) {
448        append(c1);
449        append(c2);
450    }
451
452    /**
453     * Adds three components to the panel; each component will span a single
454     * data column. Proceeds to the next data column.
455     *
456     * @param c1    the first component to add
457     * @param c2    the second component to add
458     * @param c3    the third component to add
459     */
460    public void append(Component c1, Component c2, Component c3) {
461        append(c1);
462        append(c2);
463        append(c3);
464    }
465
466    // Appending Labels with optional components ------------------------------
467
468    /**
469     * Adds a text label to the panel and proceeds to the next column.
470     *
471     * @param textWithMnemonic  the label's text - may mark a mnemonic
472     * @return the added label
473     */
474    public JLabel append(String textWithMnemonic) {
475        JLabel label = getComponentFactory().createLabel(textWithMnemonic);
476        append(label);
477        return label;
478    }
479
480    /**
481     * Adds a text label and component to the panel.
482     * Then proceeds to the next data column.<p>
483     *
484     * The created label is labelling the given component; so the component
485     * gets the focus if the (optional) label mnemonic is pressed.
486     *
487     * @param textWithMnemonic  the label's text - may mark a mnemonic
488     * @param component         the component to add
489     * @return the added label
490     */
491    public JLabel append(String textWithMnemonic, Component component) {
492        return append(textWithMnemonic, component, 1);
493    }
494
495    /**
496     * Adds a text label and component to the panel; the component will span
497     * the specified number columns. Proceeds to the next data column,
498     * and goes to the next line if the boolean flag is set.<p>
499     *
500     * The created label is labelling the given component; so the component
501     * gets the focus if the (optional) label mnemonic is pressed.
502     *
503     * @param textWithMnemonic  the label's text - may mark a mnemonic
504     * @param c                 the component to add
505     * @param nextLine          true forces a next line
506     * @return the added label
507     * @see JLabel#setLabelFor(java.awt.Component)
508     */
509    public JLabel append(String textWithMnemonic, Component c,
510            boolean nextLine) {
511        JLabel label = append(textWithMnemonic, c);
512        if (nextLine) {
513            nextLine();
514        }
515        return label;
516    }
517
518    /**
519     * Adds a text label and component to the panel; the component will span
520     * the specified number columns. Proceeds to the next data column.<p>
521     *
522     * The created label is labelling the given component; so the component
523     * gets the focus if the (optional) label mnemonic is pressed.
524     *
525     * @param textWithMnemonic  the label's text - may mark a mnemonic
526     * @param c                 the component to add
527     * @param columnSpan        number of columns the component shall span
528     * @return the added label
529     * @see JLabel#setLabelFor(java.awt.Component)
530     */
531    public JLabel append(String textWithMnemonic, Component c, int columnSpan) {
532        JLabel label = append(textWithMnemonic);
533        label.setLabelFor(c);
534        append(c, columnSpan);
535        return label;
536    }
537
538    /**
539     * Adds a text label and two components to the panel; each component
540     * will span a single column. Proceeds to the next data column.<p>
541     *
542     * The created label is labelling the first component; so the component
543     * gets the focus if the (optional) label mnemonic is pressed.
544     *
545     * @param textWithMnemonic  the label's text - may mark a mnemonic
546     * @param c1    the first component to add
547     * @param c2    the second component to add
548     * @return the added label
549     */
550    public JLabel append(String textWithMnemonic, Component c1, Component c2) {
551        JLabel label = append(textWithMnemonic, c1);
552        append(c2);
553        return label;
554    }
555
556    /**
557     * Adds a text label and two components to the panel; each component
558     * will span a single column. Proceeds to the next data column.<p>
559     *
560     * The created label is labelling the first component; so the component
561     * gets the focus if the (optional) label mnemonic is pressed.
562     *
563     * @param textWithMnemonic  the label's text - may mark a mnemonic
564     * @param c1      the first component to add
565     * @param c2      the second component to add
566     * @param colSpan the column span for the second component
567     * @return the created label
568     */
569    public JLabel append(String textWithMnemonic, Component c1, Component c2,
570            int colSpan) {
571        JLabel label = append(textWithMnemonic, c1);
572        append(c2, colSpan);
573        return label;
574    }
575
576    /**
577     * Adds a text label and three components to the panel; each component
578     * will span a single column. Proceeds to the next data column.<p>
579     *
580     * The created label is labelling the first component; so the component
581     * gets the focus if the (optional) label mnemonic is pressed.
582     *
583     * @param textWithMnemonic  the label's text - may mark a mnemonic
584     * @param c1    the first component to add
585     * @param c2    the second component to add
586     * @param c3    the third component to add
587     * @return the added label
588     */
589    public JLabel append(String textWithMnemonic, Component c1, Component c2,
590            Component c3) {
591        JLabel label = append(textWithMnemonic, c1, c2);
592        append(c3);
593        return label;
594    }
595
596    /**
597     * Adds a text label and four components to the panel; each component
598     * will span a single column. Proceeds to the next data column.<p>
599     *
600     * The created label is labelling the first component; so the component
601     * gets the focus if the (optional) label mnemonic is pressed.
602     *
603     * @param textWithMnemonic  the label's text - may mark a mnemonic
604     * @param c1    the first component to add
605     * @param c2    the second component to add
606     * @param c3    the third component to add
607     * @param c4    the fourth component to add
608     * @return the added label
609     */
610    public JLabel append(String textWithMnemonic, Component c1, Component c2,
611            Component c3, Component c4) {
612        JLabel label = append(textWithMnemonic, c1, c2, c3);
613        append(c4);
614        return label;
615    }
616
617    // Appending internationalized labels with optional components ------------
618
619    /**
620     * Adds an internationalized (i15d) text label to the panel using
621     * the given resource key and proceeds to the next column.
622     *
623     * @param resourceKey      the resource key for the the label's text
624     * @return the added label
625     */
626    public JLabel appendI15d(String resourceKey) {
627        return append(getI15dString(resourceKey));
628    }
629
630    /**
631     * Adds an internationalized (i15d) text label and component
632     * to the panel. Then proceeds to the next data column.<p>
633     *
634     * The created label is labelling the given component; so the component
635     * gets the focus if the (optional) label mnemonic is pressed.
636     *
637     * @param resourceKey  the resource key for the text to add
638     * @param component  the component to add
639     * @return the added label
640     */
641    public JLabel appendI15d(String resourceKey, Component component) {
642        return append(getI15dString(resourceKey), component, 1);
643    }
644
645    /**
646     * Adds an internationalized (i15d) text label and component
647     * to the panel. Then proceeds to the next data column.
648     * Goes to the next line if the boolean flag is set.<p>
649     *
650     * The created label is labelling the first component; so the component
651     * gets the focus if the (optional) label mnemonic is pressed.
652     *
653     * @param resourceKey  the resource key for the text to add
654     * @param component    the component to add
655     * @param nextLine     true forces a next line
656     * @return the added label
657     */
658    public JLabel appendI15d(String resourceKey, Component component,
659            boolean nextLine) {
660        return append(getI15dString(resourceKey), component, nextLine);
661    }
662
663    /**
664     * Adds an internationalized (i15d) text label to the panel using
665     * the given resource key; then proceeds to the next data column
666     * and adds a component with the given column span.
667     * Proceeds to the next data column.<p>
668     *
669     * The created label is labelling the first component; so the component
670     * gets the focus if the (optional) label mnemonic is pressed.
671     *
672     * @param resourceKey  the resource key for the text to add
673     * @param c           the component to add
674     * @param columnSpan  number of columns the component shall span
675     * @return the added label
676     */
677    public JLabel appendI15d(String resourceKey, Component c, int columnSpan) {
678        return append(getI15dString(resourceKey), c, columnSpan);
679    }
680
681    /**
682     * Adds an internationalized (i15d) text label and two components
683     * to the panel; each component will span a single column.
684     * Proceeds to the next data column.<p>
685     *
686     * The created label is labelling the first component; so the component
687     * gets the focus if the (optional) label mnemonic is pressed.
688     *
689     * @param resourceKey  the resource key for the text to add
690     * @param c1    the first component to add
691     * @param c2    the second component to add
692     * @return the added label
693     */
694    public JLabel appendI15d(String resourceKey, Component c1, Component c2) {
695        return append(getI15dString(resourceKey), c1, c2);
696    }
697
698    /**
699     * Adds an internationalized (i15d) text label and two components
700     * to the panel; each component will span a single column.
701     * Proceeds to the next data column.<p>
702     *
703     * The created label is labelling the first component; so the component
704     * gets the focus if the (optional) label mnemonic is pressed.
705     *
706     * @param resourceKey  the resource key for the text to add
707     * @param c1      the first component to add
708     * @param c2      the second component to add
709     * @param colSpan the column span for the second component
710     * @return the added label
711     */
712    public JLabel appendI15d(String resourceKey, Component c1, Component c2,
713            int colSpan) {
714        return append(getI15dString(resourceKey), c1, c2, colSpan);
715    }
716
717    /**
718     * Adds an internationalized (i15d) text label and three components
719     * to the panel; each component will span a single column.
720     * Proceeds to the next data column.<p>
721     *
722     * The created label is labelling the first component; so the component
723     * gets the focus if the (optional) label mnemonic is pressed.
724     *
725     * @param resourceKey  the resource key for the text to add
726     * @param c1    the first component to add
727     * @param c2    the second component to add
728     * @param c3    the third component to add
729     * @return the added label
730     */
731    public JLabel appendI15d(String resourceKey, Component c1, Component c2,
732            Component c3) {
733        return append(getI15dString(resourceKey), c1, c2, c3);
734    }
735
736    /**
737     * Adds an internationalized (i15d) text label and four components
738     * to the panel; each component will span a single column.
739     * Proceeds to the next data column.<p>
740     *
741     * The created label is labelling the first component; so the component
742     * gets the focus if the (optional) label mnemonic is pressed.
743     *
744     * @param resourceKey  the resource key for the text to add
745     * @param c1    the first component to add
746     * @param c2    the second component to add
747     * @param c3    the third component to add
748     * @param c4    the third component to add
749     * @return the added label
750     */
751    public JLabel appendI15d(String resourceKey, Component c1, Component c2,
752            Component c3, Component c4) {
753        return append(getI15dString(resourceKey), c1, c2, c3, c4);
754    }
755
756    // Adding Titles ----------------------------------------------------------
757
758    /**
759     * Adds a title label to the panel and proceeds to the next column.
760     *
761     * @param textWithMnemonic  the label's text - may mark a mnemonic
762     * @return the added title label
763     */
764    public JLabel appendTitle(String textWithMnemonic) {
765        JLabel titleLabel = getComponentFactory().createTitle(textWithMnemonic);
766        append(titleLabel);
767        return titleLabel;
768    }
769
770    /**
771     * Adds an internationalized title label to the panel and
772     * proceeds to the next column.
773     *
774     * @param resourceKey   the resource key for the title's text
775     * @return the added title label
776     */
777    public JLabel appendI15dTitle(String resourceKey) {
778        return appendTitle(getI15dString(resourceKey));
779    }
780
781    // Appending Separators ---------------------------------------------------
782
783    /**
784     * Adds a separator without text that spans all columns.
785     *
786     * @return the added titled separator
787     */
788    public JComponent appendSeparator() {
789        return appendSeparator("");
790    }
791
792    /**
793     * Adds a separator with the given text that spans all columns.
794     *
795     * @param text      the separator title text
796     * @return the added titled separator
797     */
798    public JComponent appendSeparator(String text) {
799        ensureCursorColumnInGrid();
800        ensureHasGapRow(paragraphGapSpec);
801        ensureHasComponentLine();
802
803        setColumn(super.getLeadingColumn());
804        int columnSpan = getColumnCount();
805        setColumnSpan(getColumnCount());
806        JComponent titledSeparator = addSeparator(text);
807        setColumnSpan(1);
808        nextColumn(columnSpan);
809        return titledSeparator;
810    }
811
812    /**
813     * Appends an internationalized titled separator for
814     * the given resource key that spans all columns.
815     *
816     * @param resourceKey   the resource key for the separator title's text
817     * @return the added titled separator
818     */
819    public JComponent appendI15dSeparator(String resourceKey) {
820        return appendSeparator(getI15dString(resourceKey));
821    }
822
823    // Overriding Superclass Behavior ***************************************
824
825    /**
826     * Returns the leading column. Unlike the superclass this method
827     * honors the column offset.
828     *
829     * @return the leading column
830     */
831    @Override
832    protected int getLeadingColumn() {
833        int column = super.getLeadingColumn();
834        return column + getLeadingColumnOffset() * getColumnIncrementSign();
835    }
836
837    // Adding Rows **********************************************************
838
839    /**
840     * Ensures that the cursor is in the grid. In case it's beyond the
841     * form's right hand side, the cursor is moved to the leading column
842     * of the next line.
843     */
844    private void ensureCursorColumnInGrid() {
845        if (isLeftToRight() && getColumn() > getColumnCount()
846                || !isLeftToRight() && getColumn() < 1) {
847            nextLine();
848        }
849    }
850
851    /**
852     * Ensures that we have a gap row before the next component row.
853     * Checks if the current row is the given <code>RowSpec</code>
854     * and appends this row spec if necessary.
855     *
856     * @param gapRowSpec  the row specification to check for
857     */
858    private void ensureHasGapRow(RowSpec gapRowSpec) {
859        if (getRow() == 1 || getRow() <= getRowCount()) {
860            return;
861        }
862
863        if (getRow() <= getRowCount()) {
864            RowSpec rowSpec = getCursorRowSpec();
865            if (rowSpec == gapRowSpec) {
866                return;
867            }
868        }
869        appendRow(gapRowSpec);
870        nextLine();
871    }
872
873    /**
874     * Ensures that the form has a component row. Adds a component row
875     * if the cursor is beyond the form's bottom.
876     */
877    private void ensureHasComponentLine() {
878        if (getRow() <= getRowCount()) {
879            return;
880        }
881        appendRow(FormFactory.PREF_ROWSPEC);
882        if (isRowGroupingEnabled()) {
883            getLayout().addGroupedRow(getRow());
884        }
885    }
886
887    /**
888     * Looks up and returns the row specification of the current row.
889     *
890     * @return the row specification of the current row
891     */
892    private RowSpec getCursorRowSpec() {
893        return getLayout().getRowSpec(getRow());
894    }
895
896}