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