001    /* JAPI - (Yet anothr (hopefully) useful) Java API
002     *
003     * Copyright (C) 2004-2006 Christian Hujer
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License as
007     * published by the Free Software Foundation; either version 2 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful, but
011     * WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018     * 02111-1307, USA.
019     */
020    
021    package net.sf.japi.swing;
022    
023    import java.awt.BorderLayout;
024    import java.awt.Component;
025    import java.awt.Container;
026    import java.awt.Dimension;
027    import java.awt.Insets;
028    import java.io.Serializable;
029    import static java.lang.Math.max;
030    import java.util.ArrayList;
031    import java.util.List;
032    import javax.swing.JToolBar;
033    
034    /** A LayoutManager that manages a layout of a {@link Container} similar to {@link BorderLayout} but with an important difference, it is possible to
035     * add as many components to a side layout region as you want. The desired purpose is to serve as LayoutManager for containers that shall contain
036     * toolbars. So this is a LayoutManager you always were looking for.
037     * <p />
038     * Technically, this class is not a 100% replacement for {@link BorderLayout}. {@link JToolBar}'s UI ({@link
039     * javax.swing.plaf.basic.BasicToolBarUI}) directly looks for some features of the class {@link BorderLayout}, and if it is not {@link BorderLayout},
040     * it uses some defaults.
041     * This class has been developed to make these defaults work as good as possible.
042     * Though this class doesn't technically replace {@link BorderLayout} - neither is this class a subclass of {@link BorderLayout} nor does it provide
043     * <em>all</em> methods {@link BorderLayout} does - it still does practically.
044     * <p />
045     * The constant values {@link #NORTH}, {@link #SOUTH}, {@link #EAST}, {@link #WEST} and {@link #CENTER} are references to those of {@link
046     * BorderLayout}.
047     * The behaviour of {@link #CENTER} is that of {@link BorderLayout}: only one component can be added, subsequently added components overried all
048     * previously added.
049     * <p />
050     * The behaviour of {@link #NORTH}, {@link #SOUTH}, {@link #EAST} and {@link #WEST} differs from {@link BorderLayout}. The position and layout
051     * behaviour is the same, with the slight difference that this LayoutManager is able to manage more than on single component in these four regions.
052     * Subsequently added components are placed from the outer to the inner. The first added component is the outmost component of that region, the last
053     * added component is the innermost component of that region. To place a component to the innermost level, simply add it to the same region again.
054     * <p />
055     * Placing a component another level than the innermost of its destination region is currently not supported but might well be supported in future.
056     * <p />
057     * There are four possible ways of specifying a constraint:
058     * <ul>
059     *  <li>Use the geographical region constants from {@link BorderLayout}</li>
060     *  <li>Use the geographical region constraints from this class</li>
061     *  <li>Use an instance of {@link ToolBarConstraints}</li>
062     *  <li>Use an enum region constraint from {@link ToolBarConstraints.Region}</li>
063     * </ul>
064     * . The constraint may be one of the String constants from this class or {@link BorderLayout} or it may
065     * be a {@link ToolBarConstraints}.
066     * @todo support rtl containers the same way as {@link BorderLayout} does it.
067     * @todo test {@link ToolBarConstraints} and {@link ToolBarConstraints.Region}
068     * @author $Author: christianhujer $
069     * @version $Id: ToolBarLayout.java,v 1.2 2006/02/09 23:58:22 christianhujer Exp $
070     * @see BorderLayout
071     * @see JToolBar
072     */
073    @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext", "FieldAccessedSynchronizedAndUnsynchronized"}) public class ToolBarLayout extends BorderLayout {
074    
075        /** Serial Version. */
076        @SuppressWarnings({"AnalyzingVariableNaming"})
077        private static final long serialVersionUID = 1L;
078    
079        /** Horizontal Gap.
080         * @serial include
081         */
082        private int hgap;
083    
084        /** Vertical Gap.
085         * @serial include
086         */
087        private int vgap;
088    
089        /** Components in the north region.
090         * @serial include
091         */
092        private List<Component> north = new ArrayList<Component>();
093    
094        /** Components in the south region.
095         * @serial include
096         */
097        private List<Component> south = new ArrayList<Component>();
098    
099        /** Components in the east region.
100         * @serial include
101         */
102        private List<Component> east = new ArrayList<Component>();
103    
104        /** Components in the west region.
105         * @serial include
106         */
107        private List<Component> west = new ArrayList<Component>();
108    
109        /** Component in the center region.
110         * @serial include
111         */
112        private Component center;
113    
114        /** Create a ToolBarLayout with zero gaps. */
115        public ToolBarLayout() {
116            this(0, 0);
117        }
118    
119        /** Create a TooLBarLayout.
120         * @param hgap horizontal gap between components
121         * @param vgap vertical gap between components
122         */
123        public ToolBarLayout(final int hgap, final int vgap) {
124            this.hgap = hgap;
125            this.vgap = vgap;
126        }
127    
128        private void addLayoutComponent(final ToolBarConstraints.Region region, final Component comp) {
129            synchronized (comp.getTreeLock()) {
130                List<Component> list = null;
131                switch (region) {
132                    case NORTH: list = north; break;
133                    case SOUTH: list = south; break;
134                    case EAST:  list = east;  break;
135                    case WEST:  list = west;  break;
136                    case CENTER: center = comp; return;
137                }
138                assert list != null;
139                list.add(comp);
140            }
141        }
142    
143        private void addLayoutComponent(final ToolBarConstraints constraints, final Component comp) {
144            synchronized (comp.getTreeLock()) {
145                List<Component> list = null;
146                switch (constraints.region) {
147                    case NORTH: list = north; break;
148                    case SOUTH: list = south; break;
149                    case EAST:  list = east;  break;
150                    case WEST:  list = west;  break;
151                    case CENTER: center = comp; return;
152                }
153                int pos = constraints.position;
154                assert list != null;
155                if (pos > list.size() || pos < 0) {
156                    pos = list.size();
157                }
158                list.add(pos, comp);
159            }
160        }
161    
162        /** {@inheritDoc} */
163        @Override public void addLayoutComponent(final String name, final Component comp) {
164            synchronized (comp.getTreeLock()) {
165                if (name == null || CENTER.equals(name)) {
166                    center = comp;
167                } else if (NORTH.equals(name)) {
168                    north.add(comp);
169                } else if (SOUTH.equals(name)) {
170                    south.add(comp);
171                } else if (EAST.equals(name)) {
172                    east.add(comp);
173                } else if (WEST.equals(name)) {
174                    west.add(comp);
175                } else {
176                    throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name);
177                }
178            }
179        }
180    
181        /** {@inheritDoc} */
182        @Override public void addLayoutComponent(final Component comp, final Object constraints) {
183            synchronized (comp.getTreeLock()) {
184                if (constraints == null || constraints instanceof String) {
185                    addLayoutComponent((String)constraints, comp);
186                } else if (constraints instanceof ToolBarConstraints) {
187                    addLayoutComponent((ToolBarConstraints)constraints, comp);
188                } else if (constraints instanceof ToolBarConstraints.Region) {
189                    addLayoutComponent((ToolBarConstraints.Region)constraints, comp);
190                } else {
191                    throw new IllegalArgumentException("cannot add to layout: constraint must be a string (or null)");
192                }
193            }
194        }
195    
196        /** {@inheritDoc} */
197        @Override public float getLayoutAlignmentX(final Container parent) {
198            return 0.5f;
199        }
200    
201        /** {@inheritDoc} */
202        @Override public float getLayoutAlignmentY(final Container parent) {
203            return 0.5f;
204        }
205    
206        /** {@inheritDoc} */
207        @Override public void invalidateLayout(final Container target) {
208            /* Do Nothing, like BorderLayout. */
209        }
210    
211        /** {@inheritDoc} */
212        @Override public void layoutContainer(final Container target) {
213            synchronized (target.getTreeLock()) {
214                final Insets insets = target.getInsets();
215                Dimension dim;
216                int top = insets.top;
217                int bottom = target.getHeight() - insets.bottom;
218                int left = insets.left;
219                int right = target.getWidth() - insets.right;
220                for (final Component comp : north) {
221                    if (comp.isVisible()) {
222                        comp.setSize(right - left, comp.getHeight());
223                        dim = comp.getPreferredSize();
224                        comp.setBounds(left, top, right - left, dim.height);
225                        top += dim.height + vgap;
226                    }
227                }
228                for (final Component comp : south) {
229                    if (comp.isVisible()) {
230                        comp.setSize(right - left, comp.getHeight());
231                        dim = comp.getPreferredSize();
232                        comp.setBounds(left, bottom - dim.height, right - left, dim.height);
233                        bottom -= dim.height + vgap;
234                    }
235                }
236                for (final Component comp : east) {
237                    if (comp.isVisible()) {
238                        comp.setSize(comp.getWidth(), bottom - top);
239                        dim = comp.getPreferredSize();
240                        comp.setBounds(right - dim.width, top, dim.width, bottom - top);
241                        right -= dim.width + hgap;
242                    }
243                }
244                for (final Component comp : west) {
245                    if (comp.isVisible()) {
246                        comp.setSize(comp.getWidth(), bottom - top);
247                        dim = comp.getPreferredSize();
248                        comp.setBounds(left, top, dim.width, bottom - top);
249                        left += dim.width + hgap;
250                    }
251                }
252                if (center != null) {
253                    center.setBounds(left, top, right - left, bottom - top);
254                }
255            }
256        }
257    
258        /** {@inheritDoc} */
259        @Override public Dimension maximumLayoutSize(final Container target) {
260            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
261        }
262    
263        /** {@inheritDoc} */
264        @Override public Dimension minimumLayoutSize(final Container target) {
265            synchronized (target.getTreeLock()) {
266                Dimension dim;
267                final Dimension minimumLayoutSize = new Dimension(0, 0);
268                for (final Component comp : east) {
269                    dim = comp.getMinimumSize();
270                    minimumLayoutSize.width += dim.width + hgap;
271                    minimumLayoutSize.height = max(dim.height, minimumLayoutSize.height);
272                }
273                for (final Component comp : west) {
274                    dim = comp.getMinimumSize();
275                    minimumLayoutSize.width += dim.width + hgap;
276                    minimumLayoutSize.height = max(dim.height, minimumLayoutSize.height);
277                }
278                if (center != null) {
279                    dim = center.getMinimumSize();
280                    minimumLayoutSize.width += dim.width;
281                    minimumLayoutSize.height =max(dim.height, minimumLayoutSize.height);
282                }
283                for (final Component comp : north) {
284                    dim = comp.getMinimumSize();
285                    minimumLayoutSize.width = max(dim.width, minimumLayoutSize.width);
286                    minimumLayoutSize.height += dim.height + vgap;
287                }
288                for (final Component comp : south) {
289                    dim = comp.getMinimumSize();
290                    minimumLayoutSize.width = max(dim.width, minimumLayoutSize.width);
291                    minimumLayoutSize.height += dim.height + vgap;
292                }
293                final Insets insets = target.getInsets();
294                minimumLayoutSize.width += insets.left + insets.right;
295                minimumLayoutSize.height += insets.top + insets.bottom;
296                return minimumLayoutSize;
297            }
298        }
299    
300        /** {@inheritDoc} */
301        @Override public Dimension preferredLayoutSize(final Container target) {
302            synchronized (target.getTreeLock()) {
303                Dimension dim;
304                final Dimension preferredLayoutSize = new Dimension(0, 0);
305                for (final Component comp : east) {
306                    dim = comp.getPreferredSize();
307                    preferredLayoutSize.width += dim.width + hgap;
308                    preferredLayoutSize.height = max(dim.height, preferredLayoutSize.height);
309                }
310                for (final Component comp : west) {
311                    dim = comp.getPreferredSize();
312                    preferredLayoutSize.width += dim.width + hgap;
313                    preferredLayoutSize.height = max(dim.height, preferredLayoutSize.height);
314                }
315                if (center != null) {
316                    dim = center.getPreferredSize();
317                    preferredLayoutSize.width += dim.width;
318                    preferredLayoutSize.height =max(dim.height, preferredLayoutSize.height);
319                }
320                for (final Component comp : north) {
321                    dim = comp.getPreferredSize();
322                    preferredLayoutSize.width = max(dim.width, preferredLayoutSize.width);
323                    preferredLayoutSize.height += dim.height + vgap;
324                }
325                for (final Component comp : south) {
326                    dim = comp.getPreferredSize();
327                    preferredLayoutSize.width = max(dim.width, preferredLayoutSize.width);
328                    preferredLayoutSize.height += dim.height + vgap;
329                }
330                final Insets insets = target.getInsets();
331                preferredLayoutSize.width += insets.left + insets.right;
332                preferredLayoutSize.height += insets.top + insets.bottom;
333                return preferredLayoutSize;
334            }
335        }
336    
337        /** {@inheritDoc} */
338        @Override public void removeLayoutComponent(final Component comp) {
339            synchronized (comp.getTreeLock()) {
340                //noinspection ObjectEquality
341                if (comp == center) {
342                    center = null;
343                } else if (north.contains(comp)) {
344                    north.remove(comp);
345                } else if (south.contains(comp)) {
346                    south.remove(comp);
347                } else if (east.contains(comp)) {
348                    east.remove(comp);
349                } else if (west.contains(comp)) {
350                    west.remove(comp);
351                }
352            }
353        }
354    
355        /** {@inheritDoc} */
356        @Override public String toString() {
357            return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + ']';
358        }
359    
360        /** Class for ToolBarLayout constraints.
361         * @author $Author: christianhujer $
362         * @version $Id: ToolBarLayout.java,v 1.2 2006/02/09 23:58:22 christianhujer Exp $
363         */
364        public static class ToolBarConstraints implements Serializable {
365    
366            /** Serial Version. */
367            @SuppressWarnings({"AnalyzingVariableNaming"})
368            private static final long serialVersionUID = 1L;
369    
370            /** Enum for region.
371             * @author $Author: christianhujer $
372             * @version $Id: ToolBarLayout.java,v 1.2 2006/02/09 23:58:22 christianhujer Exp $
373             */
374            public enum Region {
375    
376                /** Constraint for center region. */
377                CENTER,
378    
379                /** Constraint for north region, last component. */
380                NORTH,
381    
382                /** Constraint for south region, last component. */
383                SOUTH,
384    
385                /** Constraint for east region, last component. */
386                EAST,
387    
388                /** Constraint for west region, last component. */
389                WEST;
390    
391            } // enum Region
392    
393            /** Constant for first position (0). */
394            public static final int FIRST = 0;
395    
396            /** Constant for last position (-1). */
397            public static final int LAST = -1;
398    
399            /** Region constraint.
400             * @serial include
401             */
402            public Region region;
403    
404            /** Position constraint.
405             * @serial include
406             */
407            public int position;
408    
409            /** Create Constraint. */
410            public ToolBarConstraints() {
411            }
412    
413            /** Create Constraint with values.
414             * @param region Region
415             * @param position Position, must be non-negative
416             */
417            public ToolBarConstraints(final Region region, final int position) {
418                this.region = region;
419                this.position = position;
420            }
421    
422        } // class ToolBarConstraint
423    
424    } // class ToolBarLayoutManager