1 | class LetterLayout implements LayoutManager { |
2 | private String[] lines; |
3 | private Map<String, Component> map = new TreeMap<String, Component>(); |
4 | private RC[] rows; |
5 | private RC[] cols; |
6 | private Cell[][] cells; |
7 | private int spacingX = 10, spacingY = 10; |
8 | private int insetTop, insetBottom, insetLeft, insetRight; |
9 | private int template; |
10 | private boolean formWideLeftSide, formWideRightSide; |
11 | |
12 | private static final int STALACTITE = 1, LEFT_ALIGNED_ROW = 2, CENTERED_ROW = 3, FORM = 4, RIGHT_ALIGNED_ROW = 5; |
13 | private boolean debug; |
14 | |
15 | public void setLeftBorder(int border) { |
16 | insetLeft = border; |
17 | } |
18 | |
19 | public void setRightBorder(int border) { |
20 | insetRight = border; |
21 | } |
22 | |
23 | public static JComponent withBorder(JComponent component, int border) { |
24 | JPanel panel = new JPanel(new LetterLayout("C").setBorder(border)); |
25 | panel.add("C", component); |
26 | return panel; |
27 | } |
28 | |
29 | public static JPanel panel(String... lines) { |
30 | return new JPanel(new LetterLayout(lines)); |
31 | } |
32 | |
33 | public static JPanel stalactitePanel() { |
34 | return new JPanel(stalactite()); |
35 | } |
36 | |
37 | static class DummyComponent extends JComponent { |
38 | } |
39 | |
40 | /** |
41 | * info about one matrix cell |
42 | */ |
43 | static class Cell { |
44 | boolean aux; // part of a larger cell, but not top-left corner |
45 | int minWidth, minHeight; |
46 | Component component; |
47 | int colspan, rowspan; |
48 | double weightX, weightY; |
49 | } |
50 | |
51 | /** |
52 | * info about one matrix row / column |
53 | */ |
54 | static class RC { |
55 | int min; |
56 | double weightSum; |
57 | int start; |
58 | int minEnd; |
59 | } |
60 | |
61 | private LetterLayout(int template) { |
62 | this.template = template; |
63 | } |
64 | |
65 | public LetterLayout(String... lines) { |
66 | this.lines = lines; |
67 | } |
68 | |
69 | public void removeLayoutComponent(Component component) { |
70 | map.values().remove(component); |
71 | } |
72 | |
73 | public void layoutContainer(Container container) { |
74 | prepareLayout(container); |
75 | |
76 | // do layout |
77 | |
78 | if (debug) |
79 | System.out.println("Container size: " + container.getSize()); |
80 | |
81 | Insets insets = getInsets(container); |
82 | for (int r = 0; r < rows.length; r++) { |
83 | for (int i = 0; i < cols.length;) { |
84 | Cell cell = cells[i][r]; |
85 | if (cell.aux) |
86 | ++i; |
87 | else { |
88 | if (cell.component != null) { |
89 | int x1 = cols[i].start; |
90 | int y1 = rows[r].start; |
91 | int x2 = i + cell.colspan < cols.length ? cols[i + cell.colspan].start - spacingX : container.getWidth() - insets.right; |
92 | int y2 = r + cell.rowspan < rows.length ? rows[r + cell.rowspan].start - spacingY : container.getHeight() - insets.bottom; |
93 | |
94 | if (debug) |
95 | System.out.println("Layouting ("+i+", "+r+", " + cell.component.getClass().getName() + "): "+x1+" "+y1+" "+x2+" "+y2); |
96 | |
97 | cell.component.setBounds(x1, y1, x2 - x1, y2 - y1); |
98 | } |
99 | i += cells[i][r].colspan; |
100 | } |
101 | } |
102 | } |
103 | } |
104 | |
105 | private void prepareLayout(Container container) { |
106 | applyTemplate(container); |
107 | |
108 | int numRows = lines.length, numCols = lines[0].length(); |
109 | for (int i = 1; i < numRows; i++) if (lines[i].length() != numCols) |
110 | throw new IllegalArgumentException("Lines have varying length"); |
111 | cells = new Cell[numCols][numRows]; |
112 | rows = new RC[numRows]; |
113 | cols = new RC[numCols]; |
114 | |
115 | for (int r = 0; r < numRows; r++) rows[r] = new RC(); |
116 | for (int i = 0; i < numCols; i++) cols[i] = new RC(); |
117 | for (int r = 0; r < numRows; r++) for (int i = 0; i < numCols; i++) cells[i][r] = new Cell(); |
118 | |
119 | // define cells |
120 | |
121 | for (int r = 0; r < numRows; r++) { |
122 | String line = lines[r]; |
123 | for (int i = 0; i < numCols;) { |
124 | Cell cell = cells[i][r]; |
125 | if (cell.aux) { |
126 | ++i; |
127 | continue; |
128 | } |
129 | char ch = line.charAt(i); |
130 | int iNext = i; |
131 | do ++iNext; while (iNext < numCols && ch == line.charAt(iNext)); |
132 | int rNext = r; |
133 | do ++rNext; while (rNext < numRows && ch == lines[rNext].charAt(i)); |
134 | |
135 | cell.weightX = numCols == 1 || iNext > i + 1 ? 1.0 : 0.0; |
136 | cell.weightY = numRows == 1 || rNext > r + 1 ? 1.0 : 0.0; |
137 | |
138 | Component c = map.get(String.valueOf(ch)); |
139 | cell.component = c; |
140 | if (c != null) { |
141 | cell.minWidth = c.getMinimumSize().width + spacingX; |
142 | cell.minHeight = getMinimumHeight(c) + spacingY; |
143 | } |
144 | cell.colspan = iNext - i; |
145 | cell.rowspan = rNext - r; |
146 | |
147 | if (cell.colspan == 1) |
148 | cols[i].min = Math.max(cols[i].min, cell.minWidth); |
149 | if (cell.rowspan == 1) |
150 | rows[r].min = Math.max(rows[r].min, cell.minHeight); |
151 | |
152 | for (int r2 = r; r2 < rNext; r2++) |
153 | for (int i2 = i; i2 < iNext; i2++) |
154 | if (r2 != r || i2 != i) |
155 | cells[i2][r2].aux = true; |
156 | |
157 | i = iNext; |
158 | } |
159 | } |
160 | |
161 | // determine minStarts, weightSums |
162 | |
163 | while (true) { |
164 | for (int i = 0; i < numCols; i++) { |
165 | int minStart = i == 0 ? 0 : cols[i - 1].minEnd; |
166 | double weightStart = i == 0 ? 0.0 : cols[i - 1].weightSum; |
167 | for (int r = 0; r < numRows; r++) { |
168 | Cell cell = cells[i][r]; |
169 | if (!cell.aux) { |
170 | RC rc = cols[i + cell.colspan - 1]; |
171 | rc.minEnd = Math.max(rc.minEnd, minStart + cell.minWidth); |
172 | rc.weightSum = Math.max(rc.weightSum, weightStart + cell.weightX); |
173 | } |
174 | } |
175 | } |
176 | |
177 | for (int r = 0; r < numRows; r++) { |
178 | int minStart = r == 0 ? 0 : rows[r - 1].minEnd; |
179 | double weightStart = r == 0 ? 0.0 : rows[r - 1].weightSum; |
180 | for (int i = 0; i < numCols; i++) { |
181 | Cell cell = cells[i][r]; |
182 | if (!cell.aux) { |
183 | RC rc = rows[r + cell.rowspan - 1]; |
184 | rc.minEnd = Math.max(rc.minEnd, minStart + cell.minHeight); |
185 | rc.weightSum = Math.max(rc.weightSum, weightStart + cell.weightY); |
186 | } |
187 | } |
188 | } |
189 | |
190 | if (allWeightsZero(cols)) { |
191 | for (int r = 0; r < numRows; r++) |
192 | for (int i = 0; i < numCols; i++) |
193 | cells[i][r].weightX = 1.0; |
194 | continue; |
195 | } |
196 | |
197 | if (allWeightsZero(rows)) { |
198 | for (int r = 0; r < numRows; r++) |
199 | for (int i = 0; i < numCols; i++) |
200 | cells[i][r].weightY = 1.0; |
201 | continue; |
202 | } |
203 | |
204 | break; |
205 | } |
206 | |
207 | // determine row, col starts |
208 | |
209 | Insets insets = getInsets(container); |
210 | determineStarts(cols, insets.left, container.getWidth() - insets.left - insets.right + spacingX, spacingX); |
211 | determineStarts(rows, insets.top, container.getHeight() - insets.top - insets.bottom + spacingY, spacingY); |
212 | } |
213 | |
214 | private boolean allWeightsZero(RC[] rcs) { |
215 | for (int i = 0; i < rcs.length; i++) |
216 | if (rcs[i].weightSum != 0.0) |
217 | return false; |
218 | return true; |
219 | } |
220 | |
221 | private static int getMinimumHeight(Component c) { |
222 | if (c instanceof JTextArea) { |
223 | return (int) ((JTextArea) c).getUI().getRootView((JTextArea) c).getPreferredSpan(javax.swing.text.View.Y_AXIS); |
224 | } |
225 | return c.getMinimumSize().height; |
226 | } |
227 | |
228 | private void applyTemplate(Container container) { |
229 | if (template == STALACTITE) { |
230 | Component[] components = container.getComponents(); |
231 | |
232 | lines = new String[components.length + 2]; |
233 | map.clear(); |
234 | for (int i = 0; i < components.length; i++) { |
235 | String s = String.valueOf(makeIndexChar(i)); |
236 | map.put(s, components[i]); |
237 | lines[i] = s; |
238 | } |
239 | lines[components.length] = lines[components.length + 1] = " "; |
240 | } else if (template == FORM) { |
241 | /* old method of calculating numRows: |
242 | int numRows = 0; |
243 | for (String key : map.keySet()) { |
244 | if (key.length() == 1) |
245 | numRows = Math.max(numRows, Character.toLowerCase(key.charAt(0))-'a'); |
246 | }*/ |
247 | Component[] components = container.getComponents(); |
248 | int numRows = components.length/2; |
249 | |
250 | lines = new String[numRows+2]; |
251 | map.clear(); |
252 | for (int row = 0; row < numRows; row++) { |
253 | String lower = String.valueOf(makeIndexChar(row)); |
254 | String upper = String.valueOf(makeAlternateIndexChar(row)); |
255 | Component rightComponent = components[row * 2 + 1]; |
256 | if (rightComponent instanceof DummyComponent) |
257 | upper = lower; |
258 | lines[row] = (formWideLeftSide ? lower + lower : lower) + (formWideRightSide ? upper + upper : upper); |
259 | map.put(lower, components[row*2]); |
260 | if (!(rightComponent instanceof DummyComponent)) |
261 | map.put(upper, rightComponent); |
262 | } |
263 | lines[numRows] = lines[numRows+1] = (formWideLeftSide ? " " : " ") + (formWideRightSide ? " " : " "); |
264 | } else if (template == LEFT_ALIGNED_ROW) { |
265 | lines = new String[] { makeSingleRow(container) + RIGHT_CHAR + RIGHT_CHAR }; |
266 | } else if (template == CENTERED_ROW) { |
267 | lines = new String[] { "" + LEFT_CHAR + LEFT_CHAR + makeSingleRow(container) + RIGHT_CHAR + RIGHT_CHAR }; |
268 | } else if (template == RIGHT_ALIGNED_ROW) { |
269 | lines = new String[] { "" + LEFT_CHAR + LEFT_CHAR + makeSingleRow(container) }; |
270 | } |
271 | } |
272 | |
273 | private String makeSingleRow(Container container) { |
274 | Component[] components = container.getComponents(); |
275 | StringBuffer buf = new StringBuffer(); |
276 | map.clear(); |
277 | for (int i = 0; i < components.length; i++) { |
278 | String s = String.valueOf(makeAlternateIndexChar(i)); |
279 | map.put(s, components[i]); |
280 | buf.append(s); |
281 | } |
282 | return buf.toString(); |
283 | } |
284 | |
285 | private static void determineStarts(RC[] rcs, int start, int totalSize, int spacing) { |
286 | int minTotal = rcs[rcs.length - 1].minEnd; |
287 | double weightSum = rcs[rcs.length - 1].weightSum; |
288 | //System.out.println("totalSize="+totalSize+",minTotal="+minTotal+",weightSum="+weightSum); |
289 | int spare = (int) ((totalSize - minTotal) / (weightSum == 0.0 ? 1.0 : weightSum)); |
290 | int x = start, minSum = 0; |
291 | double prevWeightSum = 0.0; |
292 | for (int i = 0; i < rcs.length; i++) { |
293 | int width = rcs[i].minEnd - minSum + (int) ((rcs[i].weightSum - prevWeightSum) * spare) - spacing; |
294 | //System.out.println("i="+i+",prevws="+prevWeightSum+",ws="+rcs[i].weightSum+",min="+rcs[i].min+",width="+width); |
295 | rcs[i].start = x; |
296 | x += width + spacing; |
297 | prevWeightSum = rcs[i].weightSum; |
298 | minSum = rcs[i].minEnd; |
299 | } |
300 | } |
301 | |
302 | public void addLayoutComponent(String s, Component component) { |
303 | map.put(s, component); |
304 | } |
305 | |
306 | public Dimension minimumLayoutSize(Container container) { |
307 | prepareLayout(container); |
308 | Insets insets = getInsets(container); |
309 | Dimension result = new Dimension( |
310 | insets.left + cols[cols.length - 1].minEnd + insets.right - spacingX, |
311 | insets.top + rows[rows.length - 1].minEnd + insets.bottom - spacingY); |
312 | return result; |
313 | } |
314 | |
315 | private Insets getInsets(Container container) { |
316 | Insets insets = container.getInsets(); |
317 | return new Insets(insets.top + insetTop, |
318 | insets.left + insetLeft, |
319 | insets.bottom + insetBottom, |
320 | insets.right + insetRight); |
321 | } |
322 | |
323 | public Dimension preferredLayoutSize(Container container) { |
324 | return minimumLayoutSize(container); |
325 | } |
326 | |
327 | public LetterLayout setSpacing(int x, int y) { |
328 | spacingX = x; |
329 | spacingY = y; |
330 | return this; |
331 | } |
332 | |
333 | public LetterLayout setSpacing(int spacing) { |
334 | return setSpacing(spacing, spacing); |
335 | } |
336 | |
337 | public LetterLayout setBorder(int top, int left, int bottom, int right) { |
338 | insetTop = top; |
339 | insetLeft = left; |
340 | insetBottom = bottom; |
341 | insetRight = right; |
342 | return this; |
343 | } |
344 | |
345 | public LetterLayout setBorder(int inset) { |
346 | return setBorder(inset, inset, inset, inset); |
347 | } |
348 | |
349 | public LetterLayout setTopBorder(int inset) { |
350 | insetTop = inset; |
351 | return this; |
352 | } |
353 | |
354 | /** |
355 | * layout components from top to bottom; add components without letters! |
356 | */ |
357 | public static LetterLayout stalactite() { |
358 | return new LetterLayout(STALACTITE); |
359 | } |
360 | |
361 | /** |
362 | * layout components from left to right; add components without letters! |
363 | */ |
364 | public static LetterLayout leftAlignedRow() { |
365 | return new LetterLayout(LEFT_ALIGNED_ROW); |
366 | } |
367 | |
368 | public static LetterLayout leftAlignedRow(int spacing) { |
369 | return leftAlignedRow().setSpacing(spacing); |
370 | } |
371 | |
372 | /** |
373 | * layout components from left to right, center in container; add components without letters! |
374 | */ |
375 | public static LetterLayout centeredRow() { |
376 | return new LetterLayout(CENTERED_ROW); |
377 | } |
378 | |
379 | public static LetterLayout rightAlignedRow() { |
380 | return new LetterLayout(RIGHT_ALIGNED_ROW); |
381 | } |
382 | |
383 | public static JPanel rightAlignedRowPanel(JComponent... components) { |
384 | return makePanel(new LetterLayout(RIGHT_ALIGNED_ROW), components); |
385 | } |
386 | |
387 | private static JPanel makePanel(LetterLayout letterLayout, JComponent[] components) { |
388 | JPanel panel = new JPanel(letterLayout); |
389 | for (JComponent component : components) { |
390 | panel.add(component); |
391 | } |
392 | return panel; |
393 | } |
394 | |
395 | /** |
396 | * layout components from top to bottom; two components per row |
397 | */ |
398 | public static LetterLayout form() { |
399 | LetterLayout letterLayout = new LetterLayout(FORM); |
400 | letterLayout.formWideLeftSide = true; |
401 | letterLayout.formWideRightSide = true; |
402 | return letterLayout; |
403 | } |
404 | |
405 | /** |
406 | * layout components from top to bottom; two components per row |
407 | * left column is small, right column is wide |
408 | */ |
409 | public static LetterLayout formWideRightSide() { |
410 | LetterLayout letterLayout = new LetterLayout(FORM); |
411 | letterLayout.formWideRightSide = true; |
412 | return letterLayout; |
413 | } |
414 | |
415 | public static Component getDummyComponent() { |
416 | return new DummyComponent(); |
417 | } |
418 | |
419 | public static JPanel newPanel(String... lines) { |
420 | return new JPanel(new LetterLayout(lines)); |
421 | } |
422 | |
423 | public boolean isDebug() { |
424 | return debug; |
425 | } |
426 | |
427 | public void setDebug(boolean debug) { |
428 | this.debug = debug; |
429 | } |
430 | |
431 | public static char makeIndexChar(int idx) { |
432 | return (char) ('a' + idx*2); |
433 | } |
434 | |
435 | public static char makeAlternateIndexChar(int idx) { |
436 | return (char) ('b' + idx*2); |
437 | } |
438 | |
439 | public static char LEFT_CHAR = ',', RIGHT_CHAR = '.'; |
440 | |
441 | public static void main(String[] args) { |
442 | System.out.println((int) makeIndexChar(0)); |
443 | System.out.println((int) makeAlternateIndexChar(0)); |
444 | System.out.println((int) makeIndexChar(32000)); |
445 | System.out.println((int) makeAlternateIndexChar(32000)); |
446 | System.out.println((int) LEFT_CHAR); |
447 | System.out.println((int) RIGHT_CHAR); |
448 | } |
449 | } |
450 | |
451 | class CenteredLine extends JPanel { |
452 | public CenteredLine(Component... components) { |
453 | setLayout(LetterLayout.centeredRow()); |
454 | for (Component component : components) |
455 | add(component); |
456 | } |
457 | |
458 | public void add(String text) { |
459 | add(new JLabel(text)); |
460 | } |
461 | } |
Began life as a copy of #613
Snippet is not live.
Travelled to 12 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #2000337 |
Snippet name: | LetterLayout and CenteredLine |
Eternal ID of this version: | #2000337/1 |
Text MD5: | 7d44385396711bd148c22d53322eb0bd |
Author: | stefan |
Category: | javax |
Type: | New Tinybrain snippet |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2015-05-10 17:10:09 |
Source code size: | 14850 bytes / 461 lines |
Pitched / IR pitched: | No / Yes |
Views / Downloads: | 691 / 164 |
Referenced in: | [show references] |