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