001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.layout;
017    
018    import java.awt.Component;
019    import java.awt.GridBagConstraints;
020    import java.awt.GridBagLayout;
021    import java.awt.Insets;
022    import java.util.ArrayList;
023    import java.util.List;
024    
025    import javax.swing.JComponent;
026    import javax.swing.JLabel;
027    import javax.swing.JPanel;
028    import javax.swing.SwingConstants;
029    
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.springframework.richclient.application.ApplicationServicesLocator;
033    import org.springframework.richclient.factory.ComponentFactory;
034    import org.springframework.richclient.util.GridBagLayoutDebugPanel;
035    
036    /**
037     * This provides an easy way to create panels using a {@link GridBagLayout}.
038     * <p />
039     *
040     * Usage is:
041     *
042     * <pre>
043     * GridBagLayoutBuilder builder = new GridBagLayoutBuilder();
044     *
045     * builder.appendRightLabel(&quot;label.field1&quot;).appendField(field1);
046     * builder.appendRightLabel(&quot;label.field2&quot;).appendField(field2);
047     * builder.nextLine();
048     *
049     * builder.appendRightLabel(&quot;label.field3&quot;).appendField(field3);
050     * // because &quot;field3&quot; is the last component on this line, but the panel has
051     * // 4 columns, &quot;field3&quot; will span 3 columns
052     * builder.nextLine();
053     *
054     * // once everything's been put into the builder, ask it to build a panel
055     * // to use in the UI.
056     * JPanel panel = builder.getPanel();
057     * </pre>
058     *
059     * @author Jim Moore
060     * @see #setAutoSpanLastComponent(boolean)
061     * @see #setShowGuidelines(boolean)
062     * @see #setComponentFactory(ComponentFactory)
063     */
064    public class GridBagLayoutBuilder implements LayoutBuilder {
065        private static final Log LOG = LogFactory.getLog(GridBagLayoutBuilder.class);
066    
067        private Insets defaultInsets = new Insets(0, 0, 4, 4);
068    
069        private boolean showGuidelines = false;
070    
071        private boolean autoSpanLastComponent = true;
072    
073        private int currentCol;
074    
075        private int currentRow;
076    
077        private List rows;
078    
079        private List currentRowList;
080    
081        private int maxCol = 0;
082    
083        private ComponentFactory componentFactory;
084    
085        private static final Item NULL_ITEM = new Item(null, null);
086    
087        public GridBagLayoutBuilder() {
088            super();
089            init();
090        }
091    
092        private void init() {
093            currentCol = 0;
094            currentRow = 0;
095            rows = new ArrayList();
096            currentRowList = new ArrayList();
097        }
098    
099        /**
100         * Returns the default {@link Insets}used when adding components
101         */
102        public Insets getDefaultInsets() {
103            return defaultInsets;
104        }
105    
106        /**
107         * Sets the default {@link Insets}used when adding components
108         */
109        public void setDefaultInsets(Insets defaultInsets) {
110            this.defaultInsets = defaultInsets;
111        }
112    
113        /**
114         * Returns the current row (zero-based) that the builder is putting
115         * components in
116         */
117        public int getCurrentRow() {
118            return currentRow;
119        }
120    
121        /**
122         * Returns the current column (zero-based) that the builder is putting
123         * components in
124         */
125        public int getCurrentCol() {
126            return currentCol;
127        }
128    
129        public ComponentFactory getComponentFactory() {
130            if (componentFactory == null) {
131                componentFactory = (ComponentFactory)ApplicationServicesLocator.services().getService(ComponentFactory.class);
132            }
133            return componentFactory;
134        }
135    
136        public void setComponentFactory(ComponentFactory componentFactory) {
137            this.componentFactory = componentFactory;
138        }
139    
140        /**
141         * Appends the given component to the end of the current line, using the
142         * default insets and no expansion
143         *
144         * @param component the component to add to the current line
145         *
146         * @return "this" to make it easier to string together append calls
147         */
148        public GridBagLayoutBuilder append(Component component) {
149            return append(component, 1, 1);
150        }
151    
152        /**
153         * Appends the given component to the end of the current line, using the
154         * default insets and no expansion
155         *
156         * @param component the component to add to the current line
157         * @param colSpan   the number of columns to span
158         * @param rowSpan   the number of rows to span
159         *
160         * @return "this" to make it easier to string together append calls
161         */
162        public GridBagLayoutBuilder append(Component component, int colSpan, int rowSpan) {
163            return append(component, colSpan, rowSpan, 0.0, 0.0);
164        }
165    
166        /**
167         * Appends the given component to the end of the current line, using the
168         * default insets
169         *
170         * @param component the component to add to the current line
171         * @param colSpan   the number of columns to span
172         * @param rowSpan   the number of rows to span
173         * @param expandX   should the component "grow" horrizontally?
174         * @param expandY   should the component "grow" vertically?
175         *
176         * @return "this" to make it easier to string together append calls
177         */
178        public GridBagLayoutBuilder append(Component component, int colSpan, int rowSpan, boolean expandX, boolean expandY) {
179            return append(component, colSpan, rowSpan, expandX, expandY, defaultInsets);
180        }
181    
182        /**
183         * Appends the given component to the end of the current line
184         *
185         * @param component the component to add to the current line
186         * @param colSpan   the number of columns to span
187         * @param rowSpan   the number of rows to span
188         * @param expandX   should the component "grow" horrizontally?
189         * @param expandY   should the component "grow" vertically?
190         * @param insets    the insets to use for this component
191         *
192         * @return "this" to make it easier to string together append calls
193         */
194        public GridBagLayoutBuilder append(Component component, int colSpan, int rowSpan, boolean expandX, boolean expandY,
195                Insets insets) {
196            if (expandX && expandY)
197                return append(component, colSpan, rowSpan, 1.0, 1.0, insets);
198            else if (expandX)
199                return append(component, colSpan, rowSpan, 1.0, 0.0, insets);
200            else if (expandY)
201                return append(component, colSpan, rowSpan, 0.0, 1.0, insets);
202            else
203                return append(component, colSpan, rowSpan, 0.0, 0.0, insets);
204        }
205    
206        /**
207         * Appends the given component to the end of the current line
208         *
209         * @param component the component to add to the current line
210         * @param x         the column to put the component
211         * @param y         the row to put the component
212         * @param colSpan   the number of columns to span
213         * @param rowSpan   the number of rows to span
214         * @param expandX   should the component "grow" horrizontally?
215         * @param expandY   should the component "grow" vertically?
216         * @param insets    the insets to use for this component
217         *
218         * @return "this" to make it easier to string together append calls
219         */
220        public GridBagLayoutBuilder append(Component component, int x, int y, int colSpan, int rowSpan, boolean expandX,
221                boolean expandY, Insets insets) {
222            if (expandX && expandY)
223                return append(component, x, y, colSpan, rowSpan, 1.0, 1.0, insets);
224            else if (expandX)
225                return append(component, x, y, colSpan, rowSpan, 1.0, 0.0, insets);
226            else if (expandY)
227                return append(component, x, y, colSpan, rowSpan, 0.0, 1.0, insets);
228            else
229                return append(component, x, y, colSpan, rowSpan, 0.0, 0.0, insets);
230        }
231    
232        /**
233         * Appends the given component to the end of the current line, using the
234         * default insets
235         *
236         * @param component the component to add to the current line
237         * @param colSpan   the number of columns to span
238         * @param rowSpan   the number of rows to span
239         * @param xweight   the "growth weight" horrizontally
240         * @param yweight   the "growth weight" horrizontally
241         *
242         * @return "this" to make it easier to string together append calls
243         *
244         * @see GridBagConstraints#weightx
245         * @see GridBagConstraints#weighty
246         */
247        public GridBagLayoutBuilder append(Component component, int colSpan, int rowSpan, double xweight, double yweight) {
248            return append(component, colSpan, rowSpan, xweight, yweight, defaultInsets);
249        }
250    
251        /**
252         * Appends the given component to the end of the current line
253         *
254         * @param component the component to add to the current line
255         * @param colSpan   the number of columns to span
256         * @param rowSpan   the number of rows to span
257         * @param xweight   the "growth weight" horrizontally
258         * @param yweight   the "growth weight" horrizontally
259         * @param insets    the insets to use for this component
260         *
261         * @return "this" to make it easier to string together append calls
262         *
263         * @see GridBagConstraints#weightx
264         * @see GridBagConstraints#weighty
265         */
266        public GridBagLayoutBuilder append(Component component, int colSpan, int rowSpan, double xweight, double yweight,
267                Insets insets) {
268            return append(component, getCurrentCol(), getCurrentRow(), colSpan, rowSpan, xweight, yweight, insets);
269        }
270    
271        /**
272         * Appends a label and field to the end of the current line.
273         * <p />
274         *
275         * The label will be to the left of the field, and be right-justified.
276         * <br />
277         * The field will "grow" horizontally as space allows.
278         * <p />
279         *
280         * @param propertyName the name of the property to create the controls for
281         *
282         * @return "this" to make it easier to string together append calls
283         */
284        public GridBagLayoutBuilder appendLabeledField(String propertyName, final JComponent field,
285                LabelOrientation labelOrientation) {
286            return appendLabeledField(propertyName, field, labelOrientation, 1);
287        }
288    
289        /**
290         * Appends a label and field to the end of the current line.
291         * <p />
292         *
293         * The label will be to the left of the field, and be right-justified.
294         * <br />
295         * The field will "grow" horizontally as space allows.
296         * <p />
297         *
298         * @param propertyName the name of the property to create the controls for
299         * @param colSpan      the number of columns the field should span
300         *
301         * @return "this" to make it easier to string together append calls
302         */
303        public GridBagLayoutBuilder appendLabeledField(String propertyName, final JComponent field,
304                LabelOrientation labelOrientation, int colSpan) {
305            return appendLabeledField(propertyName, field, labelOrientation, colSpan, 1, true, false);
306        }
307    
308        /**
309         * Appends a label and field to the end of the current line.<p />
310         *
311         * The label will be to the left of the field, and be right-justified.<br />
312         * The field will "grow" horizontally as space allows.<p />
313         *
314         * @param propertyName the name of the property to create the controls for
315         * @param colSpan      the number of columns the field should span
316         *
317         * @return "this" to make it easier to string together append calls
318         */
319        public GridBagLayoutBuilder appendLabeledField(String propertyName, final JComponent field,
320                LabelOrientation labelOrientation, int colSpan, int rowSpan, boolean expandX, boolean expandY) {
321            JLabel label = createLabel(propertyName);
322            return appendLabeledField(label, field, labelOrientation, colSpan, rowSpan, expandX, expandY);
323        }
324    
325        protected JLabel createLabel(String propertyName) {
326            return getComponentFactory().createLabel(propertyName);
327        }
328    
329        /**
330         * Appends a label and field to the end of the current line.<p />
331         *
332         * The label will be to the left of the field, and be right-justified.<br />
333         * The field will "grow" horizontally as space allows.<p />
334         *
335         * @param label   the label to associate and layout with the field
336         * @param colSpan the number of columns the field should span
337         *
338         * @return "this" to make it easier to string together append calls
339         */
340        public GridBagLayoutBuilder appendLabeledField(final JLabel label, final JComponent field,
341                LabelOrientation labelOrientation, int colSpan, int rowSpan, boolean expandX, boolean expandY) {
342            label.setLabelFor(field);
343    
344            final int col = getCurrentCol();
345            final int row = getCurrentRow();
346            final Insets insets = getDefaultInsets();
347    
348            if (labelOrientation == LabelOrientation.LEFT || labelOrientation == null) {
349                label.setHorizontalAlignment(SwingConstants.RIGHT);
350                append(label, col, row, 1, 1, false, expandY, insets);
351                append(field, col + 1, row, colSpan, rowSpan, expandX, expandY, insets);
352            }
353            else if (labelOrientation == LabelOrientation.RIGHT) {
354                label.setHorizontalAlignment(SwingConstants.LEFT);
355                append(field, col, row, colSpan, rowSpan, expandX, expandY, insets);
356                append(label, col + colSpan, row, 1, rowSpan, false, expandY, insets);
357            }
358            else if (labelOrientation == LabelOrientation.TOP) {
359                label.setHorizontalAlignment(SwingConstants.LEFT);
360                append(label, col, row, colSpan, 1, expandX, false, insets);
361                append(field, col, row + 1, colSpan, rowSpan, expandX, expandY, insets);
362            }
363            else if (labelOrientation == LabelOrientation.BOTTOM) {
364                label.setHorizontalAlignment(SwingConstants.LEFT);
365                append(field, col, row, colSpan, rowSpan, expandX, expandY, insets);
366                append(label, col, row + rowSpan, colSpan, 1, expandX, false, insets);
367            }
368    
369            return this;
370        }
371    
372        /**
373         * Appends the given component to the end of the current line
374         *
375         * @param component the component to add to the current line
376         * @param x         the column to put the component
377         * @param y         the row to put the component
378         * @param colSpan   the number of columns to span
379         * @param rowSpan   the number of rows to span
380         * @param xweight   the "growth weight" horrizontally
381         * @param yweight   the "growth weight" horrizontally
382         * @param insets    the insets to use for this component
383         *
384         * @return "this" to make it easier to string together append calls
385         *
386         * @see GridBagConstraints#weightx
387         * @see GridBagConstraints#weighty
388         */
389        public GridBagLayoutBuilder append(Component component, int x, int y, int colSpan, int rowSpan, double xweight,
390                double yweight, Insets insets) {
391            final List rowList = getRow(y);
392            ensureCapacity(rowList, Math.max(x, maxCol) + 1);
393    
394            final int col = bypassPlaceholders(rowList, x);
395    
396            insertPlaceholdersIfNeeded(rowSpan, y, col, component, colSpan);
397    
398            final GridBagConstraints gbc = createGridBagConstraint(col, y, colSpan, rowSpan, xweight, yweight, insets);
399    
400            rowList.set(col, new Item(component, gbc));
401            if (LOG.isDebugEnabled())
402                LOG.debug(getDebugString(component, gbc));
403    
404            // keep track of the largest column this has seen...
405            this.maxCol = Math.max(this.maxCol, col);
406    
407            currentCol = col + colSpan;
408    
409            return this;
410        }
411    
412        private void insertPlaceholdersIfNeeded(final int rowSpan, final int y, final int col, final Component component,
413                final int colSpan) {
414            if (rowSpan > 1) {
415                growRowsIfNeeded(rowSpan);
416                for (int i = 1; i < (y + rowSpan); i++) {
417                    final List row = getRow(i);
418                    ensureCapacity(row, col + colSpan + 1);
419                    if (row.get(col) != null) {
420                        // sanity check -- shouldn't ever happen
421                        throw new IllegalStateException("Trying to overwrite another component: " + component + ", " + col
422                                + " " + y);
423                    }
424                    for (int j = 0; j < colSpan; j++) {
425                        row.set(col + j, NULL_ITEM);
426                    }
427                }
428            }
429        }
430    
431        private List getRow(final int i) {
432            ensureCapacity(rows, i + 1);
433            List row = (List)rows.get(i);
434            if (row == null) {
435                row = new ArrayList();
436                rows.set(i, row);
437            }
438            return row;
439        }
440    
441        private int bypassPlaceholders(final List list, final int col) {
442            int theCol = col;
443            while (theCol < list.size() && list.get(theCol) != null) {
444                theCol++;
445            }
446            return theCol;
447        }
448    
449        private void ensureCapacity(List list, int minSize) {
450            for (int i = list.size(); i < minSize; i++) {
451                list.add(null);
452            }
453        }
454    
455        private void growRowsIfNeeded(final int rowSpan) {
456            final int minNeededSize = currentRow + rowSpan;
457            ensureCapacity(rows, minNeededSize);
458            final int delta = minNeededSize - rows.size();
459            if (delta > 0) {
460                rows.set(currentRow, currentRowList);
461                for (int i = 0; i < delta; i++) {
462                    rows.set(currentRow + i, new ArrayList());
463                }
464            }
465        }
466    
467        /**
468         * Appends the given label to the end of the current line. The label does
469         * not "grow."
470         *
471         * @param label the label to append
472         *
473         * @return "this" to make it easier to string together append calls
474         */
475        public GridBagLayoutBuilder appendLabel(JLabel label) {
476            return appendLabel(label, 1);
477        }
478    
479        /**
480         * Appends the given label to the end of the current line. The label does
481         * not "grow."
482         *
483         * @param label   the label to append
484         * @param colSpan the number of columns to span
485         *
486         * @return "this" to make it easier to string together append calls
487         */
488        public GridBagLayoutBuilder appendLabel(JLabel label, int colSpan) {
489            return append(label, colSpan, 1, false, false);
490        }
491    
492        /**
493         * Appends a right-justified label to the end of the given line, using the
494         * provided string as the key to look in the
495         * {@link #setComponentFactory(ComponentFactory) ComponentFactory's}message
496         * bundle for the text to use.
497         *
498         * @param labelKey the key into the message bundle; if not found the key is used
499         *                 as the text to display
500         *
501         * @return "this" to make it easier to string together append calls
502         */
503        public GridBagLayoutBuilder appendRightLabel(String labelKey) {
504            return appendRightLabel(labelKey, 1);
505        }
506    
507        /**
508         * Appends a right-justified label to the end of the given line, using the
509         * provided string as the key to look in the
510         * {@link #setComponentFactory(ComponentFactory) ComponentFactory's}message
511         * bundle for the text to use.
512         *
513         * @param labelKey the key into the message bundle; if not found the key is used
514         *                 as the text to display
515         * @param colSpan  the number of columns to span
516         *
517         * @return "this" to make it easier to string together append calls
518         */
519        public GridBagLayoutBuilder appendRightLabel(String labelKey, int colSpan) {
520            final JLabel label = createLabel(labelKey);
521            label.setHorizontalAlignment(SwingConstants.RIGHT);
522            return appendLabel(label, colSpan);
523        }
524    
525        /**
526         * Appends a left-justified label to the end of the given line, using the
527         * provided string as the key to look in the
528         * {@link #setComponentFactory(ComponentFactory) ComponentFactory's}message
529         * bundle for the text to use.
530         *
531         * @param labelKey the key into the message bundle; if not found the key is used
532         *                 as the text to display
533         *
534         * @return "this" to make it easier to string together append calls
535         */
536        public GridBagLayoutBuilder appendLeftLabel(String labelKey) {
537            return appendLeftLabel(labelKey, 1);
538        }
539    
540        /**
541         * Appends a left-justified label to the end of the given line, using the
542         * provided string as the key to look in the
543         * {@link #setComponentFactory(ComponentFactory) ComponentFactory's}message
544         * bundle for the text to use.
545         *
546         * @param labelKey the key into the message bundle; if not found the key is used
547         *                 as the text to display
548         * @param colSpan  the number of columns to span
549         *
550         * @return "this" to make it easier to string together append calls
551         */
552        public GridBagLayoutBuilder appendLeftLabel(String labelKey, int colSpan) {
553            final JLabel label = createLabel(labelKey);
554            label.setHorizontalAlignment(SwingConstants.LEFT);
555            return appendLabel(label, colSpan);
556        }
557    
558        /**
559         * Appends the given component to the end of the current line. The component
560         * will "grow" horizontally as space allows.
561         *
562         * @param component the item to append
563         *
564         * @return "this" to make it easier to string together append calls
565         */
566        public GridBagLayoutBuilder appendField(Component component) {
567            return appendField(component, 1);
568        }
569    
570        /**
571         * Appends the given component to the end of the current line. The component
572         * will "grow" horizontally as space allows.
573         *
574         * @param component the item to append
575         * @param colSpan   the number of columns to span
576         *
577         * @return "this" to make it easier to string together append calls
578         */
579        public GridBagLayoutBuilder appendField(Component component, int colSpan) {
580            return append(component, colSpan, 1, true, false);
581        }
582    
583        /**
584         * Appends a seperator (usually a horizonal line). Has an implicit
585         * {@link #nextLine()}before and after it.
586         *
587         * @return "this" to make it easier to string together append calls
588         */
589        public GridBagLayoutBuilder appendSeparator() {
590            return appendSeparator(null);
591        }
592    
593        /**
594         * Appends a seperator (usually a horizonal line) using the provided string
595         * as the key to look in the
596         * {@link #setComponentFactory(ComponentFactory) ComponentFactory's}message
597         * bundle for the text to put along with the seperator. Has an implicit
598         * {@link #nextLine()}before and after it.
599         *
600         * @return "this" to make it easier to string together append calls
601         */
602        public GridBagLayoutBuilder appendSeparator(String labelKey) {
603            if (this.currentRowList.size() > 0) {
604                nextLine();
605            }
606            final JComponent separator = getComponentFactory().createLabeledSeparator(labelKey);
607            return append(separator, 1, 1, true, false).nextLine();
608        }
609    
610        /**
611         * Ends the current line and starts a new one
612         *
613         * @return "this" to make it easier to string together append calls
614         */
615        public GridBagLayoutBuilder nextLine() {
616            currentRow++;
617            this.currentCol = 0;
618    
619            return this;
620        }
621    
622        private GridBagConstraints createGridBagConstraint(int x, int y, int colSpan, int rowSpan, double xweight,
623                double yweight, Insets insets) {
624            final GridBagConstraints gbc = new GridBagConstraints();
625            gbc.gridx = x;
626            gbc.gridy = y;
627            gbc.gridwidth = colSpan;
628            gbc.gridheight = rowSpan;
629            gbc.weightx = xweight;
630            gbc.weighty = yweight;
631            gbc.insets = insets;
632    
633            // in theory other ones can be used, but I've never seen why...
634            gbc.fill = GridBagConstraints.BOTH;
635    
636            return gbc;
637        }
638    
639        /**
640         * Should this show "guidelines"? Useful for debugging layouts.
641         */
642        public void setShowGuidelines(boolean showGuidelines) {
643            this.showGuidelines = showGuidelines;
644        }
645    
646        /**
647         * Creates and returns a JPanel with all the given components in it, using
648         * the "hints" that were provided to the builder.
649         *
650         * @return a new JPanel with the components laid-out in it
651         */
652        public JPanel getPanel() {
653            if (this.currentRowList.size() > 0) {
654                this.rows.add(this.currentRowList);
655            }
656    
657            final JPanel panel = this.showGuidelines ? new GridBagLayoutDebugPanel() : new JPanel(new GridBagLayout());
658    
659            final int lastRowIndex = this.rows.size() - 1;
660            for (int currentRowIndex = 0; currentRowIndex <= lastRowIndex; currentRowIndex++) {
661                final List row = getRow(currentRowIndex);
662                addRow(row, currentRowIndex, lastRowIndex, panel);
663            }
664            return panel;
665        }
666    
667        private void addRow(final List row, final int currentRowIndex, final int lastRowIndex, final JPanel panel) {
668            final int lastColIndex = row.size() - 1;
669    
670            for (int currentColIndex = 0; currentColIndex <= lastColIndex; currentColIndex++) {
671                final Item item = (Item)row.get(currentColIndex);
672    
673                if (item != null && item != NULL_ITEM) {
674                    final GridBagConstraints gbc = item.gbc;
675    
676                    if (gbc.gridy + gbc.gridheight - 1 == lastRowIndex) {
677                                            formatLastRow(gbc);
678                                    }
679    
680                                    if (gbc.gridx + gbc.gridwidth - 1 == lastColIndex) {
681                                            formatLastColumn(gbc, currentColIndex);
682                                    }
683    
684                    if (LOG.isDebugEnabled()) {
685                        LOG.debug("Adding to panel: " + getDebugString(item.component, gbc));
686                    }
687                    panel.add(item.component, gbc);
688                }
689            }
690        }
691    
692        private String getDebugString(Component component, GridBagConstraints gbc) {
693            final StringBuffer buffer = new StringBuffer();
694    
695            if (component instanceof JComponent) {
696                final JComponent jcomp = (JComponent)component;
697                final String name = jcomp.getName();
698                if (name != null && !"".equals(jcomp.getName())) {
699                    buffer.append(name);
700                }
701                else {
702                    if (jcomp instanceof JLabel) {
703                        buffer.append(((JLabel)jcomp).getText());
704                    }
705                    else {
706                        buffer.append(jcomp.toString());
707                    }
708                }
709            }
710            else {
711                buffer.append(component.toString());
712            }
713    
714            buffer.append(", ");
715            buffer.append("GridBagConstraint[");
716            buffer.append("anchor=").append(gbc.anchor).append(",");
717            buffer.append("fill=").append(gbc.fill).append(",");
718            buffer.append("gridheight=").append(gbc.gridheight).append(",");
719            buffer.append("gridwidth=").append(gbc.gridwidth).append(",");
720            buffer.append("gridx=").append(gbc.gridx).append(",");
721            buffer.append("gridy=").append(gbc.gridy).append(",");
722            buffer.append("weightx=").append(gbc.weightx).append(",");
723            buffer.append("weighty=").append(gbc.weighty).append("]");
724            return buffer.toString();
725        }
726    
727        private void formatLastRow(final GridBagConstraints gbc) {
728            // remove any insets at the bottom of the GBC
729            final Insets oldInset = gbc.insets;
730            gbc.insets = new Insets(oldInset.top, oldInset.left, 0, oldInset.right);
731        }
732    
733        /**
734         * Should the last column before a {@link #nextLine()}automaticly span to
735         * the end of the panel?
736         * <p />
737         *
738         * For example, if you have
739         *
740         * <pre>
741         * append(a).append(b).append(c).nextLine();
742         * append(d).append(e).nextLine();
743         * </pre>
744         *
745         * then "e" would automaticly span two columns.
746         *
747         * @param autoSpanLastComponent default is true
748         */
749        public void setAutoSpanLastComponent(boolean autoSpanLastComponent) {
750            this.autoSpanLastComponent = autoSpanLastComponent;
751        }
752    
753        private void formatLastColumn(final GridBagConstraints gbc, final int currentColIndex) {
754            // remove any insets at the right of the GBC
755            final Insets oldInset = gbc.insets;
756            gbc.insets = new Insets(oldInset.top, oldInset.left, oldInset.bottom, 0);
757    
758            if (this.autoSpanLastComponent) {
759                // increase the gridwidth if needed
760                final int colSpan = (this.maxCol - currentColIndex) + 1;
761                if (colSpan > gbc.gridwidth) {
762                    if (LOG.isDebugEnabled()) {
763                        LOG.debug("Increasing gridwidth from " + gbc.gridwidth + " to " + colSpan);
764                    }
765                    gbc.gridwidth = colSpan;
766                }
767            }
768        }
769    
770        //*************************************************************************
771        //
772        // INNER CLASSES
773        //
774        //*************************************************************************
775    
776        private static class Item {
777            public Component component;
778    
779            public GridBagConstraints gbc;
780    
781            public Item(Component component, GridBagConstraints gbc) {
782                this.component = component;
783                this.gbc = gbc;
784            }
785        }
786    
787    }