001: /*
002: * @(#)JideBoxLayout.java
003: *
004: * Copyright 2002 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import com.jidesoft.utils.SecurityUtils;
009:
010: import java.awt.*;
011: import java.util.HashMap;
012: import java.util.Map;
013:
014: /**
015: * JideBoxLayout is very similar to BoxLayout in the way that all components
016: * are arragned either from left to right or from top to bottom. Different \
017: * from BoxLayout, there are three possible contraints when adding component
018: * to this layout - FIX, FLEXIBLE and VARY.
019: * <ul>
020: * <li>FIX: use the preferred size of the compoent and size is fixed
021: * <li>FLEXIBLE: respect the preferred size of the compoennt but size can be changed.
022: * <li>VARY: ignore preferred size. Its size is calculated based how much area left.
023: * </ul>
024: * This is the default layout manager for {@link com.jidesoft.swing.JideSplitPane}.
025: */
026: public class JideBoxLayout implements LayoutManager2 {
027:
028: private final boolean DEBUG = false;
029:
030: /**
031: * True if resetToPreferredSizes has been invoked.
032: */
033: private boolean doReset = true;
034:
035: /**
036: * Axis, 0 for horizontal, or 1 for veritcal.
037: */
038: protected int _axis;
039: protected Container _target;
040: private int _gap = 0;
041:
042: protected int[] _componentSizes;
043:
044: /**
045: * For FIX component, the width (or height if vertical) is and
046: * will always be the preferred width.
047: */
048: public static final String FIX = "fix";
049:
050: /**
051: * FLEXIBLE components try to keep the preferred width. If
052: * there isn't enough space, all FLEXIBLE components will shrink
053: * proportionally.
054: */
055: public static final String FLEXIBLE = "flexible";
056:
057: /**
058: * For VARY component, the width will always be whatever width left.
059: * You can allow add multiple FIX or FLEXIBLE components but only
060: * one VARY component is allowed.
061: */
062: public static final String VARY = "vary";
063:
064: private final HashMap<Component, Object> _constraintMap = new HashMap<Component, Object>();
065:
066: /**
067: * Specifies that components should be laid out left to right.
068: */
069: public static final int X_AXIS = 0;
070:
071: /**
072: * Specifies that components should be laid out top to bottom.
073: */
074: public static final int Y_AXIS = 1;
075:
076: /**
077: * Specifies that components should be laid out in the direction of
078: * a line of text as determined by the target container's
079: * <code>ComponentOrientation</code> property.
080: */
081: public static final int LINE_AXIS = 2;
082:
083: /**
084: * Specifies that components should be laid out in the direction that
085: * lines flow across a page as determined by the target container's
086: * <code>ComponentOrientation</code> property.
087: */
088: public static final int PAGE_AXIS = 3;
089:
090: private boolean _resetWhenInvalidate = true;
091:
092: /**
093: * Creates a layout manager that will lay out components along the
094: * given axis.
095: *
096: * @param target the container that needs to be laid out
097: * @throws AWTError if the value of <code>axis</code> is invalid
098: */
099: public JideBoxLayout(Container target) {
100: this (target, X_AXIS);
101: }
102:
103: /**
104: * @param target the container that needs to be laid out
105: * @param axis the axis to lay out components along. Can be one of:
106: * <code>JideBoxLayout.X_AXIS</code>,
107: * <code>JideBoxLayout.Y_AXIS</code>,
108: * <code>JideBoxLayout.LINE_AXIS</code> or
109: * <code>JideBoxLayout.PAGE_AXIS</code>
110: */
111: public JideBoxLayout(Container target, int axis) {
112: this (target, axis, 0);
113: }
114:
115: /**
116: * @param target the container that needs to be laid out
117: * @param axis the axis to lay out components along. Can be one of:
118: * <code>JideBoxLayout.X_AXIS</code>,
119: * <code>JideBoxLayout.Y_AXIS</code>,
120: * <code>JideBoxLayout.LINE_AXIS</code> or
121: * <code>JideBoxLayout.PAGE_AXIS</code>
122: * @param gap
123: */
124: public JideBoxLayout(Container target, int axis, int gap) {
125: if (axis != X_AXIS && axis != Y_AXIS && axis != LINE_AXIS
126: && axis != PAGE_AXIS) {
127: throw new AWTError("Invalid axis");
128: }
129: _axis = axis;
130: _target = target;
131: _gap = gap;
132: }
133:
134: /**
135: * Lays out the specified container.
136: *
137: * @param container the container to be laid out
138: */
139: public void layoutContainer(Container container) {
140: synchronized (container.getTreeLock()) {
141: if (DEBUG) {
142: System.out.println("===> Start <====");
143: }
144: Dimension containerSize = container.getSize();
145: if (containerSize.height <= 0 || containerSize.width <= 0) {
146: return;
147: }
148:
149: Insets insets = _target.getInsets();
150:
151: if (doReset) {
152: _componentSizes = new int[_target.getComponentCount()];
153: int availableSize = getAvailableSize(containerSize,
154: insets);
155: availableSize -= getGapSize();
156: if (availableSize <= 0) {
157: return;
158: }
159: boolean success = calculateComponentSizes(
160: availableSize, 0, _target.getComponentCount());
161: if (!success) {
162: return;
163: }
164: doReset = false;
165: if (_componentSizes.length == 0) {
166: container.repaint(); // repaint when the last component is removed.
167: }
168: } else {
169: int totalSize = 0;
170: for (int componentSize : _componentSizes) {
171: totalSize += componentSize;
172: }
173: boolean containerResized = totalSize + getGapSize() != getSizeForPrimaryAxis(containerSize);
174: if (containerResized) {
175: int availableSize = getAvailableSize(containerSize,
176: insets);
177: availableSize -= getGapSize();
178: if (availableSize <= 0) {
179: return;
180: }
181: boolean success = calculateComponentSizes(
182: availableSize, 0, _target
183: .getComponentCount());
184: if (!success) {
185: return;
186: }
187: }
188: }
189:
190: int location = getSizeForPrimaryAxis(insets, true);
191: for (int i = 0; i < _target.getComponentCount(); i++) {
192: Component comp = _target.getComponent(i);
193: setComponentToSize(comp, _componentSizes[i], location,
194: insets, containerSize);
195: location += _componentSizes[i];
196: if (_componentSizes[i] != 0)
197: location += _gap;
198: }
199: if (DEBUG) {
200: System.out.println("<==== End ====>");
201: }
202: }
203: }
204:
205: protected boolean calculateComponentSizes(int availableSize,
206: int startIndex, int endIndex) {
207: int availableSizeExcludeFixed = availableSize;
208: int varMinSize = 0;
209: int flexMinSize = 0;
210: int varIndex = -1;
211: int totalFlexSize = 0;
212: int totalFlexSizeMinusMin = 0;
213: int lastFlexIndex = -1;
214: for (int i = startIndex; i < endIndex; i++) {
215: Component comp = _target.getComponent(i);
216: if (!comp.isVisible()) {
217: continue;
218: }
219: Object constraint = _constraintMap.get(comp);
220: int minimumSize = getSizeForPrimaryAxis(comp
221: .getMinimumSize());
222: int preferredSize = getSizeForPrimaryAxis(getPreferredSizeOf(
223: comp, i));
224: if (FIX.equals(constraint)) {
225: availableSizeExcludeFixed -= Math.max(preferredSize,
226: minimumSize);
227: } else if (VARY.equals(constraint)) {
228: varIndex = i;
229: getPreferredSizeOf(comp, i); // there is a bug in jdk1.5 which minimum size returns a large number if preferred size is not call.
230: varMinSize = minimumSize;
231: } else /* if (FLEXIBLE.equals(constraint)) */{
232: if (preferredSize > minimumSize) {
233: totalFlexSizeMinusMin += preferredSize
234: - minimumSize;
235: }
236: totalFlexSize += preferredSize;
237: flexMinSize += minimumSize;
238: lastFlexIndex = i;
239: }
240: }
241:
242: if ("false".equals(SecurityUtils.getProperty(
243: "JideBoxLayout.alwaysLayout", "false"))
244: && availableSizeExcludeFixed - varMinSize < 0) {
245: return false;
246: }
247:
248: boolean hasVary = varIndex != -1;
249: boolean expand = availableSizeExcludeFixed - varMinSize >= totalFlexSize;
250:
251: if (!hasVary || (hasVary && !expand)) {
252: double resizeRatio;
253: if (expand) {
254: resizeRatio = totalFlexSize == 0 ? 0
255: : (double) (availableSizeExcludeFixed - varMinSize)
256: / (double) totalFlexSize;
257: } else {
258: resizeRatio = totalFlexSizeMinusMin == 0 ? 0
259: : (double) (availableSizeExcludeFixed
260: - varMinSize - flexMinSize)
261: / (double) totalFlexSizeMinusMin;
262: }
263:
264: for (int i = startIndex; i < endIndex; i++) {
265: Component comp = _target.getComponent(i);
266: if (!comp.isVisible()) {
267: setComponentSize(i, 0);
268: } else {
269: Object constraint = _constraintMap.get(comp);
270: int minimumSize = getSizeForPrimaryAxis(comp
271: .getMinimumSize());
272: int preferredSize = getSizeForPrimaryAxis(getPreferredSizeOf(
273: comp, i));
274: if (FIX.equals(constraint)) {
275: setComponentSize(i, Math.max(preferredSize,
276: minimumSize));
277: } else if (VARY.equals(constraint)) {
278: setComponentSize(i, varMinSize);
279: } else /* if (FLEXIBLE.equals(constraint)) */{
280: if (expand) {
281: setComponentSize(i,
282: (int) (preferredSize * resizeRatio));
283: } else {
284: setComponentSize(
285: i,
286: minimumSize
287: + (int) ((preferredSize - minimumSize) * resizeRatio));
288: }
289: }
290: }
291: }
292: } else { // if (expand && hasVary) { // VARY component get all extra spaces.
293: for (int i = startIndex; i < endIndex; i++) {
294: Component comp = _target.getComponent(i);
295: if (!comp.isVisible()) {
296: setComponentSize(i, 0);
297: } else {
298: Object constraint = _constraintMap.get(comp);
299: int minimumSize = getSizeForPrimaryAxis(comp
300: .getMinimumSize());
301: int preferredSize = getSizeForPrimaryAxis(getPreferredSizeOf(
302: comp, i));
303: if (FIX.equals(constraint)) {
304: setComponentSize(i, Math.max(preferredSize,
305: minimumSize));
306: } else if (VARY.equals(constraint)) {
307: setComponentSize(i, availableSizeExcludeFixed
308: - totalFlexSize);
309: } else /* if (FLEXIBLE.equals(constraint)) */{
310: setComponentSize(i, Math.max(preferredSize,
311: minimumSize));
312: }
313: }
314: }
315: }
316:
317: int totalActualSize = 0;
318: for (int i = startIndex; i < endIndex; i++) {
319: totalActualSize += _componentSizes[i];
320: }
321:
322: if (totalActualSize != availableSize) {
323: if (varIndex != -1) {
324: setComponentSize(varIndex, _componentSizes[varIndex]
325: + (availableSize - totalActualSize));
326: } else if (lastFlexIndex != -1) {
327: setComponentSize(lastFlexIndex,
328: _componentSizes[lastFlexIndex]
329: + (availableSize - totalActualSize));
330: }
331: }
332:
333: return true;
334: }
335:
336: private void setComponentSize(int index, int size) {
337: if (DEBUG) {
338: System.out.println("setComponentSize index: " + index
339: + " size: " + size);
340: }
341: _componentSizes[index] = size;
342: }
343:
344: /**
345: * If the layout manager uses a per-component string,
346: * adds the component <code>comp</code> to the layout,
347: * associating it
348: * with the string specified by <code>name</code>.
349: *
350: * @param name the string to be associated with the component
351: * @param component the component to be added
352: */
353: public void addLayoutComponent(String name, Component component) {
354: doReset = true;
355: }
356:
357: /**
358: * Returns the minimum size needed to contain the children.
359: * The width is the sum of all the childrens min widths and
360: * the height is the largest of the childrens minimum heights.
361: */
362: public Dimension minimumLayoutSize(Container container) {
363: int minPrimary = 0;
364: int minSecondary = 0;
365: Insets insets = _target.getInsets();
366:
367: synchronized (container.getTreeLock()) {
368: for (int i = 0; i < _target.getComponentCount(); i++) {
369: Component comp = _target.getComponent(i);
370: if (!comp.isVisible()) {
371: continue;
372: }
373: Object constraint = _constraintMap.get(comp);
374: Dimension minimumSize = comp.getMinimumSize();
375: if (FIX.equals(constraint)) {
376: minPrimary += getPreferredSizeOfComponent(comp);
377: } else {
378: minPrimary += getSizeForPrimaryAxis(minimumSize);
379: }
380: int secSize = getSizeForSecondaryAxis(minimumSize);
381: if (secSize > minSecondary)
382: minSecondary = secSize;
383: }
384:
385: if (insets != null) {
386: minPrimary += getSizeForPrimaryAxis(insets, true)
387: + getSizeForPrimaryAxis(insets, false);
388: minSecondary += getSizeForSecondaryAxis(insets, true)
389: + getSizeForSecondaryAxis(insets, false);
390: }
391: }
392:
393: ComponentOrientation o = _target.getComponentOrientation();
394: if (resolveAxis(_axis, o) == X_AXIS) {
395: return new Dimension(minPrimary + getGapSize(),
396: minSecondary);
397: } else {
398: return new Dimension(minSecondary, minPrimary
399: + getGapSize());
400: }
401: }
402:
403: /**
404: * Returns the preferred size needed to contain the children.
405: * The width is the
406: * sum of all the childrens preferred widths and
407: * the height is the largest of the childrens preferred heights.
408: */
409: public Dimension preferredLayoutSize(Container container) {
410: int prePrimary = 0;
411: int preSecondary = 0;
412: Insets insets = _target.getInsets();
413:
414: synchronized (container.getTreeLock()) {
415: for (int i = 0; i < _target.getComponentCount(); i++) {
416: Component comp = _target.getComponent(i);
417: if (!comp.isVisible()) {
418: continue;
419: }
420: Dimension preferredSize = getPreferredSizeOf(comp, i);
421: prePrimary += getSizeForPrimaryAxis(preferredSize);
422: int secSize = getSizeForSecondaryAxis(preferredSize);
423: if (secSize > preSecondary)
424: preSecondary = secSize;
425: }
426:
427: if (insets != null) {
428: prePrimary += getSizeForPrimaryAxis(insets, true)
429: + getSizeForPrimaryAxis(insets, false);
430: preSecondary += getSizeForSecondaryAxis(insets, true)
431: + getSizeForSecondaryAxis(insets, false);
432: }
433: }
434: if (_axis == 0) {
435: return new Dimension(prePrimary + getGapSize(),
436: preSecondary);
437: } else {
438: return new Dimension(preSecondary, prePrimary
439: + getGapSize());
440: }
441: }
442:
443: private int getGapSize() {
444: if (_gap == 0) {
445: return 0;
446: } else {
447: int count = 0;
448: for (int i = 0; i < _target.getComponentCount(); i++) {
449: if (_target.getComponent(i).isVisible()) {
450: count++;
451: }
452: }
453: return Math.max(0, (count - 1)) * _gap;
454: }
455: }
456:
457: /**
458: * Removes the specified component from the layout.
459: *
460: * @param comp the component to be removed
461: */
462: public void removeLayoutComponent(Component comp) {
463: _constraintMap.remove(comp);
464:
465: if (comp instanceof JideSplitPaneDivider)
466: doReset = true;
467: }
468:
469: //
470: // LayoutManager2
471: //
472:
473: /**
474: * Adds the specified component to the layout, using the specified
475: * constraint object.
476: *
477: * @param comp the component to be added
478: * @param constraints where/how the component is added to the layout.
479: */
480: public void addLayoutComponent(Component comp, Object constraints) {
481: if (constraints == null)
482: _constraintMap.put(comp, FLEXIBLE);
483: else
484: _constraintMap.put(comp, constraints);
485: doReset = true;
486: }
487:
488: /**
489: * Returns the alignment along the x axis. This specifies how
490: * the component would like to be aligned relative to other
491: * components. The value should be a number between 0 and 1
492: * where 0 represents alignment along the origin, 1 is aligned
493: * the furthest away from the origin, 0.5 is centered, etc.
494: */
495: public synchronized float getLayoutAlignmentX(Container target) {
496: return 0.0f;
497: }
498:
499: /**
500: * Returns the alignment along the y axis. This specifies how
501: * the component would like to be aligned relative to other
502: * components. The value should be a number between 0 and 1
503: * where 0 represents alignment along the origin, 1 is aligned
504: * the furthest away from the origin, 0.5 is centered, etc.
505: */
506: public synchronized float getLayoutAlignmentY(Container target) {
507: return 0.0f;
508: }
509:
510: /**
511: * Invalidates the layout, indicating that if the layout manager
512: * has cached information it should be discarded.
513: */
514: public synchronized void invalidateLayout(Container c) {
515: if (isResetWhenInvalidate() || componentCountChanged(c)) {
516: doReset = true;
517: }
518: }
519:
520: protected boolean componentCountChanged(Container c) {
521: if (_componentSizes == null) {
522: return true;
523: }
524: int oldLength = 0;
525: for (int _componentSize : _componentSizes) {
526: if (_componentSize > 0) {
527: oldLength++;
528: }
529: }
530: int newLength = 0;
531: for (int i = 0; i < c.getComponentCount(); i++) {
532: if (c.getComponent(i).isVisible()) {
533: newLength++;
534: }
535: }
536: return newLength != oldLength;
537: }
538:
539: /**
540: * Returns the maximum layout size, which is Integer.MAX_VALUE
541: * in both directions.
542: */
543: public Dimension maximumLayoutSize(Container target) {
544: return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
545: }
546:
547: /**
548: * Returns the width of the passed in Components preferred size.
549: */
550: protected int getPreferredSizeOfComponent(Component c) {
551: return getSizeForPrimaryAxis(c.getPreferredSize());
552: }
553:
554: /**
555: * Returns the width of the passed in Components minimum size.
556: */
557: int getMinimumSizeOfComponent(Component c) {
558: return getSizeForPrimaryAxis(c.getMinimumSize());
559: }
560:
561: /**
562: * Returns the width of the passed in component.
563: */
564: protected int getSizeOfComponent(Component c) {
565: return getSizeForPrimaryAxis(c.getSize());
566: }
567:
568: /**
569: * Returns the available width based on the container size and
570: * Insets.
571: */
572: protected int getAvailableSize(Dimension containerSize,
573: Insets insets) {
574: if (insets == null)
575: return getSizeForPrimaryAxis(containerSize);
576: return (getSizeForPrimaryAxis(containerSize) - (getSizeForPrimaryAxis(
577: insets, true) + getSizeForPrimaryAxis(insets, false)));
578: }
579:
580: /**
581: * Returns the left inset, unless the Insets are null in which case
582: * 0 is returned.
583: */
584: protected int getInitialLocation(Insets insets) {
585: if (insets != null)
586: return getSizeForPrimaryAxis(insets, true);
587: return 0;
588: }
589:
590: /**
591: * Sets the width of the component c to be size, placing its
592: * x location at location, y to the insets.top and height
593: * to the containersize.height less the top and bottom insets.
594: */
595: protected void setComponentToSize(Component c, int size,
596: int location, Insets insets, Dimension containerSize) {
597: if (insets != null) {
598: ComponentOrientation o = _target.getComponentOrientation();
599: if (resolveAxis(_axis, o) == X_AXIS) {
600: c.setBounds(Math.max(location, 0), Math.max(insets.top,
601: 0), Math.max(size, 0), Math.max(
602: containerSize.height
603: - (insets.top + insets.bottom), 0));
604: } else {
605: c.setBounds(Math.max(insets.left, 0), Math.max(
606: location, 0), Math.max(containerSize.width
607: - (insets.left + insets.right), 0), Math.max(
608: size, 0));
609: }
610: } else {
611: ComponentOrientation o = _target.getComponentOrientation();
612: if (resolveAxis(_axis, o) == X_AXIS) {
613: c.setBounds(Math.max(location, 0), 0,
614: Math.max(size, 0), Math.max(
615: containerSize.height, 0));
616: } else {
617: c.setBounds(0, Math.max(location, 0), Math.max(
618: containerSize.width, 0), Math.max(size, 0));
619: }
620: }
621: }
622:
623: /**
624: * If the axis == 0, the width is returned, otherwise the height.
625: */
626: int getSizeForPrimaryAxis(Dimension size) {
627: ComponentOrientation o = _target.getComponentOrientation();
628: if (resolveAxis(_axis, o) == X_AXIS) {
629: return size.width;
630: } else {
631: return size.height;
632: }
633: }
634:
635: /**
636: * If the axis == X_AXIS, the width is returned, otherwise the height.
637: */
638: int getSizeForSecondaryAxis(Dimension size) {
639: ComponentOrientation o = _target.getComponentOrientation();
640: if (resolveAxis(_axis, o) == X_AXIS) {
641: return size.height;
642: } else {
643: return size.width;
644: }
645: }
646:
647: /**
648: * Returns a particular value of the inset identified by the
649: * axis and <code>isTop</code><p>.
650: * axis isTop
651: * 0 true - left
652: * 0 false - right
653: * 1 true - top
654: * 1 false - bottom
655: */
656: int getSizeForPrimaryAxis(Insets insets, boolean isTop) {
657: ComponentOrientation o = _target.getComponentOrientation();
658: if (resolveAxis(_axis, o) == X_AXIS) {
659: if (isTop) {
660: return insets.left;
661: } else {
662: return insets.right;
663: }
664: } else {
665: if (isTop) {
666: return insets.top;
667: } else {
668: return insets.bottom;
669: }
670: }
671: }
672:
673: /**
674: * Returns a particular value of the inset identified by the
675: * axis and <code>isTop</code><p>.
676: * axis isTop
677: * 0 true - left
678: * 0 false - right
679: * 1 true - top
680: * 1 false - bottom
681: */
682: int getSizeForSecondaryAxis(Insets insets, boolean isTop) {
683: ComponentOrientation o = _target.getComponentOrientation();
684: if (resolveAxis(_axis, o) == X_AXIS) {
685: if (isTop) {
686: return insets.top;
687: } else {
688: return insets.bottom;
689: }
690: } else {
691: if (isTop) {
692: return insets.left;
693: } else {
694: return insets.right;
695: }
696: }
697: }
698:
699: /**
700: * Gets the map of constraints.
701: *
702: * @return the map of constraints
703: */
704: public Map<Component, Object> getConstraintMap() {
705: return _constraintMap;
706: }
707:
708: /**
709: * Given one of the 4 axis values, resolve it to an absolute axis.
710: * The relative axis values, PAGE_AXIS and LINE_AXIS are converted
711: * to their absolute couterpart given the target's ComponentOrientation
712: * value. The absolute axes, X_AXIS and Y_AXIS are returned unmodified.
713: *
714: * @param axis the axis to resolve
715: * @param o the ComponentOrientation to resolve against
716: * @return the resolved axis
717: */
718: protected static int resolveAxis(int axis, ComponentOrientation o) {
719: int absoluteAxis;
720: if (axis == LINE_AXIS) {
721: absoluteAxis = o.isHorizontal() ? X_AXIS : Y_AXIS;
722: } else if (axis == PAGE_AXIS) {
723: absoluteAxis = o.isHorizontal() ? Y_AXIS : X_AXIS;
724: } else {
725: absoluteAxis = axis;
726: }
727: return absoluteAxis;
728: }
729:
730: /**
731: * Gets the gap between each component.
732: *
733: * @return the gap between each component.
734: */
735: public int getGap() {
736: return _gap;
737: }
738:
739: /**
740: * Sets the gap between each component. Make sure you cal doLayout() after you change the gap.
741: *
742: * @param gap
743: */
744: public void setGap(int gap) {
745: _gap = gap;
746: }
747:
748: protected Dimension getPreferredSizeOf(Component comp, int atIndex) {
749: return comp.getPreferredSize();
750: }
751:
752: /**
753: * Checks of the layout should be reset when {@link #invalidateLayout(java.awt.Container)} is called.
754: *
755: * @return true or false.
756: */
757: public boolean isResetWhenInvalidate() {
758: return _resetWhenInvalidate;
759: }
760:
761: /**
762: * Sets the flag if the layout should be reset when {@link #invalidateLayout(java.awt.Container)} is called.
763: *
764: * @param resetWhenInvalidate
765: */
766: public void setResetWhenInvalidate(boolean resetWhenInvalidate) {
767: _resetWhenInvalidate = resetWhenInvalidate;
768: }
769: }
|