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.awt.ComponentOrientation;
035import java.awt.Container;
036
037import com.jgoodies.forms.factories.FormFactory;
038import com.jgoodies.forms.layout.CellConstraints;
039import com.jgoodies.forms.layout.ColumnSpec;
040import com.jgoodies.forms.layout.FormLayout;
041import com.jgoodies.forms.layout.RowSpec;
042
043/**
044 * An abstract class that minimizes the effort required to implement
045 * non-visual builders that use the {@link FormLayout}.<p>
046 *
047 * Builders hide details of the FormLayout and provide convenience behavior
048 * that assists you in constructing a form.
049 * This class provides a cell cursor that helps you traverse a form while
050 * you add components. Also, it offers several methods to append custom
051 * and logical columns and rows.
052 *
053 * @author Karsten Lentzsch
054 * @version $Revision$
055 *
056 * @see    ButtonBarBuilder
057 * @see    ButtonStackBuilder
058 * @see    PanelBuilder
059 * @see    I15dPanelBuilder
060 * @see    DefaultFormBuilder
061 */
062public abstract class AbstractFormBuilder {
063
064    /**
065     * Holds the layout container that we are building.
066     */
067    private final Container container;
068
069    /**
070     * Holds the instance of <code>FormLayout</code> that is used to
071     * specify, fill and layout this form.
072     */
073    private final FormLayout layout;
074
075    /**
076     * Holds an instance of <code>CellConstraints</code> that will be used to
077     * specify the location, extent and alignments of the component to be
078     * added next.
079     */
080    private CellConstraints currentCellConstraints;
081
082    /**
083     * Specifies if we fill the grid from left to right or right to left.
084     * This value is initialized during the construction from the layout
085     * container's component orientation.
086     *
087     * @see #isLeftToRight()
088     * @see #setLeftToRight(boolean)
089     * @see ComponentOrientation
090     */
091    private boolean leftToRight;
092
093    // Instance Creation ****************************************************
094
095    /**
096     * Constructs an instance of <code>AbstractFormBuilder</code>
097     * for the given FormLayout and layout container.
098     *
099     * @param layout     the {@link FormLayout} to use
100     * @param container  the layout container
101     *
102     * @exception NullPointerException if the layout or container is null
103     */
104    public AbstractFormBuilder(FormLayout layout, Container container) {
105        if (layout == null) {
106            throw new NullPointerException("The layout must not be null.");
107        }
108
109        if (container == null) {
110            throw new NullPointerException(
111                    "The layout container must not be null.");
112        }
113
114        this.container = container;
115        this.layout = layout;
116
117        container.setLayout(layout);
118        currentCellConstraints = new CellConstraints();
119        ComponentOrientation orientation = container.getComponentOrientation();
120        leftToRight = orientation.isLeftToRight()
121                || !orientation.isHorizontal();
122    }
123
124    /**
125     * Constructs an instance of <code>AbstractFormBuilder</code> for the given
126     * container and form layout.
127     *
128     * @param container  the layout container
129     * @param layout     the {@link FormLayout} to use
130     *
131     * @deprecated Replaced by {@link #AbstractFormBuilder(FormLayout, Container)}.
132     */
133    @Deprecated
134    public AbstractFormBuilder(Container container, FormLayout layout) {
135        this(layout, container);
136    }
137
138    // Accessors ************************************************************
139
140    /**
141     * Returns the container used to build the form.
142     *
143     * @return the layout container
144     */
145    public final Container getContainer() {
146        return container;
147    }
148
149    /**
150     * Returns the instance of {@link FormLayout} used to build this form.
151     *
152     * @return the FormLayout
153     */
154    public final FormLayout getLayout() {
155        return layout;
156    }
157
158    /**
159     * Returns the number of columns in the form.
160     *
161     * @return the number of columns
162     */
163    public final int getColumnCount() {
164        return getLayout().getColumnCount();
165    }
166
167    /**
168     * Returns the number of rows in the form.
169     *
170     * @return the number of rows
171     */
172    public final int getRowCount() {
173        return getLayout().getRowCount();
174    }
175
176    // Accessing the Cursor Direction ***************************************
177
178    /**
179     * Returns whether this builder fills the form left-to-right
180     * or right-to-left. The initial value of this property is set
181     * during the builder construction from the layout container's
182     * <code>componentOrientation</code> property.
183     *
184     * @return true indicates left-to-right, false indicates right-to-left
185     *
186     * @see #setLeftToRight(boolean)
187     * @see ComponentOrientation
188     */
189    public final boolean isLeftToRight() {
190        return leftToRight;
191    }
192
193    /**
194     * Sets the form fill direction to left-to-right or right-to-left.
195     * The initial value of this property is set during the builder construction
196     * from the layout container's <code>componentOrientation</code> property.
197     *
198     * @param b   true indicates left-to-right, false right-to-left
199     *
200     * @see #isLeftToRight()
201     * @see ComponentOrientation
202     */
203    public final void setLeftToRight(boolean b) {
204        leftToRight = b;
205    }
206
207    // Accessing the Cursor Location and Extent *****************************
208
209    /**
210     * Returns the cursor's column.
211     *
212     * @return the cursor's column
213     */
214    public final int getColumn() {
215        return currentCellConstraints.gridX;
216    }
217
218    /**
219     * Sets the cursor to the given column.
220     *
221     * @param column    the cursor's new column index
222     */
223    public final void setColumn(int column) {
224        currentCellConstraints.gridX = column;
225    }
226
227    /**
228     * Returns the cursor's row.
229     *
230     * @return the cursor's row
231     */
232    public final int getRow() {
233        return currentCellConstraints.gridY;
234    }
235
236    /**
237     * Sets the cursor to the given row.
238     *
239     * @param row       the cursor's new row index
240     */
241    public final void setRow(int row) {
242        currentCellConstraints.gridY = row;
243    }
244
245    /**
246     * Sets the cursor's column span.
247     *
248     * @param columnSpan    the cursor's new column span (grid width)
249     */
250    public final void setColumnSpan(int columnSpan) {
251        currentCellConstraints.gridWidth = columnSpan;
252    }
253
254    /**
255     * Sets the cursor's row span.
256     *
257     * @param rowSpan    the cursor's new row span (grid height)
258     */
259    public final void setRowSpan(int rowSpan) {
260        currentCellConstraints.gridHeight = rowSpan;
261    }
262
263    /**
264     * Sets the cursor's origin to the given column and row.
265     *
266     * @param column         the new column index
267     * @param row                the new row index
268     */
269    public final void setOrigin(int column, int row) {
270        setColumn(column);
271        setRow(row);
272    }
273
274    /**
275     * Sets the cursor's extent to the given column span and row span.
276     *
277     * @param columnSpan    the new column span (grid width)
278     * @param rowSpan       the new row span (grid height)
279     */
280    public final void setExtent(int columnSpan, int rowSpan) {
281        setColumnSpan(columnSpan);
282        setRowSpan(rowSpan);
283    }
284
285    /**
286     * Sets the cell bounds (location and extent) to the given column, row,
287     * column span and row span.
288     *
289     * @param column       the new column index (grid x)
290     * @param row          the new row index         (grid y)
291     * @param columnSpan   the new column span  (grid width)
292     * @param rowSpan      the new row span     (grid height)
293     */
294    public final void setBounds(int column, int row, int columnSpan,
295            int rowSpan) {
296        setColumn(column);
297        setRow(row);
298        setColumnSpan(columnSpan);
299        setRowSpan(rowSpan);
300    }
301
302    /**
303     * Moves to the next column, does the same as #nextColumn(1).
304     */
305    public final void nextColumn() {
306        nextColumn(1);
307    }
308
309    /**
310     * Moves to the next column.
311     *
312     * @param columns         number of columns to move
313     */
314    public final void nextColumn(int columns) {
315        currentCellConstraints.gridX += columns * getColumnIncrementSign();
316    }
317
318    /**
319     * Increases the row by one; does the same as #nextRow(1).
320     */
321    public final void nextRow() {
322        nextRow(1);
323    }
324
325    /**
326     * Increases the row by the specified rows.
327     *
328     * @param rows         number of rows to move
329     */
330    public final void nextRow(int rows) {
331        currentCellConstraints.gridY += rows;
332    }
333
334    /**
335     * Moves to the next line: increases the row and resets the column;
336     * does the same as #nextLine(1).
337     */
338    public final void nextLine() {
339        nextLine(1);
340    }
341
342    /**
343     * Moves the cursor down several lines: increases the row by the
344     * specified number of lines and sets the cursor to the leading column.
345     *
346     * @param lines  number of rows to move
347     */
348    public final void nextLine(int lines) {
349        nextRow(lines);
350        setColumn(getLeadingColumn());
351    }
352
353    // Form Constraints Alignment *******************************************
354
355    /**
356     * Sets the horizontal alignment.
357     *
358     * @param alignment the new horizontal alignment
359     */
360    public final void setHAlignment(CellConstraints.Alignment alignment) {
361        currentCellConstraints.hAlign = alignment;
362    }
363
364    /**
365     * Sets the vertical alignment.
366     *
367     * @param alignment the new vertical alignment
368     */
369    public final void setVAlignment(CellConstraints.Alignment alignment) {
370        currentCellConstraints.vAlign = alignment;
371    }
372
373    /**
374     * Sets the horizontal and vertical alignment.
375     *
376     * @param hAlign the new horizontal alignment
377     * @param vAlign the new vertical alignment
378     */
379    public final void setAlignment(CellConstraints.Alignment hAlign,
380            CellConstraints.Alignment vAlign) {
381        setHAlignment(hAlign);
382        setVAlignment(vAlign);
383    }
384
385    // Appending Columns ******************************************************
386
387    /**
388     * Appends the given column specification to the builder's layout.
389     *
390     * @param columnSpec  the column specification object to append
391     *
392     * @see #appendColumn(String)
393     */
394    public final void appendColumn(ColumnSpec columnSpec) {
395        getLayout().appendColumn(columnSpec);
396    }
397
398    /**
399     * Appends a column specification to the builder's layout
400     * that represents the given string encoding.
401     *
402     * @param encodedColumnSpec  the column specification to append in encoded form
403     *
404     * @see #appendColumn(ColumnSpec)
405     */
406    public final void appendColumn(String encodedColumnSpec) {
407        appendColumn(new ColumnSpec(encodedColumnSpec));
408    }
409
410    /**
411     * Appends a glue column.
412     *
413     * @see #appendLabelComponentsGapColumn()
414     * @see #appendRelatedComponentsGapColumn()
415     * @see #appendUnrelatedComponentsGapColumn()
416     */
417    public final void appendGlueColumn() {
418        appendColumn(FormFactory.GLUE_COLSPEC);
419    }
420
421    /**
422     * Appends a column that is the default gap between a label and
423     * its associated component.
424     *
425     * @since 1.0.3
426     *
427     * @see #appendGlueColumn()
428     * @see #appendRelatedComponentsGapColumn()
429     * @see #appendUnrelatedComponentsGapColumn()
430     */
431    public final void appendLabelComponentsGapColumn() {
432        appendColumn(FormFactory.LABEL_COMPONENT_GAP_COLSPEC);
433    }
434
435    /**
436     * Appends a column that is the default gap for related components.
437     *
438     * @see #appendGlueColumn()
439     * @see #appendLabelComponentsGapColumn()
440     * @see #appendUnrelatedComponentsGapColumn()
441     */
442    public final void appendRelatedComponentsGapColumn() {
443        appendColumn(FormFactory.RELATED_GAP_COLSPEC);
444    }
445
446    /**
447     * Appends a column that is the default gap for unrelated components.
448     *
449     * @see #appendGlueColumn()
450     * @see #appendLabelComponentsGapColumn()
451     * @see #appendRelatedComponentsGapColumn()
452     */
453    public final void appendUnrelatedComponentsGapColumn() {
454        appendColumn(FormFactory.UNRELATED_GAP_COLSPEC);
455    }
456
457    // Appending Rows ********************************************************
458
459    /**
460     * Appends the given row specification to the builder's layout.
461     *
462     * @param rowSpec  the row specification object to append
463     *
464     * @see #appendRow(String)
465     */
466    public final void appendRow(RowSpec rowSpec) {
467        getLayout().appendRow(rowSpec);
468    }
469
470    /**
471     * Appends a row specification to the builder's layout that represents
472     * the given string encoding.
473     *
474     * @param encodedRowSpec  the row specification to append in encoded form
475     *
476     * @see #appendRow(RowSpec)
477     */
478    public final void appendRow(String encodedRowSpec) {
479        appendRow(new RowSpec(encodedRowSpec));
480    }
481
482    /**
483     * Appends a glue row.
484     *
485     * @see #appendRelatedComponentsGapRow()
486     * @see #appendUnrelatedComponentsGapRow()
487     * @see #appendParagraphGapRow()
488     */
489    public final void appendGlueRow() {
490        appendRow(FormFactory.GLUE_ROWSPEC);
491    }
492
493    /**
494     * Appends a row that is the default gap for related components.
495     *
496     * @see #appendGlueRow()
497     * @see #appendUnrelatedComponentsGapRow()
498     * @see #appendParagraphGapRow()
499     */
500    public final void appendRelatedComponentsGapRow() {
501        appendRow(FormFactory.RELATED_GAP_ROWSPEC);
502    }
503
504    /**
505     * Appends a row that is the default gap for unrelated components.
506     *
507     * @see #appendGlueRow()
508     * @see #appendRelatedComponentsGapRow()
509     * @see #appendParagraphGapRow()
510     */
511    public final void appendUnrelatedComponentsGapRow() {
512        appendRow(FormFactory.UNRELATED_GAP_ROWSPEC);
513    }
514
515    /**
516     * Appends a row that is the default gap for paragraphs.
517     *
518     * @since 1.0.3
519     *
520     * @see #appendGlueRow()
521     * @see #appendRelatedComponentsGapRow()
522     * @see #appendUnrelatedComponentsGapRow()
523     */
524    public final void appendParagraphGapRow() {
525        appendRow(FormFactory.PARAGRAPH_GAP_ROWSPEC);
526    }
527
528    // Adding Components ****************************************************
529
530    /**
531     * Adds a component to the panel using the given cell constraints.
532     *
533     * @param component        the component to add
534     * @param cellConstraints  the component's cell constraints
535     * @return the added component
536     */
537    public final Component add(Component component,
538            CellConstraints cellConstraints) {
539        container.add(component, cellConstraints);
540        return component;
541    }
542
543    /**
544     * Adds a component to the panel using the given encoded cell constraints.
545     *
546     * @param component               the component to add
547     * @param encodedCellConstraints  the component's encoded cell constraints
548     * @return the added component
549     */
550    public final Component add(Component component,
551            String encodedCellConstraints) {
552        container.add(component, new CellConstraints(encodedCellConstraints));
553        return component;
554    }
555
556    /**
557     * Adds a component to the container using the default cell constraints.
558     * Note that when building from left to right, this method won't adjust
559     * the cell constraints if the column span is larger than 1. In this case
560     * you should use {@link #add(Component, CellConstraints)} with a cell
561     * constraints object created by {@link #createLeftAdjustedConstraints(int)}.
562     *
563     * @param component        the component to add
564     * @return the added component
565     *
566     * @see #add(Component, CellConstraints)
567     * @see #createLeftAdjustedConstraints(int)
568     */
569    public final Component add(Component component) {
570        add(component, currentCellConstraints);
571        return component;
572    }
573
574    // Misc *****************************************************************
575
576    /**
577     * Returns the CellConstraints object that is used as a cursor and
578     * holds the current column span and row span.
579     *
580     * @return the builder's current {@link CellConstraints} object
581     */
582    protected final CellConstraints cellConstraints() {
583        return currentCellConstraints;
584    }
585
586    /**
587     * Returns the index of the leading column.<p>
588     *
589     * Subclasses may override this method, for example, if the form
590     * has a leading gap column that should not be filled with components.
591     *
592     * @return the leading column
593     */
594    protected int getLeadingColumn() {
595        return isLeftToRight() ? 1 : getColumnCount();
596    }
597
598    /**
599     * Returns the sign (-1 or 1) used to increment the cursor's column
600     * when moving to the next column.
601     *
602     * @return -1 for right-to-left, 1 for left-to-right
603     */
604    protected final int getColumnIncrementSign() {
605        return isLeftToRight() ? 1 : -1;
606    }
607
608    /**
609     * Creates and returns a <code>CellConstraints</code> object at
610     * the current cursor position that uses the given column span
611     * and is adjusted to the left. Useful when building from right to left.
612     *
613     * @param columnSpan   the column span to be used in the constraints
614     * @return CellConstraints adjusted to the left hand side
615     */
616    protected final CellConstraints createLeftAdjustedConstraints(
617            int columnSpan) {
618        int firstColumn = isLeftToRight() ? getColumn()
619                : getColumn() + 1 - columnSpan;
620        return new CellConstraints(firstColumn, getRow(), columnSpan,
621                cellConstraints().gridHeight);
622    }
623
624}