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