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 javax.swing.JButton;
034import javax.swing.JComponent;
035import javax.swing.JPanel;
036
037import com.jgoodies.forms.factories.Borders;
038import com.jgoodies.forms.factories.FormFactory;
039import com.jgoodies.forms.layout.ColumnSpec;
040import com.jgoodies.forms.layout.ConstantSize;
041import com.jgoodies.forms.layout.FormLayout;
042import com.jgoodies.forms.layout.FormSpec;
043import com.jgoodies.forms.layout.RowSpec;
044import com.jgoodies.forms.util.LayoutStyle;
045
046/**
047 * A non-visual builder that assists you in building consistent button bars
048 * that comply with popular UI style guides. It utilizes the {@link FormLayout}.
049 * This class is in turn used by the
050 * {@link com.jgoodies.forms.factories.ButtonBarFactory} that provides
051 * an even higher level of abstraction for building consistent button bars.<p>
052 *
053 * Buttons added to the builder are either gridded or fixed and may fill
054 * their FormLayout cell or not. All gridded buttons get the same width,
055 * while fixed buttons use their own size. Gridded buttons honor
056 * the default minimum button width as specified by the current
057 * {@link com.jgoodies.forms.util.LayoutStyle}.<p>
058 *
059 * You can set an optional hint for narrow  margin for the fixed width buttons.
060 * This is useful if you want to lay out a button bar that includes a button
061 * with a long text. For example, in a bar with
062 * 'Copy to Clipboard', 'OK', 'Cancel' you may declare the clipboard button
063 * as a fixed size button with narrow margins, OK and Cancel as gridded.
064 * Gridded buttons are marked as narrow by default.
065 * Note that some look&amp;feels do not support the narrow margin feature,
066 * and conversely, others have only narrow margins. The JGoodies look&amp;feels
067 * honor the setting, the Mac Aqua l&amp;f uses narrow margins all the time.<p>
068 *
069 * To honor the platform's button order (left-to-right vs. right-to-left)
070 * this builder uses the <em>leftToRightButtonOrder</em> property.
071 * It is initialized with the current LayoutStyle's button order,
072 * which in turn is left-to-right on most platforms and right-to-left
073 * on the Mac OS X. Builder methods that create sequences of buttons
074 * (e.g. {@link #addGriddedButtons(JButton[])} honor the button order.
075 * If you want to ignore the default button order, you can either
076 * add add individual buttons, or create a ButtonBarBuilder instance
077 * with the order set to left-to-right. For the latter see
078 * {@link #createLeftToRightBuilder()}. Also see the button order
079 * example below.<p>
080 *
081 * <strong>Example:</strong><br>
082 * The following example builds a button bar with <i>Help</i> button on the
083 * left-hand side and <i>OK, Cancel, Apply</i> buttons on the right-hand side.
084 * <pre>
085 * private JPanel createHelpOKCancelApplyBar(
086 *         JButton help, JButton ok, JButton cancel, JButton apply) {
087 *     ButtonBarBuilder builder = new ButtonBarBuilder();
088 *     builder.addGridded(help);
089 *     builder.addRelatedGap();
090 *     builder.addGlue();
091 *     builder.addGriddedButtons(new JButton[]{ok, cancel, apply});
092 *     return builder.getPanel();
093 * }
094 * </pre><p>
095 *
096 * <strong>Button Order Example:</strong><br>
097 * The following example builds three button bars where one honors
098 * the platform's button order and the other two ignore it.
099 * <pre>
100 * public JComponent buildPanel() {
101 *     FormLayout layout = new FormLayout("pref");
102 *     DefaultFormBuilder rowBuilder = new DefaultFormBuilder(layout);
103 *     rowBuilder.setDefaultDialogBorder();
104 *
105 *     rowBuilder.append(buildButtonSequence(new ButtonBarBuilder()));
106 *     rowBuilder.append(buildButtonSequence(ButtonBarBuilder.createLeftToRightBuilder()));
107 *     rowBuilder.append(buildIndividualButtons(new ButtonBarBuilder()));
108 *
109 *     return rowBuilder.getPanel();
110 * }
111 *
112 * private Component buildButtonSequence(ButtonBarBuilder builder) {
113 *     builder.addGriddedButtons(new JButton[] {
114 *             new JButton("One"),
115 *             new JButton("Two"),
116 *             new JButton("Three")
117 *     });
118 *     return builder.getPanel();
119 * }
120 *
121 * private Component buildIndividualButtons(ButtonBarBuilder builder) {
122 *     builder.addGridded(new JButton("One"));
123 *     builder.addRelatedGap();
124 *     builder.addGridded(new JButton("Two"));
125 *     builder.addRelatedGap();
126 *     builder.addGridded(new JButton("Three"));
127 *     return builder.getPanel();
128 * }
129 * </pre>
130 *
131 * @author        Karsten Lentzsch
132 * @version $Revision$
133 *
134 * @see ButtonStackBuilder
135 * @see com.jgoodies.forms.factories.ButtonBarFactory
136 * @see com.jgoodies.forms.util.LayoutStyle
137 */
138public final class ButtonBarBuilder extends PanelBuilder {
139
140    /**
141     * Specifies the columns of the initial FormLayout used in constructors.
142     */
143    private static final ColumnSpec[] COL_SPECS = new ColumnSpec[] {};
144
145    /**
146     * Specifies the FormLayout's the single button bar row.
147     */
148    private static final RowSpec[] ROW_SPECS = new RowSpec[] {
149            new RowSpec("center:pref") };
150
151    /**
152     * The client property key used to indicate that a button shall
153     * get narrow margins on the left and right hand side.<p>
154     *
155     * This optional setting will be honored by all JGoodies Look&amp;Feel
156     * implementations. The Mac Aqua l&amp;f uses narrow margins only.
157     * Other look&amp;feel implementations will likely ignore this key
158     * and so may render a wider button margin.
159     */
160    private static final String NARROW_KEY = "jgoodies.isNarrow";
161
162    /**
163     * Describes how sequences of buttons are added to the button bar:
164     * left-to-right or right-to-left. This setting is initialized using
165     * the current {@link LayoutStyle}'s button order. It is honored
166     * only by builder methods that build sequences of button, for example
167     * {@link #addGriddedButtons(JButton[])}, and ignored if you add
168     * individual button, for example using {@link #addGridded(JComponent)}.
169     *
170     * @see #isLeftToRight()
171     * @see #setLeftToRight(boolean)
172     * @see #addGriddedButtons(JButton[])
173     * @see #addGriddedGrowingButtons(JButton[])
174     */
175    private boolean leftToRight;
176
177    // Instance Creation ****************************************************
178
179    /**
180     * Constructs an instance of <code>ButtonBarBuilder</code> on a
181     * <code>JPanel</code> using a preconfigured FormLayout as layout manager.
182     */
183    public ButtonBarBuilder() {
184        this(new JPanel(null));
185    }
186
187    /**
188     * Constructs an instance of <code>ButtonBarBuilder</code> on the given
189     * panel using a preconfigured FormLayout as layout manager.
190     *
191     * @param panel  the layout container
192     */
193    public ButtonBarBuilder(JPanel panel) {
194        super(new FormLayout(COL_SPECS, ROW_SPECS), panel);
195        leftToRight = LayoutStyle.getCurrent().isLeftToRightButtonOrder();
196    }
197
198    /**
199     * Creates and returns a <code>ButtonBarBuilder</code> with
200     * initialized with a left to right button order.
201     *
202     * @return a button bar builder with button order set to left-to-right
203     */
204    public static ButtonBarBuilder createLeftToRightBuilder() {
205        ButtonBarBuilder builder = new ButtonBarBuilder();
206        builder.setLeftToRightButtonOrder(true);
207        return builder;
208    }
209
210    // Accessing Properties *************************************************
211
212    /**
213     * Returns whether button sequences will be ordered from
214     * left to right or from right to left.
215     *
216     * @return true if button sequences are ordered from left to right
217     * @since 1.0.3
218     *
219     * @see LayoutStyle#isLeftToRightButtonOrder()
220     */
221    public boolean isLeftToRightButtonOrder() {
222        return leftToRight;
223    }
224
225    /**
226     * Sets the order for button sequences to either left to right,
227     * or right to left.
228     *
229     * @param newButtonOrder  true if button sequences shall be ordered
230     *     from left to right
231     * @since 1.0.3
232     *
233     * @see LayoutStyle#isLeftToRightButtonOrder()
234     */
235    public void setLeftToRightButtonOrder(boolean newButtonOrder) {
236        leftToRight = newButtonOrder;
237    }
238
239    // Default Borders ******************************************************
240
241    /**
242     * Sets a default border that has a gap in the bar's north.
243     */
244    public void setDefaultButtonBarGapBorder() {
245        getPanel().setBorder(Borders.BUTTON_BAR_GAP_BORDER);
246    }
247
248    // Adding Components ****************************************************
249
250    /**
251     * Adds a sequence of related gridded buttons each separated by
252     * a default gap. Honors this builder's button order. If you
253     * want to use a fixed left to right order, add individual buttons.
254     *
255     * @param buttons  an array of buttons to add
256     *
257     * @see LayoutStyle
258     */
259    public void addGriddedButtons(JButton[] buttons) {
260        int length = buttons.length;
261        for (int i = 0; i < length; i++) {
262            int index = leftToRight ? i : length - 1 - i;
263            addGridded(buttons[index]);
264            if (i < buttons.length - 1) {
265                addRelatedGap();
266            }
267        }
268    }
269
270    /**
271     * Adds a sequence of gridded buttons that grow
272     * where each is separated by a default gap.
273     * Honors this builder's button order. If you
274     * want to use a fixed left to right order,
275     * add individual buttons.
276     *
277     * @param buttons  an array of buttons to add
278     *
279     * @see LayoutStyle
280     */
281    public void addGriddedGrowingButtons(JButton[] buttons) {
282        int length = buttons.length;
283        for (int i = 0; i < length; i++) {
284            int index = leftToRight ? i : length - 1 - i;
285            addGriddedGrowing(buttons[index]);
286            if (i < buttons.length - 1) {
287                addRelatedGap();
288            }
289        }
290    }
291
292    /**
293     * Adds a fixed size component. Unlike the gridded components,
294     * this component keeps its individual preferred dimension.
295     *
296     * @param component  the component to add
297     */
298    public void addFixed(JComponent component) {
299        getLayout().appendColumn(FormFactory.PREF_COLSPEC);
300        add(component);
301        nextColumn();
302    }
303
304    /**
305     * Adds a fixed size component with narrow margins. Unlike the gridded
306     * components, this component keeps its individual preferred dimension.
307     *
308     * @param component  the component to add
309     */
310    public void addFixedNarrow(JComponent component) {
311        component.putClientProperty(NARROW_KEY, Boolean.TRUE);
312        addFixed(component);
313    }
314
315    /**
316     * Adds a gridded component, i.e. a component that will get
317     * the same dimension as all other gridded components.
318     *
319     * @param component  the component to add
320     */
321    public void addGridded(JComponent component) {
322        getLayout().appendColumn(FormFactory.BUTTON_COLSPEC);
323        getLayout().addGroupedColumn(getColumn());
324        component.putClientProperty(NARROW_KEY, Boolean.TRUE);
325        add(component);
326        nextColumn();
327    }
328
329    /**
330     * Adds a gridded component that grows. The component's initial size
331     * (before it grows) is the same as for all other gridded components.
332     *
333     * @param component  the component to add
334     */
335    public void addGriddedGrowing(JComponent component) {
336        getLayout().appendColumn(FormFactory.GROWING_BUTTON_COLSPEC);
337        getLayout().addGroupedColumn(getColumn());
338        component.putClientProperty(NARROW_KEY, Boolean.TRUE);
339        add(component);
340        nextColumn();
341    }
342
343    /**
344     * Adds a glue that will be given the extra space,
345     * if this box is larger than its preferred size.
346     */
347    public void addGlue() {
348        appendGlueColumn();
349        nextColumn();
350    }
351
352    /**
353     * Adds the standard gap for related components.
354     */
355    public void addRelatedGap() {
356        appendRelatedComponentsGapColumn();
357        nextColumn();
358    }
359
360    /**
361     * Adds the standard gap for unrelated components.
362     */
363    public void addUnrelatedGap() {
364        appendUnrelatedComponentsGapColumn();
365        nextColumn();
366    }
367
368    /**
369     * Adds a strut of a specified size.
370     *
371     * @param size  a <code>ConstantSize</code> that describes the gap's size
372     */
373    public void addStrut(ConstantSize size) {
374        getLayout().appendColumn(
375                new ColumnSpec(ColumnSpec.LEFT, size, FormSpec.NO_GROW));
376        nextColumn();
377    }
378
379}