New version
[fanfix.git] / libs / WrapLayout.java
1 package be.nikiroo.utils;
2
3 import java.awt.Component;
4 import java.awt.Container;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.Insets;
8
9 import javax.swing.JScrollPane;
10 import javax.swing.SwingUtilities;
11
12 /**
13 * FlowLayout subclass that fully supports wrapping of components.
14 *
15 * @author https://tips4java.wordpress.com/2008/11/06/wrap-layout/
16 */
17 public class WrapLayout extends FlowLayout {
18 private static final long serialVersionUID = 1L;
19
20 /**
21 * Constructs a new <code>WrapLayout</code> with a left alignment and a
22 * default 5-unit horizontal and vertical gap.
23 */
24 public WrapLayout() {
25 super();
26 }
27
28 /**
29 * Constructs a new <code>FlowLayout</code> with the specified alignment and
30 * a default 5-unit horizontal and vertical gap. The value of the alignment
31 * argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>,
32 * or <code>WrapLayout</code>.
33 *
34 * @param align
35 * the alignment value
36 */
37 public WrapLayout(int align) {
38 super(align);
39 }
40
41 /**
42 * Creates a new flow layout manager with the indicated alignment and the
43 * indicated horizontal and vertical gaps.
44 * <p>
45 * The value of the alignment argument must be one of
46 * <code>WrapLayout</code>, <code>WrapLayout</code>, or
47 * <code>WrapLayout</code>.
48 *
49 * @param align
50 * the alignment value
51 * @param hgap
52 * the horizontal gap between components
53 * @param vgap
54 * the vertical gap between components
55 */
56 public WrapLayout(int align, int hgap, int vgap) {
57 super(align, hgap, vgap);
58 }
59
60 /**
61 * Returns the preferred dimensions for this layout given the <i>visible</i>
62 * components in the specified target container.
63 *
64 * @param target
65 * the component which needs to be laid out
66 * @return the preferred dimensions to lay out the subcomponents of the
67 * specified container
68 */
69 @Override
70 public Dimension preferredLayoutSize(Container target) {
71 return layoutSize(target, true);
72 }
73
74 /**
75 * Returns the minimum dimensions needed to layout the <i>visible</i>
76 * components contained in the specified target container.
77 *
78 * @param target
79 * the component which needs to be laid out
80 * @return the minimum dimensions to lay out the subcomponents of the
81 * specified container
82 */
83 @Override
84 public Dimension minimumLayoutSize(Container target) {
85 Dimension minimum = layoutSize(target, false);
86 minimum.width -= (getHgap() + 1);
87 return minimum;
88 }
89
90 /**
91 * Returns the minimum or preferred dimension needed to layout the target
92 * container.
93 *
94 * @param target
95 * target to get layout size for
96 * @param preferred
97 * should preferred size be calculated
98 * @return the dimension to layout the target container
99 */
100 private Dimension layoutSize(Container target, boolean preferred) {
101 synchronized (target.getTreeLock()) {
102 // Each row must fit with the width allocated to the containter.
103 // When the container width = 0, the preferred width of the
104 // container
105 // has not yet been calculated so lets ask for the maximum.
106
107 int targetWidth = target.getSize().width;
108 Container container = target;
109
110 while (container.getSize().width == 0
111 && container.getParent() != null) {
112 container = container.getParent();
113 }
114
115 targetWidth = container.getSize().width;
116
117 if (targetWidth == 0)
118 targetWidth = Integer.MAX_VALUE;
119
120 int hgap = getHgap();
121 int vgap = getVgap();
122 Insets insets = target.getInsets();
123 int horizontalInsetsAndGap = insets.left + insets.right
124 + (hgap * 2);
125 int maxWidth = targetWidth - horizontalInsetsAndGap;
126
127 // Fit components into the allowed width
128
129 Dimension dim = new Dimension(0, 0);
130 int rowWidth = 0;
131 int rowHeight = 0;
132
133 int nmembers = target.getComponentCount();
134
135 for (int i = 0; i < nmembers; i++) {
136 Component m = target.getComponent(i);
137
138 if (m.isVisible()) {
139 Dimension d = preferred ? m.getPreferredSize() : m
140 .getMinimumSize();
141
142 // Can't add the component to current row. Start a new
143 // row.
144
145 if (rowWidth + d.width > maxWidth) {
146 addRow(dim, rowWidth, rowHeight);
147 rowWidth = 0;
148 rowHeight = 0;
149 }
150
151 // Add a horizontal gap for all components after the
152 // first
153
154 if (rowWidth != 0) {
155 rowWidth += hgap;
156 }
157
158 rowWidth += d.width;
159 rowHeight = Math.max(rowHeight, d.height);
160 }
161 }
162
163 addRow(dim, rowWidth, rowHeight);
164
165 dim.width += horizontalInsetsAndGap;
166 dim.height += insets.top + insets.bottom + vgap * 2;
167
168 // When using a scroll pane or the DecoratedLookAndFeel we need
169 // to
170 // make sure the preferred size is less than the size of the
171 // target containter so shrinking the container size works
172 // correctly. Removing the horizontal gap is an easy way to do
173 // this.
174
175 Container scrollPane = SwingUtilities.getAncestorOfClass(
176 JScrollPane.class, target);
177
178 if (scrollPane != null && target.isValid()) {
179 dim.width -= (hgap + 1);
180 }
181
182 return dim;
183 }
184 }
185
186 /*
187 * A new row has been completed. Use the dimensions of this row to update
188 * the preferred size for the container.
189 *
190 * @param dim update the width and height when appropriate
191 *
192 * @param rowWidth the width of the row to add
193 *
194 * @param rowHeight the height of the row to add
195 */
196 private void addRow(Dimension dim, int rowWidth, int rowHeight) {
197 dim.width = Math.max(dim.width, rowWidth);
198
199 if (dim.height > 0) {
200 dim.height += getVgap();
201 }
202
203 dim.height += rowHeight;
204 }
205 }