Commit | Line | Data |
---|---|---|
8caeb8bd NR |
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 | } |