001    /* JAPI - (Yet another (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 <a href="mailto:chris@riedquat.de">Christian Hujer</a>
069     * @see BorderLayout
070     * @see JToolBar
071     */
072    @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext", "FieldAccessedSynchronizedAndUnsynchronized"})
073    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        @SuppressWarnings({"deprecation"})
164        @Override public void addLayoutComponent(final String name, final Component comp) {
165            synchronized (comp.getTreeLock()) {
166                if (name == null || CENTER.equals(name)) {
167                    center = comp;
168                } else if (NORTH.equals(name)) {
169                    north.add(comp);
170                } else if (SOUTH.equals(name)) {
171                    south.add(comp);
172                } else if (EAST.equals(name)) {
173                    east.add(comp);
174                } else if (WEST.equals(name)) {
175                    west.add(comp);
176                } else {
177                    throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name);
178                }
179            }
180        }
181    
182        /** {@inheritDoc} */
183        @Override public void addLayoutComponent(final Component comp, final Object constraints) {
184            synchronized (comp.getTreeLock()) {
185                if (constraints == null || constraints instanceof String) {
186                    addLayoutComponent((String)constraints, comp);
187                } else if (constraints instanceof ToolBarConstraints) {
188                    addLayoutComponent((ToolBarConstraints)constraints, comp);
189                } else if (constraints instanceof ToolBarConstraints.Region) {
190                    addLayoutComponent((ToolBarConstraints.Region)constraints, comp);
191                } else {
192                    throw new IllegalArgumentException("cannot add to layout: constraint must be a string (or null)");
193                }
194            }
195        }
196    
197        /** {@inheritDoc} */
198        @Override public float getLayoutAlignmentX(final Container parent) {
199            return 0.5f;
200        }
201    
202        /** {@inheritDoc} */
203        @Override public float getLayoutAlignmentY(final Container parent) {
204            return 0.5f;
205        }
206    
207        /** {@inheritDoc} */
208        @Override public void invalidateLayout(final Container target) {
209            /* Do Nothing, like BorderLayout. */
210        }
211    
212        /** {@inheritDoc} */
213        @Override public void layoutContainer(final Container target) {
214            synchronized (target.getTreeLock()) {
215                final Insets insets = target.getInsets();
216                Dimension dim;
217                int top = insets.top;
218                int bottom = target.getHeight() - insets.bottom;
219                int left = insets.left;
220                int right = target.getWidth() - insets.right;
221                for (final Component comp : north) {
222                    if (comp.isVisible()) {
223                        comp.setSize(right - left, comp.getHeight());
224                        dim = comp.getPreferredSize();
225                        comp.setBounds(left, top, right - left, dim.height);
226                        top += dim.height + vgap;
227                    }
228                }
229                for (final Component comp : south) {
230                    if (comp.isVisible()) {
231                        comp.setSize(right - left, comp.getHeight());
232                        dim = comp.getPreferredSize();
233                        comp.setBounds(left, bottom - dim.height, right - left, dim.height);
234                        bottom -= dim.height + vgap;
235                    }
236                }
237                for (final Component comp : east) {
238                    if (comp.isVisible()) {
239                        comp.setSize(comp.getWidth(), bottom - top);
240                        dim = comp.getPreferredSize();
241                        comp.setBounds(right - dim.width, top, dim.width, bottom - top);
242                        right -= dim.width + hgap;
243                    }
244                }
245                for (final Component comp : west) {
246                    if (comp.isVisible()) {
247                        comp.setSize(comp.getWidth(), bottom - top);
248                        dim = comp.getPreferredSize();
249                        comp.setBounds(left, top, dim.width, bottom - top);
250                        left += dim.width + hgap;
251                    }
252                }
253                if (center != null) {
254                    center.setBounds(left, top, right - left, bottom - top);
255                }
256            }
257        }
258    
259        /** {@inheritDoc} */
260        @Override public Dimension maximumLayoutSize(final Container target) {
261            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
262        }
263    
264        /** {@inheritDoc} */
265        @Override public Dimension minimumLayoutSize(final Container target) {
266            synchronized (target.getTreeLock()) {
267                Dimension dim;
268                final Dimension minimumLayoutSize = new Dimension(0, 0);
269                for (final Component comp : east) {
270                    dim = comp.getMinimumSize();
271                    minimumLayoutSize.width += dim.width + hgap;
272                    minimumLayoutSize.height = max(dim.height, minimumLayoutSize.height);
273                }
274                for (final Component comp : west) {
275                    dim = comp.getMinimumSize();
276                    minimumLayoutSize.width += dim.width + hgap;
277                    minimumLayoutSize.height = max(dim.height, minimumLayoutSize.height);
278                }
279                if (center != null) {
280                    dim = center.getMinimumSize();
281                    minimumLayoutSize.width += dim.width;
282                    minimumLayoutSize.height =max(dim.height, minimumLayoutSize.height);
283                }
284                for (final Component comp : north) {
285                    dim = comp.getMinimumSize();
286                    minimumLayoutSize.width = max(dim.width, minimumLayoutSize.width);
287                    minimumLayoutSize.height += dim.height + vgap;
288                }
289                for (final Component comp : south) {
290                    dim = comp.getMinimumSize();
291                    minimumLayoutSize.width = max(dim.width, minimumLayoutSize.width);
292                    minimumLayoutSize.height += dim.height + vgap;
293                }
294                final Insets insets = target.getInsets();
295                minimumLayoutSize.width += insets.left + insets.right;
296                minimumLayoutSize.height += insets.top + insets.bottom;
297                return minimumLayoutSize;
298            }
299        }
300    
301        /** {@inheritDoc} */
302        @Override public Dimension preferredLayoutSize(final Container target) {
303            synchronized (target.getTreeLock()) {
304                Dimension dim;
305                final Dimension preferredLayoutSize = new Dimension(0, 0);
306                for (final Component comp : east) {
307                    dim = comp.getPreferredSize();
308                    preferredLayoutSize.width += dim.width + hgap;
309                    preferredLayoutSize.height = max(dim.height, preferredLayoutSize.height);
310                }
311                for (final Component comp : west) {
312                    dim = comp.getPreferredSize();
313                    preferredLayoutSize.width += dim.width + hgap;
314                    preferredLayoutSize.height = max(dim.height, preferredLayoutSize.height);
315                }
316                if (center != null) {
317                    dim = center.getPreferredSize();
318                    preferredLayoutSize.width += dim.width;
319                    preferredLayoutSize.height =max(dim.height, preferredLayoutSize.height);
320                }
321                for (final Component comp : north) {
322                    dim = comp.getPreferredSize();
323                    preferredLayoutSize.width = max(dim.width, preferredLayoutSize.width);
324                    preferredLayoutSize.height += dim.height + vgap;
325                }
326                for (final Component comp : south) {
327                    dim = comp.getPreferredSize();
328                    preferredLayoutSize.width = max(dim.width, preferredLayoutSize.width);
329                    preferredLayoutSize.height += dim.height + vgap;
330                }
331                final Insets insets = target.getInsets();
332                preferredLayoutSize.width += insets.left + insets.right;
333                preferredLayoutSize.height += insets.top + insets.bottom;
334                return preferredLayoutSize;
335            }
336        }
337    
338        /** {@inheritDoc} */
339        @Override public void removeLayoutComponent(final Component comp) {
340            synchronized (comp.getTreeLock()) {
341                //noinspection ObjectEquality
342                if (comp == center) {
343                    center = null;
344                } else if (north.contains(comp)) {
345                    north.remove(comp);
346                } else if (south.contains(comp)) {
347                    south.remove(comp);
348                } else if (east.contains(comp)) {
349                    east.remove(comp);
350                } else if (west.contains(comp)) {
351                    west.remove(comp);
352                }
353            }
354        }
355    
356        /** {@inheritDoc} */
357        @Override public String toString() {
358            return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + ']';
359        }
360    
361        /** Class for ToolBarLayout constraints.
362         * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a>
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 <a href="mailto:chris@riedquat.de">Christian Hujer</a>
372             */
373            public enum Region {
374    
375                /** Constraint for center region. */
376                CENTER,
377    
378                /** Constraint for north region, last component. */
379                NORTH,
380    
381                /** Constraint for south region, last component. */
382                SOUTH,
383    
384                /** Constraint for east region, last component. */
385                EAST,
386    
387                /** Constraint for west region, last component. */
388                WEST,
389    
390            } // enum Region
391    
392            /** Constant for first position (0). */
393            public static final int FIRST = 0;
394    
395            /** Constant for last position (-1). */
396            public static final int LAST = -1;
397    
398            /** Region constraint.
399             * @serial include
400             */
401            public Region region;
402    
403            /** Position constraint.
404             * @serial include
405             */
406            public int position;
407    
408            /** Create Constraint. */
409            public ToolBarConstraints() {
410            }
411    
412            /** Create Constraint with values.
413             * @param region Region
414             * @param position Position, must be non-negative
415             */
416            public ToolBarConstraints(final Region region, final int position) {
417                this.region = region;
418                this.position = position;
419            }
420    
421        } // class ToolBarConstraint
422    
423    } // class ToolBarLayoutManager