1 /* $Id$ */
2 /***************************************************************************
3  *                   (C) Copyright 2003-2010 - Stendhal                    *
4  ***************************************************************************
5  ***************************************************************************
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  ***************************************************************************/
13 package games.stendhal.client.gui.layout;
14 
15 import java.awt.Component;
16 import java.awt.Container;
17 import java.awt.Dimension;
18 import java.awt.Insets;
19 import java.awt.LayoutManager2;
20 import java.util.Arrays;
21 import java.util.EnumSet;
22 import java.util.IdentityHashMap;
23 import java.util.Map;
24 
25 import javax.swing.JComponent;
26 
27 /**
28  * A simple layout manager that does what BoxLayout fails to do,
29  * and provides a predictable layout with few hidden interactions.
30  * <p>
31  * Minimum size is properly supported within reasonable limits.
32  * <p>
33  * Maximum size has only a soft support, i.e. maximum size works
34  * as a limiting preferred size, even if the child component itself
35  * would suggest a larger size.
36  * <p>
37  * Component alignment is supported in the direction perpendicular to the
38  * layout direction.
39  * <p>
40  * SBoxLayout supports constraints of type {@link SLayout}. A single constraint
41  * flag can passed as the second parameter to
42  * <code>Container.add(Component c, Object constraint)</code>. Constraints can
43  * be combined using the object returned by {@link #constraint(SLayout...)} as
44  * the constraints object.
45  */
46 public class SBoxLayout implements LayoutManager2 {
47 	/*
48 	 * Implementation considerations:
49 	 * 	- MaxSize is not fully supported. It could be done the same way as MinSize is now
50 	 *
51 	 * 	- Expanding and contracting the components is done by same amount for all
52 	 * 	the components that are resized (unless forbidden by minimum/maximum size
53 	 * 	constraints). Should it be relative to the component size instead?
54 	 *
55 	 * Further refinement:
56 	 * 	Layout management in swing is dumb (and not just buggy for the fundamental
57 	 * 	layout managers). Any change in a child component will result in revalidation
58 	 * 	of the whole component tree until the first validation root (which normally
59 	 *	is the top level window). This is a huge performance problem as changing a
60 	 *	single number in the StatsPanel will result in the whole window layout being
61 	 *	redone - and 3 changes in a second is normal.
62 	 *		Therefore, investigate if it's feasible to make a component that acts as
63 	 *	a validation root, and passes the invalidation upwards only if its size changes.
64 	 */
65 
66 	public static final boolean VERTICAL = false;
67 	public static final boolean HORIZONTAL = true;
68 
69 	/** Common padding width where padding or border is wanted. */
70 	public static final int COMMON_PADDING = 5;
71 
72 	/**
73 	 * Create a constraints object.
74 	 *
75 	 * @param flags constraint flags
76 	 * @return constraints object
77 	 */
constraint(SLayout .... flags)78 	public static Object constraint(SLayout ... flags) {
79 		EnumSet<SLayout> obj = EnumSet.noneOf(SLayout.class);
80 		for (SLayout flag : flags) {
81 			obj.add(flag);
82 		}
83 		return obj;
84 	}
85 
86 	private static final Direction horizontalDirection = new HDirection();
87 	private static final Direction verticalDirection = new VDirection();
88 
89 	/**
90 	 * Layout constraints of the child components.
91 	 */
92 	private final Map<Component, EnumSet<SLayout>> constraints;
93 
94 	/**
95 	 * The direction object. All the dimension calculations are
96 	 * delegated to this.
97 	 */
98 	private final Direction d;
99 
100 	/** Preciously calculated dimension data, or <code>null</code> if it has been invalidated. */
101 	private Dimension cachedMinimum, cachedMaximum, cachedPreferred;
102 
103 	/** Amount of axially expandable components. */
104 	private int expandable;
105 	/** Amount of padding between components. */
106 	private int padding;
107 
108 	/**
109 	 * Create a new SBoxLayout.
110 	 *
111 	 * @param direction layout direction
112 	 */
SBoxLayout(boolean direction)113 	public SBoxLayout(boolean direction) {
114 		constraints = new IdentityHashMap<>();
115 		if (direction == VERTICAL) {
116 			d = verticalDirection;
117 		} else {
118 			d = horizontalDirection;
119 		}
120 	}
121 
122 	/**
123 	 * Create a new SBoxLayout with padding between components.
124 	 *
125 	 * @param direction layout direction
126 	 * @param padding component padding in pixels
127 	 */
SBoxLayout(boolean direction, int padding)128 	public SBoxLayout(boolean direction, int padding) {
129 		this(direction);
130 		setPadding(padding);
131 	}
132 
133 	/**
134 	 * Set the padding between the components. Typically you should use either
135 	 * 0 (the default), or COMMON_PADDING for consistent look. For the padding
136 	 * around everything use appropriate empty border instead.
137 	 *
138 	 * @param padding pixel width of the padding
139 	 */
setPadding(int padding)140 	public final void setPadding(int padding) {
141 		this.padding = padding;
142 	}
143 
144 	@Override
addLayoutComponent(Component component, Object flags)145 	public void addLayoutComponent(Component component, Object flags) {
146 		EnumSet<SLayout> constraintFlags = EnumSet.noneOf(SLayout.class);
147 		if (flags != null) {
148 			if (flags instanceof SLayout) {
149 				constraintFlags.add(d.translate((SLayout) flags));
150 			} else if (flags instanceof EnumSet<?>) {
151 				translateFlags((EnumSet<?>) flags, constraintFlags);
152 			} else {
153 				throw new IllegalArgumentException("Invalid flags: " + flags);
154 			}
155 
156 			// Keep count of expandable items
157 			if (constraintFlags.contains(SLayout.EXPAND_AXIAL)) {
158 				expandable++;
159 			}
160 		}
161 		constraints.put(component, constraintFlags);
162 	}
163 
164 	@SuppressWarnings("unlikely-arg-type")
translateFlags(EnumSet<?> rawFlags, EnumSet<SLayout> constraintFlags)165 	private void translateFlags(EnumSet<?> rawFlags, EnumSet<SLayout> constraintFlags) {
166 		Arrays.stream(SLayout.values()).filter(rawFlags::contains).map(d::translate).forEach(constraintFlags::add);
167 	}
168 
169 	/*
170 	 * (non-Javadoc)
171 	 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String, java.awt.Component)
172 	 */
173 	@Override
addLayoutComponent(String id, Component component)174 	public void addLayoutComponent(String id, Component component) {
175 	}
176 
177 	/**
178 	 * Add to the primary dimension.
179 	 *
180 	 * @param result the dimension to be expanded
181 	 * @param length the expanding amount
182 	 */
addToPrimary(Dimension result, int length)183 	private void addToPrimary(Dimension result, int length) {
184 		d.setPrimary(result, d.getPrimary(result) + length);
185 	}
186 
187 	/**
188 	 * Expand a <code>Dimension</code> so that it can include
189 	 * another <code>Dimension</code>.
190 	 *
191 	 * @param result The dimension to be expanded
192 	 * @param dim limiting dimension
193 	 */
expand(Dimension result, Dimension dim)194 	private void expand(Dimension result, Dimension dim) {
195 		result.width = Math.max(result.width, dim.width);
196 		result.height = Math.max(result.height, dim.height);
197 	}
198 
199 	/*
200 	 * (non-Javadoc)
201 	 * @see java.awt.LayoutManager2#getLayoutAlignmentX(java.awt.Container)
202 	 */
203 	@Override
getLayoutAlignmentX(Container target)204 	public float getLayoutAlignmentX(Container target) {
205 		// The specs don't tell what this actually should do
206 		return 0;
207 	}
208 
209 	/*
210 	 * (non-Javadoc)
211 	 * @see java.awt.LayoutManager2#getLayoutAlignmentY(java.awt.Container)
212 	 */
213 	@Override
getLayoutAlignmentY(Container target)214 	public float getLayoutAlignmentY(Container target) {
215 		// The specs don't tell what this actually should do
216 		return 0;
217 	}
218 
219 	/**
220 	 * Get a components preferred dimensions restricted by
221 	 * the maximum and minimum constraints.
222 	 *
223 	 * @param comp the component to examine
224 	 * @return constraint adjusted preferred dimensions
225 	 */
getPreferred(Component comp)226 	private Dimension getPreferred(Component comp) {
227 		Dimension dim = comp.getPreferredSize();
228 
229 		expand(dim, comp.getMinimumSize());
230 		shrink(dim, comp.getMaximumSize());
231 
232 		return dim;
233 	}
234 
235 	/*
236 	 * (non-Javadoc)
237 	 * @see java.awt.LayoutManager2#invalidateLayout(java.awt.Container)
238 	 */
239 	@Override
invalidateLayout(Container target)240 	public void invalidateLayout(Container target) {
241 		cachedMinimum = null;
242 		cachedMaximum = null;
243 		cachedPreferred = null;
244 	}
245 
246 	/*
247 	 * (non-Javadoc)
248 	 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
249 	 */
250 	@Override
layoutContainer(Container parent)251 	public void layoutContainer(Container parent) {
252 		// Maximum dimensions available for use
253 		Dimension realDim = parent.getSize();
254 		Insets insets = parent.getInsets();
255 
256 		Dimension preferred = preferredLayoutSize(parent);
257 
258 		final int stretch = d.getPrimary(realDim) - d.getPrimary(preferred);
259 
260 		// remove the insets for the actual area we have in use
261 		realDim.width -= insets.left + insets.right;
262 		realDim.height -= insets.top + insets.bottom;
263 
264 		Dimension position = new Dimension(insets.left, insets.top);
265 
266 		// Check the conditions, and pass the task to the proper layout method
267 		if (stretch >= 0) {
268 			layoutSufficientSpace(parent, realDim, position, stretch);
269 		} else {
270 			Dimension minDim = minimumLayoutSize(parent);
271 			// remove the insets for the actual area we have in use
272 			minDim.width -= insets.left + insets.right;
273 			minDim.height -= insets.top + insets.bottom;
274 			final int squeeze = d.getPrimary(realDim) - d.getPrimary(minDim);
275 			if (squeeze < 0) {
276 				layoutUnderMinimumSpace(parent, realDim, position);
277 			} else {
278 				layoutWithSqueeze(parent, realDim, position, stretch);
279 			}
280 		}
281 	}
282 
283 	/**
284 	 * Lay out the components, when we have sufficient space to do so.
285 	 *
286 	 * @param parent the parent container of the components
287 	 * @param realDim the dimensions of the space in use
288 	 * @param startPosition top left corner
289 	 * @param stretch the total amount to stretch the components
290 	 */
layoutSufficientSpace(Container parent, Dimension realDim, Dimension startPosition, int stretch)291 	private void layoutSufficientSpace(Container parent, Dimension realDim,
292 			Dimension startPosition, int stretch) {
293 		int remainingStretch = stretch;
294 		int remainingExpandable = expandable;
295 
296 		// Easy - we got at least the dimensions we asked for
297 		for (Component c : parent.getComponents()) {
298 			// Skip hidden components
299 			if (c.isVisible()) {
300 				Dimension cPref = getPreferred(c);
301 				shrink(cPref, realDim);
302 				int xAlign = 0;
303 				int yAlign = 0;
304 
305 				EnumSet<SLayout> flags = constraints.get(c);
306 				if (flags.contains(SLayout.EXPAND_PERPENDICULAR)) {
307 					d.setSecondary(cPref, d.getSecondary(realDim));
308 				} else {
309 					xAlign = getXAlignment(c, realDim);
310 					yAlign = getYAlignment(c, realDim);
311 				}
312 
313 				if ((remainingStretch > 0) && flags.contains(SLayout.EXPAND_AXIAL)) {
314 					// Stretch the components that allow it, if needed
315 					int add = Math.max(1, remainingStretch / remainingExpandable);
316 					addToPrimary(cPref, add);
317 					remainingStretch -= add;
318 					remainingExpandable--;
319 				}
320 				c.setBounds(startPosition.width + xAlign, startPosition.height + yAlign, cPref.width, cPref.height);
321 
322 				// Move the coordinates of the next component by the size of the
323 				// previous + padding
324 				shiftByPrimary(startPosition, cPref);
325 				addToPrimary(startPosition, padding);
326 			}
327 		}
328 	}
329 
330 	/**
331 	 * Lay out the components in a smaller space than the specified minimum.
332 	 * Just gives the components their required minimum, until the space runs out.
333 	 *
334 	 * @param parent the parent container of the components
335 	 * @param realDim the dimensions of the space in use
336 	 * @param startPosition top left corner
337 	 */
layoutUnderMinimumSpace(Container parent, Dimension realDim, Dimension startPosition)338 	private void layoutUnderMinimumSpace(Container parent, Dimension realDim, Dimension startPosition) {
339 		for (Component c : parent.getComponents()) {
340 			// Skip hidden components
341 			if (c.isVisible()) {
342 				Dimension compSize = c.getMinimumSize();
343 				shrink(compSize, realDim);
344 				int xAlign = 0;
345 				int yAlign = 0;
346 
347 				EnumSet<SLayout> flags = constraints.get(c);
348 				if (flags.contains(SLayout.EXPAND_PERPENDICULAR)) {
349 					d.setSecondary(compSize, d.getSecondary(realDim));
350 				} else {
351 					xAlign = getXAlignment(c, realDim);
352 					yAlign = getYAlignment(c, realDim);
353 				}
354 
355 				int shrink = d.getPrimary(realDim) - d.getPrimary(compSize) - d.getPrimary(startPosition);
356 				if (shrink < 0) {
357 					// avoid < 0 sizes
358 					shrink = Math.max(-d.getPrimary(compSize), shrink);
359 					addToPrimary(compSize, shrink);
360 				}
361 				c.setBounds(startPosition.width + xAlign, startPosition.height + yAlign, compSize.width, compSize.height);
362 
363 				// Move the coordinates of the next component by the size of the
364 				// previous + padding
365 				shiftByPrimary(startPosition, compSize);
366 				addToPrimary(startPosition, padding);
367 			}
368 		}
369 	}
370 
371 	/**
372 	 * Lay out in sufficient, but smaller space than preferred.
373 	 * Respects the minimum dimensions, and squeezes only the components
374 	 * that do not break that constraint.
375 	 *
376 	 * @param parent the parent container of the components
377 	 * @param realDim the dimensions of the space in use
378 	 * @param startPosition top left corner
379 	 * @param stretch the total amount to stretch the components (negative)
380 	 */
layoutWithSqueeze(Container parent, Dimension realDim, Dimension startPosition, int stretch)381 	private void layoutWithSqueeze(Container parent, Dimension realDim,
382 			Dimension startPosition, int stretch) {
383 		/*
384 		 * We can squeeze the components without violating the constraints,
385 		 * but calculations take a bit of effort.
386 		 */
387 		int numComponents = parent.getComponents().length;
388 		int[] dim;
389 		boolean[] violations = new boolean[numComponents];
390 		int numViolations = 0;
391 
392 		// Only visible components can be squeezed
393 		int numVisible = 0;
394 		for (Component c : parent.getComponents()) {
395 			if (c.isVisible()) {
396 				numVisible++;
397 			}
398 		}
399 
400 		int numSqueezable;
401 		/*
402 		 * Start by trying to squeeze all, and then mark as
403 		 * incompressible the components whose size would become
404 		 * too small. Repeat until only those components that can
405 		 * take it, get squeezed.
406 		 */
407 		do {
408 			dim = new int[numComponents];
409 
410 			numSqueezable = numVisible;
411 			for (boolean b : violations) {
412 				if (b) {
413 					numSqueezable--;
414 				}
415 			}
416 			int remainingSqueeze = -stretch;
417 			numViolations = 0;
418 
419 			for (int i = 0; i < numComponents; i++) {
420 				Component c = parent.getComponents()[i];
421 
422 				if (c.isVisible()) {
423 					Dimension cPref = getPreferred(c);
424 
425 					int adjust = 0;
426 					if (remainingSqueeze > 0 && !violations[i]) {
427 						adjust = Math.max(1, remainingSqueeze / numSqueezable);
428 						remainingSqueeze -= adjust;
429 						numSqueezable--;
430 					}
431 					dim[i] = d.getPrimary(cPref) - adjust;
432 					if (dim[i] < d.getPrimary(c.getMinimumSize())) {
433 						violations[i] = true;
434 						numViolations++;
435 					}
436 				} else {
437 					dim[i] = 0;
438 				}
439 			}
440 		} while (numViolations != 0);
441 
442 		// Done with the dimensions, now lay it out
443 		for (int i = 0; i < numComponents; i++) {
444 			Component c = parent.getComponents()[i];
445 			// skip hidden components
446 			if (c.isVisible()) {
447 				Dimension cPref = getPreferred(c);
448 				shrink(cPref, realDim);
449 				int xAlign = 0;
450 				int yAlign = 0;
451 
452 				EnumSet<SLayout> flags = constraints.get(c);
453 				if (flags.contains(SLayout.EXPAND_PERPENDICULAR)) {
454 					d.setSecondary(cPref, d.getSecondary(realDim));
455 				} else {
456 					xAlign = getXAlignment(c, realDim);
457 					yAlign = getYAlignment(c, realDim);
458 				}
459 				d.setPrimary(cPref, dim[i]);
460 				c.setBounds(startPosition.width + xAlign, startPosition.height + yAlign, cPref.width, cPref.height);
461 
462 				// Move the coordinates of the next component by the size of the
463 				// previous + padding
464 				shiftByPrimary(startPosition, cPref);
465 				addToPrimary(startPosition, padding);
466 			}
467 		}
468 	}
469 
470 	/**
471 	 * Get the x alignment of a child component.
472 	 *
473 	 * @param c component
474 	 * @param available available space
475 	 * @return x alignment in pixels
476 	 */
getXAlignment(Component c, Dimension available)477 	private int getXAlignment(Component c, Dimension available) {
478 		if (d == horizontalDirection) {
479 			return 0;
480 		} else {
481 			return getPerpendicularAlignment(c, available);
482 		}
483 	}
484 
485 	/**
486 	 * Get the y alignment of a child component.
487 	 *
488 	 * @param c component
489 	 * @param available available space
490 	 * @return y alignment in pixels
491 	 */
getYAlignment(Component c, Dimension available)492 	private int getYAlignment(Component c, Dimension available) {
493 		if (d == horizontalDirection) {
494 			return getPerpendicularAlignment(c, available);
495 		} else {
496 			return 0;
497 		}
498 	}
499 
500 	/**
501 	 * Get the pixel alignment of a component in the perpendicular direction.
502 	 *
503 	 * @param c component
504 	 * @param available size of the container of the component
505 	 * @return pixel alignment
506 	 */
getPerpendicularAlignment(Component c, Dimension available)507 	private int getPerpendicularAlignment(Component c, Dimension available) {
508 		int align = 0;
509 		int extra = d.getSecondary(available) - d.getSecondary(c.getPreferredSize());
510 		if (extra > 0) {
511 			align = (int) (extra * d.getComponentAlignment(c));
512 		}
513 
514 		return align;
515 	}
516 
517 	/*
518 	 * (non-Javadoc)
519 	 * @see java.awt.LayoutManager2#maximumLayoutSize(java.awt.Container)
520 	 */
521 	@Override
maximumLayoutSize(Container parent)522 	public Dimension maximumLayoutSize(Container parent) {
523 		/*
524 		 * The specs are *very* vague about what this should do (and
525 		 * helpfully name the parameter "target", sigh), but returning
526 		 * the max size of the whole layout seems to be what's wanted,
527 		 * and most other tries crash in a way or another.
528 		 */
529 		if (cachedMaximum != null) {
530 			return new Dimension(cachedMaximum);
531 		}
532 
533 		Dimension result = new Dimension();
534 
535 		int numVisible = 0;
536 		for (Component c : parent.getComponents()) {
537 			// Skip hidden components
538 			if (c.isVisible()) {
539 				numVisible++;
540 				d.addComponentDimensions(result, c.getMaximumSize());
541 			}
542 		}
543 
544 		// Take padding in account
545 		if (numVisible > 1) {
546 			d.setPrimary(result, safeAdd(d.getPrimary(result), (numVisible - 1) * padding));
547 		}
548 
549 		// Expand by the insets
550 		Insets insets = parent.getInsets();
551 		result.width = safeAdd(result.width, insets.left + insets.right);
552 		result.height = safeAdd(result.height, insets.top + insets.bottom);
553 
554 		cachedMaximum = result;
555 		return result;
556 	}
557 
558 	/*
559 	 * (non-Javadoc)
560 	 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
561 	 */
562 	@Override
minimumLayoutSize(Container parent)563 	public Dimension minimumLayoutSize(Container parent) {
564 		if (cachedMinimum != null) {
565 			return new Dimension(cachedMinimum);
566 		}
567 		Dimension result = new Dimension();
568 
569 		int numVisible = 0;
570 		for (Component c : parent.getComponents()) {
571 			// Skip hidden components
572 			if (c.isVisible()) {
573 				numVisible++;
574 				d.addComponentDimensions(result, c.getMinimumSize());
575 			}
576 		}
577 
578 		// Take padding in account
579 		if (numVisible > 1) {
580 			d.setPrimary(result, safeAdd(d.getPrimary(result), (numVisible - 1) * padding));
581 		}
582 
583 		// Expand by the insets
584 		Insets insets = parent.getInsets();
585 		result.width += insets.left + insets.right;
586 		result.height += insets.top + insets.bottom;
587 
588 		cachedMinimum = result;
589 		return result;
590 	}
591 
592 	/*
593 	 * (non-Javadoc)
594 	 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
595 	 */
596 	@Override
preferredLayoutSize(Container parent)597 	public Dimension preferredLayoutSize(Container parent) {
598 		if (cachedPreferred != null) {
599 			return new Dimension(cachedPreferred);
600 		}
601 		Dimension result = new Dimension();
602 
603 		int numVisible = 0;
604 		for (Component c : parent.getComponents()) {
605 			// Skip hidden components
606 			if (c.isVisible()) {
607 				numVisible++;
608 				d.addComponentDimensions(result, getPreferred(c));
609 			}
610 		}
611 
612 		// Take padding in account
613 		if (numVisible > 1) {
614 			d.setPrimary(result, safeAdd(d.getPrimary(result), (numVisible - 1) * padding));
615 		}
616 
617 		// Expand by the insets
618 		Insets insets = parent.getInsets();
619 		result.width = safeAdd(result.width, insets.left + insets.right);
620 		result.height = safeAdd(result.height, insets.top + insets.bottom);
621 
622 		/*
623 		 * Check the constraints of the parent. Unlike the standard layout
624 		 * managers we should never suggest sizes outside the range that
625 		 * the parent should do.
626 		 */
627 		Dimension maxDim = parent.getMaximumSize();
628 		Dimension minDim = parent.getMinimumSize();
629 
630 		/*
631 		 *  Despite what said above, the return value can still be smaller
632 		 *  than the specified minimum values, if the user has set
633 		 *  inconsistent minimum and maximum constraints.
634 		 */
635 		expand(result, minDim);
636 		shrink(result, maxDim);
637 
638 		cachedPreferred = result;
639 		return result;
640 	}
641 
642 	/*
643 	 * (non-Javadoc)
644 	 * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
645 	 */
646 	@Override
removeLayoutComponent(Component component)647 	public void removeLayoutComponent(Component component) {
648 		EnumSet<SLayout> constr = constraints.get(component);
649 		if (constr.contains(SLayout.EXPAND_AXIAL)) {
650 			expandable--;
651 		}
652 
653 		constraints.remove(component);
654 	}
655 
656 	/**
657 	 * Expand a dimension by the primary dimension of another.
658 	 *
659 	 * @param result the dimension to be expanded
660 	 * @param dim the expanding dimension
661 	 */
shiftByPrimary(Dimension result, Dimension dim)662 	private void shiftByPrimary(Dimension result, Dimension dim) {
663 		addToPrimary(result, d.getPrimary(dim));
664 	}
665 
666 	/**
667 	 * Shrink a <code>Dimension</code> so that it does not exceed
668 	 * the limits of another.
669 	 *
670 	 * @param result The dimension to be shrunk
671 	 * @param dim limiting dimension
672 	 */
shrink(Dimension result, Dimension dim)673 	private void shrink(Dimension result, Dimension dim) {
674 		result.width = Math.min(result.width, dim.width);
675 		result.height = Math.min(result.height, dim.height);
676 	}
677 
678 	/**
679 	 * A safe addition for dimensions. Returns Integer.MAX_VALUE if the
680 	 * addition would overflow. Some components do set that to their maximum
681 	 * size so they'd overflow if there are other components or insets.
682 	 *
683 	 * @param a
684 	 * @param b
685 	 * @return sum of a and b, or Integer.MAX_VALUE
686 	 */
safeAdd(int a, int b)687 	private static int safeAdd(int a, int b) {
688 		int tmp = a + b;
689 		if (tmp >= 0) {
690 			return tmp;
691 		} else {
692 			return Integer.MAX_VALUE;
693 		}
694 	}
695 
696 	/**
697 	 * An abstraction for various direction dependent operations.
698 	 */
699 	private interface Direction {
700 		/**
701 		 * Translate X and Y to axial and perpendicular.
702 		 * @param dir
703 		 * @return SLayout
704 		 */
translate(SLayout dir)705 		SLayout translate(SLayout dir);
706 
707 		/**
708 		 * Get the alignment of the component perpendicular to the layout axis.
709 		 *
710 		 * @param component component to examine
711 		 * @return component alignment
712 		 */
getComponentAlignment(Component component)713 		float getComponentAlignment(Component component);
714 
715 		/**
716 		 * Expand a dimension by a component's dimensions.
717 		 *
718 		 * @param result the dimension to be expanded
719 		 * @param dim the dimensions to be added
720 		 */
addComponentDimensions(Dimension result, Dimension dim)721 		void addComponentDimensions(Dimension result, Dimension dim);
722 
723 		/**
724 		 * Set primary dimension.
725 		 *
726 		 * @param result the dimension to be modified
727 		 * @param length
728 		 */
setPrimary(Dimension result, int length)729 		void setPrimary(Dimension result, int length);
730 
731 		/**
732 		 * Set secondary dimension.
733 		 *
734 		 * @param result the dimension to be modified
735 		 * @param length
736 		 */
setSecondary(Dimension result, int length)737 		void setSecondary(Dimension result, int length);
738 
739 		/**
740 		 * Get the dimension along the layout axis.
741 		 *
742 		 * @param dim
743 		 * @return primary dimension
744 		 */
getPrimary(Dimension dim)745 		int getPrimary(Dimension dim);
746 
747 		/**
748 		 * Get the dimension perpendicular to the layout axis.
749 		 *
750 		 * @param dim
751 		 * @return secondary dimension
752 		 */
getSecondary(Dimension dim)753 		int getSecondary(Dimension dim);
754 	}
755 
756 	/**
757 	 * Horizontal direction calculations.
758 	 */
759 	private static class HDirection implements Direction {
760 		@Override
translate(SLayout dir)761 		public SLayout translate(SLayout dir) {
762 			if (dir == SLayout.EXPAND_X) {
763 				dir = SLayout.EXPAND_AXIAL;
764 			} else if (dir == SLayout.EXPAND_Y) {
765 				dir = SLayout.EXPAND_PERPENDICULAR;
766 			}
767 
768 			return dir;
769 		}
770 
771 		@Override
addComponentDimensions(Dimension result, Dimension dim)772 		public void addComponentDimensions(Dimension result, Dimension dim) {
773 			// Avoid integer overflows
774 			result.width = safeAdd(result.width, dim.width);
775 			result.height = Math.max(result.height, dim.height);
776 		}
777 
778 		@Override
getPrimary(Dimension dim)779 		public int getPrimary(Dimension dim) {
780 			return dim.width;
781 		}
782 
783 		@Override
getSecondary(Dimension dim)784 		public int getSecondary(Dimension dim) {
785 			return dim.height;
786 		}
787 
788 		@Override
setPrimary(Dimension result, int length)789 		public void setPrimary(Dimension result, int length) {
790 			result.width = length;
791 		}
792 
793 		@Override
setSecondary(Dimension result, int length)794 		public void setSecondary(Dimension result, int length) {
795 			result.height = length;
796 		}
797 
798 		@Override
getComponentAlignment(Component component)799 		public float getComponentAlignment(Component component) {
800 			return component.getAlignmentY();
801 		}
802 	}
803 
804 	/**
805 	 * Vertical dimension calculations.
806 	 */
807 	private static class VDirection implements Direction  {
808 		@Override
translate(SLayout dir)809 		public SLayout translate(SLayout dir) {
810 			if (dir == SLayout.EXPAND_X) {
811 				dir = SLayout.EXPAND_PERPENDICULAR;
812 			} else if (dir == SLayout.EXPAND_Y) {
813 				dir = SLayout.EXPAND_AXIAL;
814 			}
815 
816 			return dir;
817 		}
818 
819 		@Override
addComponentDimensions(Dimension result, Dimension dim)820 		public void addComponentDimensions(Dimension result, Dimension dim) {
821 			result.width = Math.max(result.width, dim.width);
822 			// Avoid integer overflows
823 			result.height = safeAdd(result.height, dim.height);
824 		}
825 
826 		@Override
getPrimary(Dimension dim)827 		public int getPrimary(Dimension dim) {
828 			return dim.height;
829 		}
830 
831 		@Override
getSecondary(Dimension dim)832 		public int getSecondary(Dimension dim) {
833 			return dim.width;
834 		}
835 
836 		@Override
setPrimary(Dimension result, int length)837 		public void setPrimary(Dimension result, int length) {
838 			result.height = length;
839 		}
840 
841 		@Override
setSecondary(Dimension result, int length)842 		public void setSecondary(Dimension result, int length) {
843 			result.width = length;
844 		}
845 
846 		@Override
getComponentAlignment(Component component)847 		public float getComponentAlignment(Component component) {
848 			return component.getAlignmentX();
849 		}
850 	}
851 
852 	/**
853 	 * An utility component for layout.
854 	 */
855 	private static class Spring extends JComponent {
856 	}
857 
858 	/**
859 	 * Add a utility component that expands by default, to a container using
860 	 * SBoxLayout. Adding it rather than just creating the component is a
861 	 * workaround for components not passing information about new subcomponents
862 	 * if the user explicitly specifies the constraints.
863 	 *
864 	 * @param target the container where to add a string to
865 	 * @return A spring with preferred dimensions 0, 0.
866 	 */
addSpring(Container target)867 	public static JComponent addSpring(Container target) {
868 		JComponent spring = new Spring();
869 		target.add(spring, SLayout.EXPAND_AXIAL);
870 
871 		return spring;
872 	}
873 
874 	/**
875 	 * A convenience method for creating a container using SBoxLayout.
876 	 *
877 	 * @param direction layout direction
878 	 * @return A component using SBoxLayout
879 	 */
createContainer(boolean direction)880 	public static JComponent createContainer(boolean direction) {
881 		JComponent container = new Spring();
882 		container.setLayout(new SBoxLayout(direction));
883 
884 		return container;
885 	}
886 
887 	/**
888 	 * A convenience method for creating a container using SBoxLayout with
889 	 * padding between the components.
890 	 *
891 	 * @param direction layout direction
892 	 * @param padding padding in pixels between the components
893 	 * @return A component using SBoxLayout
894 	 */
createContainer(boolean direction, int padding)895 	public static JComponent createContainer(boolean direction, int padding) {
896 		JComponent container = new Spring();
897 		container.setLayout(new SBoxLayout(direction, padding));
898 
899 		return container;
900 	}
901 }
902