|
1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.util.swing.layout; |
|
27 |
|
28 import java.awt.*; |
|
29 import java.util.*; |
|
30 import java.util.List; |
|
31 import com.oracle.solaris.vp.util.swing.layout.AbstractLayout.SizedSet; |
|
32 import com.oracle.solaris.vp.util.swing.layout.Anchor.AnchorType; |
|
33 |
|
34 public abstract class AbstractTableLayout<C extends HasAnchors> |
|
35 extends ConstrainedLayout<C> implements HasAnchors { |
|
36 |
|
37 // |
|
38 // Inner classes |
|
39 // |
|
40 |
|
41 protected static class CellInfo { |
|
42 // |
|
43 // Instance data |
|
44 // |
|
45 |
|
46 private Component component; |
|
47 private Dimension size; |
|
48 private boolean preferred; |
|
49 private List<RowOrCol> parents = new ArrayList<RowOrCol>(); |
|
50 |
|
51 // |
|
52 // Constructors |
|
53 // |
|
54 |
|
55 public CellInfo(Component component, boolean preferred) { |
|
56 this.component = component; |
|
57 this.preferred = preferred; |
|
58 } |
|
59 |
|
60 // |
|
61 // CellInfo methods |
|
62 // |
|
63 |
|
64 public void add(RowOrCol parent) { |
|
65 parents.add(parent); |
|
66 } |
|
67 |
|
68 public Component getComponent() { |
|
69 return component; |
|
70 } |
|
71 |
|
72 public Dimension getSize() { |
|
73 // Deferred fetch |
|
74 if (size == null) { |
|
75 size = preferred ? |
|
76 component.getPreferredSize() : component.getMinimumSize(); |
|
77 } |
|
78 return size; |
|
79 } |
|
80 |
|
81 public void remove() { |
|
82 for (int i = parents.size() - 1; i >= 0; i--) { |
|
83 parents.get(i).childRemoved(this); |
|
84 parents.remove(i); |
|
85 } |
|
86 } |
|
87 } |
|
88 |
|
89 protected static class RowOrCol { |
|
90 // |
|
91 // Instance data |
|
92 // |
|
93 |
|
94 private AbstractTableConstraint constraint; |
|
95 private boolean isRow; |
|
96 private int size = -1; |
|
97 private float weight = -1f; |
|
98 private List<CellInfo> children = new ArrayList<CellInfo>(); |
|
99 private List<CellInfo> roChildren = |
|
100 Collections.unmodifiableList(children); |
|
101 |
|
102 // |
|
103 // Constructors |
|
104 // |
|
105 |
|
106 public RowOrCol(AbstractTableConstraint constraint, boolean isRow) { |
|
107 this.constraint = constraint; |
|
108 this.isRow = isRow; |
|
109 } |
|
110 |
|
111 // |
|
112 // Instance data |
|
113 // |
|
114 |
|
115 public void add(CellInfo child) { |
|
116 children.add(child); |
|
117 child.add(this); |
|
118 } |
|
119 |
|
120 /** |
|
121 * Called by the child when it is removed from layout. |
|
122 */ |
|
123 public boolean childRemoved(CellInfo child) { |
|
124 boolean ret = children.remove(child); |
|
125 if (ret) { |
|
126 size = -1; |
|
127 } |
|
128 return ret; |
|
129 } |
|
130 |
|
131 public AbstractTableConstraint getConstraint() { |
|
132 return constraint; |
|
133 } |
|
134 |
|
135 public List<CellInfo> getChildren() { |
|
136 return roChildren; |
|
137 } |
|
138 |
|
139 /** |
|
140 * Get the maximum size (row height or column width) of every child |
|
141 * {@code Component}. |
|
142 */ |
|
143 public int getSize() { |
|
144 // Deferred fetch |
|
145 if (size == -1) { |
|
146 for (CellInfo child : children) { |
|
147 Dimension d = child.getSize(); |
|
148 int s = isRow ? d.height : d.width; |
|
149 if (s > size) { |
|
150 size = s; |
|
151 } |
|
152 } |
|
153 } |
|
154 return size; |
|
155 } |
|
156 |
|
157 public float getWeight() { |
|
158 // Deferred fetch |
|
159 if (weight == -1) { |
|
160 weight = constraint.getWeight(); |
|
161 } |
|
162 return weight; |
|
163 } |
|
164 |
|
165 /** |
|
166 * Remove all children in this row/column from layout (in any |
|
167 * row/column). |
|
168 */ |
|
169 public void remove() { |
|
170 for (int i = children.size() - 1; i >= 0; i--) { |
|
171 remove(children.get(i)); |
|
172 } |
|
173 } |
|
174 |
|
175 /** |
|
176 * Remove the given child in this row/column from layout (in any |
|
177 * row/column). |
|
178 */ |
|
179 public boolean remove(CellInfo child) { |
|
180 if (children.contains(child)) { |
|
181 child.remove(); |
|
182 return true; |
|
183 } |
|
184 return false; |
|
185 } |
|
186 |
|
187 public void setSize(int size) { |
|
188 this.size = size; |
|
189 } |
|
190 |
|
191 public void setWeight(float weight) { |
|
192 this.weight = weight; |
|
193 } |
|
194 } |
|
195 |
|
196 protected static class RowOrColSet implements SizedSet { |
|
197 // |
|
198 // Instance data |
|
199 // |
|
200 |
|
201 private int tSizes = -1; |
|
202 private int tGaps = -1; |
|
203 private List<RowOrCol> elements = new ArrayList<RowOrCol>(); |
|
204 private List<RowOrCol> roElements = |
|
205 Collections.unmodifiableList(elements); |
|
206 |
|
207 // |
|
208 // SizedSet methods |
|
209 // |
|
210 |
|
211 @Override |
|
212 public int getCount() { |
|
213 return elements.size(); |
|
214 } |
|
215 |
|
216 @Override |
|
217 public int getSize(int i) { |
|
218 return elements.get(i).getSize(); |
|
219 } |
|
220 |
|
221 @Override |
|
222 public float getWeight(int i) { |
|
223 return elements.get(i).getWeight(); |
|
224 } |
|
225 |
|
226 @Override |
|
227 public void setSize(int i, int size) { |
|
228 elements.get(i).setSize(size); |
|
229 } |
|
230 |
|
231 @Override |
|
232 public void setWeight(int i, float weight) { |
|
233 elements.get(i).setWeight(weight); |
|
234 } |
|
235 |
|
236 // |
|
237 // RowOrColSet methods |
|
238 // |
|
239 |
|
240 public void add(RowOrCol element) { |
|
241 elements.add(element); |
|
242 } |
|
243 |
|
244 public void add(int i, RowOrCol element) { |
|
245 elements.add(i, element); |
|
246 } |
|
247 |
|
248 public List<RowOrCol> getElements() { |
|
249 return roElements; |
|
250 } |
|
251 |
|
252 public int getGap(int i) { |
|
253 AbstractTableConstraint constraint = |
|
254 elements.get(i).getConstraint(); |
|
255 |
|
256 return (i == 0 && constraint.getIgnoreFirstGap()) ? |
|
257 0 : constraint.getGap(); |
|
258 } |
|
259 |
|
260 public int getGapTotal() { |
|
261 // Deferred fetch |
|
262 if (tGaps == -1) { |
|
263 tGaps = 0; |
|
264 for (int i = 0, n = getCount(); i < n; i++) { |
|
265 tGaps += getGap(i); |
|
266 } |
|
267 } |
|
268 return tGaps; |
|
269 } |
|
270 |
|
271 public int getSizeTotal() { |
|
272 // Deferred fetch |
|
273 if (tSizes == -1) { |
|
274 tSizes = 0; |
|
275 for (RowOrCol element : elements) { |
|
276 tSizes += element.getSize(); |
|
277 } |
|
278 } |
|
279 return tSizes; |
|
280 } |
|
281 |
|
282 public void remove(int i) { |
|
283 RowOrCol element = elements.remove(i); |
|
284 if (element != null) { |
|
285 element.remove(); |
|
286 tSizes = -1; |
|
287 tGaps = -1; |
|
288 } |
|
289 } |
|
290 } |
|
291 |
|
292 protected static class TableInfo { |
|
293 // |
|
294 // Instance data |
|
295 // |
|
296 |
|
297 private RowOrColSet cols; |
|
298 private RowOrColSet rows; |
|
299 |
|
300 // |
|
301 // Constructors |
|
302 // |
|
303 |
|
304 public TableInfo(RowOrColSet cols, RowOrColSet rows) { |
|
305 this.cols = cols; |
|
306 this.rows = rows; |
|
307 |
|
308 // Remove rows/columns from layout if a) directed by constraints b) |
|
309 // they contain only invisible Components |
|
310 for (RowOrColSet set : new RowOrColSet[] {rows, cols}) { |
|
311 List<RowOrCol> elements = set.getElements(); |
|
312 OUTER: for (int i = elements.size() - 1; i >= 0; i--) { |
|
313 RowOrCol element = elements.get(i); |
|
314 if (!element.getConstraint().getLayoutIfInvisible()) { |
|
315 for (CellInfo comp : element.getChildren()) { |
|
316 if (comp.getComponent().isVisible()) { |
|
317 continue OUTER; |
|
318 } |
|
319 } |
|
320 |
|
321 // Remove this row/column from layout |
|
322 set.remove(i); |
|
323 } |
|
324 } |
|
325 } |
|
326 } |
|
327 |
|
328 // |
|
329 // TableInfo methods |
|
330 // |
|
331 |
|
332 public RowOrColSet getColumns() { |
|
333 return cols; |
|
334 } |
|
335 |
|
336 public RowOrColSet getRows() { |
|
337 return rows; |
|
338 } |
|
339 } |
|
340 |
|
341 // |
|
342 // Instance data |
|
343 // |
|
344 |
|
345 public HorizontalAnchor hAnchor; |
|
346 public VerticalAnchor vAnchor; |
|
347 |
|
348 // |
|
349 // Constructors |
|
350 // |
|
351 |
|
352 public AbstractTableLayout(HorizontalAnchor hAnchor, |
|
353 VerticalAnchor vAnchor, C defaultElementConstraint) { |
|
354 |
|
355 super(defaultElementConstraint); |
|
356 this.hAnchor = hAnchor; |
|
357 this.vAnchor = vAnchor; |
|
358 } |
|
359 |
|
360 // |
|
361 // HasAnchors methods |
|
362 // |
|
363 |
|
364 @Override |
|
365 public HorizontalAnchor getHorizontalAnchor() { |
|
366 return hAnchor; |
|
367 } |
|
368 |
|
369 @Override |
|
370 public VerticalAnchor getVerticalAnchor() { |
|
371 return vAnchor; |
|
372 } |
|
373 |
|
374 // |
|
375 // LayoutManager methods |
|
376 // |
|
377 |
|
378 /** |
|
379 * Lay out Components in the given Container. |
|
380 */ |
|
381 @Override |
|
382 public void layoutContainer(Container container) { |
|
383 TableInfo info = getTableInfo(container, true); |
|
384 |
|
385 RowOrColSet rows = info.getRows(); |
|
386 int nRows = rows.getCount(); |
|
387 if (nRows == 0) { |
|
388 return; |
|
389 } |
|
390 |
|
391 RowOrColSet cols = info.getColumns(); |
|
392 int nCols = cols.getCount(); |
|
393 |
|
394 Insets insets = container.getInsets(); |
|
395 Dimension aSize = container.getSize(); |
|
396 |
|
397 // The amount of space we have to work with |
|
398 int cWidth = aSize.width - insets.left - insets.right; |
|
399 int cHeight = aSize.height - insets.top - insets.bottom; |
|
400 |
|
401 // Where to start drawing the first Component |
|
402 int top = insets.top; |
|
403 int tableLeft = insets.left; |
|
404 |
|
405 // First set heights of rows |
|
406 top += fitToSize(cHeight, rows, getVerticalAnchor()); |
|
407 |
|
408 // Then set widths of columns |
|
409 tableLeft += fitToSize(cWidth, cols, getHorizontalAnchor()); |
|
410 |
|
411 // Lay out components |
|
412 for (int r = 0; r < nRows; r++) { |
|
413 |
|
414 RowOrCol row = rows.getElements().get(r); |
|
415 int rSize = row.getSize(); |
|
416 top += rows.getGap(r); |
|
417 |
|
418 List<CellInfo> children = row.getChildren(); |
|
419 int left = tableLeft; |
|
420 for (int c = 0; c < nCols; c++) { |
|
421 |
|
422 RowOrCol col = cols.getElements().get(c); |
|
423 int cSize = col.getSize(); |
|
424 left += cols.getGap(c); |
|
425 |
|
426 if (c < children.size()) { |
|
427 CellInfo cInfo = children.get(c); |
|
428 Component comp = cInfo.getComponent(); |
|
429 Dimension pSize = cInfo.getSize(); |
|
430 |
|
431 HasAnchors hasAnchors = compToConst.get(comp); |
|
432 |
|
433 int[] yAndHeight = getOffsetAndSize(rSize, |
|
434 pSize.height, hasAnchors.getVerticalAnchor()); |
|
435 |
|
436 int[] xAndWidth = getOffsetAndSize(cSize, |
|
437 pSize.width, hasAnchors.getHorizontalAnchor()); |
|
438 |
|
439 comp.setBounds(left + xAndWidth[0], top + yAndHeight[0], |
|
440 xAndWidth[1], yAndHeight[1]); |
|
441 } |
|
442 |
|
443 left += cSize; |
|
444 } |
|
445 |
|
446 top += rSize; |
|
447 } |
|
448 } |
|
449 |
|
450 // |
|
451 // AbstractLayout methods |
|
452 // |
|
453 |
|
454 @Override |
|
455 protected Dimension getLayoutSize(Container container, boolean preferred) { |
|
456 TableInfo info = getTableInfo(container, preferred); |
|
457 |
|
458 RowOrColSet cols = info.getColumns(); |
|
459 RowOrColSet rows = info.getRows(); |
|
460 |
|
461 int width = cols.getSizeTotal() + cols.getGapTotal(); |
|
462 int height = rows.getSizeTotal() + rows.getGapTotal(); |
|
463 |
|
464 Insets insets = container.getInsets(); |
|
465 width += insets.left + insets.right; |
|
466 height += insets.top + insets.bottom; |
|
467 |
|
468 return new Dimension(width, height); |
|
469 } |
|
470 |
|
471 protected TableInfo getTableInfo(Container container, boolean preferred) { |
|
472 Component[] comps = getLayoutComponents(container.getComponents()); |
|
473 |
|
474 int nCols = getColumnCount(container); |
|
475 int nRows = 0; |
|
476 if (nCols != 0) { |
|
477 nRows = comps.length / nCols; |
|
478 if (comps.length % nCols > 0) { |
|
479 nRows++; |
|
480 } |
|
481 } |
|
482 |
|
483 // Build cells |
|
484 CellInfo[] cInfo = new CellInfo[comps.length]; |
|
485 for (int i = 0; i < comps.length; i++) { |
|
486 cInfo[i] = new CellInfo(comps[i], preferred); |
|
487 } |
|
488 |
|
489 // Build columns |
|
490 RowOrColSet cols = new RowOrColSet(); |
|
491 for (int c = 0; c < nCols; c++) { |
|
492 AbstractTableConstraint constraint = |
|
493 getColumnConstraint(container, c); |
|
494 |
|
495 RowOrCol col = new RowOrCol(constraint, false); |
|
496 |
|
497 for (int r = 0; r < nRows; r++) { |
|
498 int index = r * nCols + c; |
|
499 if (index < cInfo.length) { |
|
500 col.add(cInfo[index]); |
|
501 } |
|
502 } |
|
503 |
|
504 cols.add(col); |
|
505 } |
|
506 |
|
507 // Build rows |
|
508 RowOrColSet rows = new RowOrColSet(); |
|
509 for (int r = 0; r < nRows; r++) { |
|
510 AbstractTableConstraint constraint = |
|
511 getRowConstraint(container, r); |
|
512 |
|
513 RowOrCol row = new RowOrCol(constraint, true); |
|
514 |
|
515 for (int c = 0; c < nCols; c++) { |
|
516 int index = r * nCols + c; |
|
517 if (index < cInfo.length) { |
|
518 row.add(cInfo[index]); |
|
519 } |
|
520 } |
|
521 |
|
522 rows.add(row); |
|
523 } |
|
524 |
|
525 return new TableInfo(cols, rows); |
|
526 } |
|
527 |
|
528 // |
|
529 // AbstractTableLayout methods |
|
530 // |
|
531 |
|
532 protected abstract AbstractTableConstraint getColumnConstraint( |
|
533 Container container, int col); |
|
534 |
|
535 protected abstract AbstractTableConstraint getRowConstraint( |
|
536 Container container, int row); |
|
537 |
|
538 /** |
|
539 * Gets the number of columns in this {@code AbstractTableLayout}. |
|
540 * |
|
541 * @param container |
|
542 * the {@code Container} being layed out |
|
543 */ |
|
544 protected abstract int getColumnCount(Container container); |
|
545 |
|
546 public void setHorizontalAnchor(HorizontalAnchor hAnchor) { |
|
547 this.hAnchor = hAnchor; |
|
548 } |
|
549 |
|
550 public void setVerticalAnchor(VerticalAnchor vAnchor) { |
|
551 this.vAnchor = vAnchor; |
|
552 } |
|
553 |
|
554 // |
|
555 // Private methods |
|
556 // |
|
557 |
|
558 private int fitToSize(int aSize, RowOrColSet set, Anchor anchor) { |
|
559 int top = 0; |
|
560 int tSizes = set.getSizeTotal(); |
|
561 int tGaps = set.getGapTotal(); |
|
562 |
|
563 // Excess/insufficient space |
|
564 int extraSize = aSize - tSizes - tGaps; |
|
565 |
|
566 if (extraSize != 0) { |
|
567 |
|
568 if (extraSize < 0 || |
|
569 anchor.getAnchorType() == Anchor.AnchorType.FILL) { |
|
570 |
|
571 distributeSpace(extraSize, set); |
|
572 } else { |
|
573 |
|
574 switch (anchor.getAnchorType()) { |
|
575 case RIGHT_BOTTOM: |
|
576 top += (aSize - tSizes - tGaps); |
|
577 break; |
|
578 |
|
579 case CENTER: |
|
580 top += (aSize - tSizes - tGaps) / 2; |
|
581 break; |
|
582 } |
|
583 } |
|
584 } |
|
585 |
|
586 return top; |
|
587 } |
|
588 |
|
589 /** |
|
590 * Returns a two-element array consisting of the offset and size of a |
|
591 * resized {@code Component}. |
|
592 * |
|
593 * @param aSize |
|
594 * the available size in which to fit the {@code Component} |
|
595 * |
|
596 * @param pSize |
|
597 * the preferred size in which to fit the {@code Component} |
|
598 * |
|
599 * @param anchor |
|
600 * the Anchor of the {@code Component} |
|
601 */ |
|
602 private int[] getOffsetAndSize(int aSize, int pSize, Anchor anchor) { |
|
603 // Excess/insufficient space |
|
604 int extra = aSize - pSize; |
|
605 |
|
606 int offset = 0; |
|
607 int size = pSize; |
|
608 Anchor.AnchorType type = anchor.getAnchorType(); |
|
609 |
|
610 if (extra != 0) { |
|
611 |
|
612 if (extra < 0 || type == Anchor.AnchorType.FILL) { |
|
613 size = aSize; |
|
614 } else { |
|
615 |
|
616 switch (type) { |
|
617 case RIGHT_BOTTOM: |
|
618 offset = (aSize - pSize); |
|
619 break; |
|
620 |
|
621 case CENTER: |
|
622 offset = (aSize - pSize) / 2; |
|
623 break; |
|
624 } |
|
625 } |
|
626 } |
|
627 |
|
628 return new int[] {offset, size}; |
|
629 } |
|
630 } |