Merge branch 'master' of elynx.fr:repo/jvcard
[jvcard.git] / src / com / googlecode / lanterna / gui2 / GridLayout.java
1 /*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19 package com.googlecode.lanterna.gui2;
20
21 import com.googlecode.lanterna.TerminalPosition;
22 import com.googlecode.lanterna.TerminalSize;
23
24 import java.util.*;
25
26 /**
27 * This emulates the behaviour of the GridLayout in SWT (as opposed to the one in AWT/Swing). I originally ported the
28 * SWT class itself but due to licensing concerns (the eclipse license is not compatible with LGPL) I was advised not to
29 * do that. This is a partial implementation and some of the semantics have changed, but in general it works the same
30 * way so the SWT documentation will generally match.
31 * <p>
32 * You use the {@code GridLayout} by specifying a number of columns you want your grid to have and then when you add
33 * components, you assign {@code LayoutData} to these components using the different static methods in this class
34 * ({@code createLayoutData(..)}). You can set components to span both rows and columns, as well as defining how to
35 * distribute the available space.
36 */
37 public class GridLayout implements LayoutManager {
38 /**
39 * The enum is used to specify where in a grid cell a component should be placed, in the case that the preferred
40 * size of the component is smaller than the space in the cell. This class will generally use two alignments, one
41 * for horizontal and one for vertical.
42 */
43 public enum Alignment {
44 /**
45 * Place the component at the start of the cell (horizontally or vertically) and leave whatever space is left
46 * after the preferred size empty.
47 */
48 BEGINNING,
49 /**
50 * Place the component at the middle of the cell (horizontally or vertically) and leave the space before and
51 * after empty.
52 */
53 CENTER,
54 /**
55 * Place the component at the end of the cell (horizontally or vertically) and leave whatever space is left
56 * before the preferred size empty.
57 */
58 END,
59 /**
60 * Force the component to be the same size as the table cell
61 */
62 FILL,
63 ;
64 }
65
66 static class GridLayoutData implements LayoutData {
67 final Alignment horizontalAlignment;
68 final Alignment verticalAlignment;
69 final boolean grabExtraHorizontalSpace;
70 final boolean grabExtraVerticalSpace;
71 final int horizontalSpan;
72 final int verticalSpan;
73
74 private GridLayoutData(
75 Alignment horizontalAlignment,
76 Alignment verticalAlignment,
77 boolean grabExtraHorizontalSpace,
78 boolean grabExtraVerticalSpace,
79 int horizontalSpan,
80 int verticalSpan) {
81
82 if(horizontalSpan < 1 || verticalSpan < 1) {
83 throw new IllegalArgumentException("Horizontal/Vertical span must be 1 or greater");
84 }
85
86 this.horizontalAlignment = horizontalAlignment;
87 this.verticalAlignment = verticalAlignment;
88 this.grabExtraHorizontalSpace = grabExtraHorizontalSpace;
89 this.grabExtraVerticalSpace = grabExtraVerticalSpace;
90 this.horizontalSpan = horizontalSpan;
91 this.verticalSpan = verticalSpan;
92 }
93 }
94
95 private static GridLayoutData DEFAULT = new GridLayoutData(
96 Alignment.BEGINNING,
97 Alignment.BEGINNING,
98 false,
99 false,
100 1,
101 1);
102
103 /**
104 * Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
105 * component in case the cell space is larger than the preferred size of the component
106 * @param horizontalAlignment Horizontal alignment strategy
107 * @param verticalAlignment Vertical alignment strategy
108 * @return The layout data object containing the specified alignments
109 */
110 public static LayoutData createLayoutData(Alignment horizontalAlignment, Alignment verticalAlignment) {
111 return createLayoutData(horizontalAlignment, verticalAlignment, false, false);
112 }
113
114 /**
115 * Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
116 * component in case the cell space is larger than the preferred size of the component. This method also has fields
117 * for indicating that the component would like to take more space if available to the container. For example, if
118 * the container is assigned is assigned an area of 50x15, but all the child components in the grid together only
119 * asks for 40x10, the remaining 10 columns and 5 rows will be empty. If just a single component asks for extra
120 * space horizontally and/or vertically, the grid will expand out to fill the entire area and the text space will be
121 * assigned to the component that asked for it.
122 *
123 * @param horizontalAlignment Horizontal alignment strategy
124 * @param verticalAlignment Vertical alignment strategy
125 * @param grabExtraHorizontalSpace If set to {@code true}, this component will ask to be assigned extra horizontal
126 * space if there is any to assign
127 * @param grabExtraVerticalSpace If set to {@code true}, this component will ask to be assigned extra vertical
128 * space if there is any to assign
129 * @return The layout data object containing the specified alignments and size requirements
130 */
131 public static LayoutData createLayoutData(
132 Alignment horizontalAlignment,
133 Alignment verticalAlignment,
134 boolean grabExtraHorizontalSpace,
135 boolean grabExtraVerticalSpace) {
136
137 return createLayoutData(horizontalAlignment, verticalAlignment, grabExtraHorizontalSpace, grabExtraVerticalSpace, 1, 1);
138 }
139
140 /**
141 * Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
142 * component in case the cell space is larger than the preferred size of the component. This method also has fields
143 * for indicating that the component would like to take more space if available to the container. For example, if
144 * the container is assigned is assigned an area of 50x15, but all the child components in the grid together only
145 * asks for 40x10, the remaining 10 columns and 5 rows will be empty. If just a single component asks for extra
146 * space horizontally and/or vertically, the grid will expand out to fill the entire area and the text space will be
147 * assigned to the component that asked for it. It also puts in data on how many rows and/or columns the component
148 * should span.
149 *
150 * @param horizontalAlignment Horizontal alignment strategy
151 * @param verticalAlignment Vertical alignment strategy
152 * @param grabExtraHorizontalSpace If set to {@code true}, this component will ask to be assigned extra horizontal
153 * space if there is any to assign
154 * @param grabExtraVerticalSpace If set to {@code true}, this component will ask to be assigned extra vertical
155 * space if there is any to assign
156 * @param horizontalSpan How many "cells" this component wants to span horizontally
157 * @param verticalSpan How many "cells" this component wants to span vertically
158 * @return The layout data object containing the specified alignments, size requirements and cell spanning
159 */
160 public static LayoutData createLayoutData(
161 Alignment horizontalAlignment,
162 Alignment verticalAlignment,
163 boolean grabExtraHorizontalSpace,
164 boolean grabExtraVerticalSpace,
165 int horizontalSpan,
166 int verticalSpan) {
167
168 return new GridLayoutData(
169 horizontalAlignment,
170 verticalAlignment,
171 grabExtraHorizontalSpace,
172 grabExtraVerticalSpace,
173 horizontalSpan,
174 verticalSpan);
175 }
176
177 /**
178 * This is a shortcut method that will create a grid layout data object that will expand its cell as much as is can
179 * horizontally and make the component occupy the whole area horizontally and center it vertically
180 * @param horizontalSpan How many cells to span horizontally
181 * @return Layout data object with the specified span and horizontally expanding as much as it can
182 */
183 public static LayoutData createHorizontallyFilledLayoutData(int horizontalSpan) {
184 return createLayoutData(
185 Alignment.FILL,
186 Alignment.CENTER,
187 true,
188 false,
189 horizontalSpan,
190 1);
191 }
192
193 /**
194 * This is a shortcut method that will create a grid layout data object that will expand its cell as much as is can
195 * vertically and make the component occupy the whole area vertically and center it horizontally
196 * @param horizontalSpan How many cells to span vertically
197 * @return Layout data object with the specified span and vertically expanding as much as it can
198 */
199 public static LayoutData createHorizontallyEndAlignedLayoutData(int horizontalSpan) {
200 return createLayoutData(
201 Alignment.END,
202 Alignment.CENTER,
203 true,
204 false,
205 horizontalSpan,
206 1);
207 }
208
209 private final int numberOfColumns;
210 private int horizontalSpacing;
211 private int verticalSpacing;
212 private int topMarginSize;
213 private int bottomMarginSize;
214 private int leftMarginSize;
215 private int rightMarginSize;
216
217 private boolean changed;
218
219 /**
220 * Creates a new {@code GridLayout} with the specified number of columns. Initially, this layout will have a
221 * horizontal spacing of 1 and vertical spacing of 0, with a left and right margin of 1.
222 * @param numberOfColumns Number of columns in this grid
223 */
224 public GridLayout(int numberOfColumns) {
225 this.numberOfColumns = numberOfColumns;
226 this.horizontalSpacing = 1;
227 this.verticalSpacing = 0;
228 this.topMarginSize = 0;
229 this.bottomMarginSize = 0;
230 this.leftMarginSize = 1;
231 this.rightMarginSize = 1;
232 this.changed = true;
233 }
234
235 /**
236 * Returns the horizontal spacing, i.e. the number of empty columns between each cell
237 * @return Horizontal spacing
238 */
239 public int getHorizontalSpacing() {
240 return horizontalSpacing;
241 }
242
243 /**
244 * Sets the horizontal spacing, i.e. the number of empty columns between each cell
245 * @param horizontalSpacing New horizontal spacing
246 * @return Itself
247 */
248 public GridLayout setHorizontalSpacing(int horizontalSpacing) {
249 if(horizontalSpacing < 0) {
250 throw new IllegalArgumentException("Horizontal spacing cannot be less than 0");
251 }
252 this.horizontalSpacing = horizontalSpacing;
253 this.changed = true;
254 return this;
255 }
256
257 /**
258 * Returns the vertical spacing, i.e. the number of empty columns between each row
259 * @return Vertical spacing
260 */
261 public int getVerticalSpacing() {
262 return verticalSpacing;
263 }
264
265 /**
266 * Sets the vertical spacing, i.e. the number of empty columns between each row
267 * @param verticalSpacing New vertical spacing
268 * @return Itself
269 */
270 public GridLayout setVerticalSpacing(int verticalSpacing) {
271 if(verticalSpacing < 0) {
272 throw new IllegalArgumentException("Vertical spacing cannot be less than 0");
273 }
274 this.verticalSpacing = verticalSpacing;
275 this.changed = true;
276 return this;
277 }
278
279 /**
280 * Returns the top margin, i.e. number of empty rows above the first row in the grid
281 * @return Top margin, in number of rows
282 */
283 public int getTopMarginSize() {
284 return topMarginSize;
285 }
286
287 /**
288 * Sets the top margin, i.e. number of empty rows above the first row in the grid
289 * @param topMarginSize Top margin, in number of rows
290 * @return Itself
291 */
292 public GridLayout setTopMarginSize(int topMarginSize) {
293 if(topMarginSize < 0) {
294 throw new IllegalArgumentException("Top margin size cannot be less than 0");
295 }
296 this.topMarginSize = topMarginSize;
297 this.changed = true;
298 return this;
299 }
300
301 /**
302 * Returns the bottom margin, i.e. number of empty rows below the last row in the grid
303 * @return Bottom margin, in number of rows
304 */
305 public int getBottomMarginSize() {
306 return bottomMarginSize;
307 }
308
309 /**
310 * Sets the bottom margin, i.e. number of empty rows below the last row in the grid
311 * @param bottomMarginSize Bottom margin, in number of rows
312 * @return Itself
313 */
314 public GridLayout setBottomMarginSize(int bottomMarginSize) {
315 if(bottomMarginSize < 0) {
316 throw new IllegalArgumentException("Bottom margin size cannot be less than 0");
317 }
318 this.bottomMarginSize = bottomMarginSize;
319 this.changed = true;
320 return this;
321 }
322
323 /**
324 * Returns the left margin, i.e. number of empty columns left of the first column in the grid
325 * @return Left margin, in number of columns
326 */
327 public int getLeftMarginSize() {
328 return leftMarginSize;
329 }
330
331 /**
332 * Sets the left margin, i.e. number of empty columns left of the first column in the grid
333 * @param leftMarginSize Left margin, in number of columns
334 * @return Itself
335 */
336 public GridLayout setLeftMarginSize(int leftMarginSize) {
337 if(leftMarginSize < 0) {
338 throw new IllegalArgumentException("Left margin size cannot be less than 0");
339 }
340 this.leftMarginSize = leftMarginSize;
341 this.changed = true;
342 return this;
343 }
344
345 /**
346 * Returns the right margin, i.e. number of empty columns right of the last column in the grid
347 * @return Right margin, in number of columns
348 */
349 public int getRightMarginSize() {
350 return rightMarginSize;
351 }
352
353 /**
354 * Sets the right margin, i.e. number of empty columns right of the last column in the grid
355 * @param rightMarginSize Right margin, in number of columns
356 * @return Itself
357 */
358 public GridLayout setRightMarginSize(int rightMarginSize) {
359 if(rightMarginSize < 0) {
360 throw new IllegalArgumentException("Right margin size cannot be less than 0");
361 }
362 this.rightMarginSize = rightMarginSize;
363 this.changed = true;
364 return this;
365 }
366
367 @Override
368 public boolean hasChanged() {
369 return this.changed;
370 }
371
372 @Override
373 public TerminalSize getPreferredSize(List<Component> components) {
374 TerminalSize preferredSize = TerminalSize.ZERO;
375 if(components.isEmpty()) {
376 return preferredSize.withRelative(
377 leftMarginSize + rightMarginSize,
378 topMarginSize + bottomMarginSize);
379 }
380
381 Component[][] table = buildTable(components);
382 table = eliminateUnusedRowsAndColumns(table);
383
384 //Figure out each column first, this can be done independently of the row heights
385 int preferredWidth = 0;
386 int preferredHeight = 0;
387 for(int width: getPreferredColumnWidths(table)) {
388 preferredWidth += width;
389 }
390 for(int height: getPreferredRowHeights(table)) {
391 preferredHeight += height;
392 }
393 preferredSize = preferredSize.withRelative(preferredWidth, preferredHeight);
394 preferredSize = preferredSize.withRelativeColumns(leftMarginSize + rightMarginSize + (table[0].length - 1) * horizontalSpacing);
395 preferredSize = preferredSize.withRelativeRows(topMarginSize + bottomMarginSize + (table.length - 1) * verticalSpacing);
396 return preferredSize;
397 }
398
399 @Override
400 public void doLayout(TerminalSize area, List<Component> components) {
401 //Sanity check, if the area is way too small, just return
402 Component[][] table = buildTable(components);
403 table = eliminateUnusedRowsAndColumns(table);
404
405 if(area.equals(TerminalSize.ZERO) ||
406 table.length == 0 ||
407 area.getColumns() <= leftMarginSize + rightMarginSize + ((table[0].length - 1) * horizontalSpacing) ||
408 area.getRows() <= bottomMarginSize + topMarginSize + ((table.length - 1) * verticalSpacing)) {
409 return;
410 }
411
412 //Adjust area to the margins
413 area = area.withRelative(-leftMarginSize - rightMarginSize, -topMarginSize - bottomMarginSize);
414
415 Map<Component, TerminalSize> sizeMap = new IdentityHashMap<Component, TerminalSize>();
416 Map<Component, TerminalPosition> positionMap = new IdentityHashMap<Component, TerminalPosition>();
417
418 //Figure out each column first, this can be done independently of the row heights
419 int[] columnWidths = getPreferredColumnWidths(table);
420
421 //Take notes of which columns we can expand if the usable area is larger than what the components want
422 Set<Integer> expandableColumns = getExpandableColumns(table);
423
424 //Next, start shrinking to make sure it fits the size of the area we are trying to lay out on.
425 //Notice we subtract the horizontalSpacing to take the space between components into account
426 TerminalSize areaWithoutHorizontalSpacing = area.withRelativeColumns(-horizontalSpacing * (table[0].length - 1));
427 int totalWidth = shrinkWidthToFitArea(areaWithoutHorizontalSpacing, columnWidths);
428
429 //Finally, if there is extra space, make the expandable columns larger
430 while(areaWithoutHorizontalSpacing.getColumns() > totalWidth && !expandableColumns.isEmpty()) {
431 totalWidth = grabExtraHorizontalSpace(areaWithoutHorizontalSpacing, columnWidths, expandableColumns, totalWidth);
432 }
433
434 //Now repeat for rows
435 int[] rowHeights = getPreferredRowHeights(table);
436 Set<Integer> expandableRows = getExpandableRows(table);
437 TerminalSize areaWithoutVerticalSpacing = area.withRelativeRows(-verticalSpacing * (table.length - 1));
438 int totalHeight = shrinkHeightToFitArea(areaWithoutVerticalSpacing, rowHeights);
439 while(areaWithoutVerticalSpacing.getRows() > totalHeight && !expandableRows.isEmpty()) {
440 totalHeight = grabExtraVerticalSpace(areaWithoutVerticalSpacing, rowHeights, expandableRows, totalHeight);
441 }
442
443 //Ok, all constraints are in place, we can start placing out components. To simplify, do it horizontally first
444 //and vertically after
445 TerminalPosition tableCellTopLeft = TerminalPosition.TOP_LEFT_CORNER;
446 for(int y = 0; y < table.length; y++) {
447 tableCellTopLeft = tableCellTopLeft.withColumn(0);
448 for(int x = 0; x < table[y].length; x++) {
449 Component component = table[y][x];
450 if(component != null && !positionMap.containsKey(component)) {
451 GridLayoutData layoutData = getLayoutData(component);
452 TerminalSize size = component.getPreferredSize();
453 TerminalPosition position = tableCellTopLeft;
454
455 int availableHorizontalSpace = 0;
456 int availableVerticalSpace = 0;
457 for (int i = 0; i < layoutData.horizontalSpan; i++) {
458 availableHorizontalSpace += columnWidths[x + i] + (i > 0 ? horizontalSpacing : 0);
459 }
460 for (int i = 0; i < layoutData.verticalSpan; i++) {
461 availableVerticalSpace += rowHeights[y + i] + (i > 0 ? verticalSpacing : 0);
462 }
463
464 //Make sure to obey the size restrictions
465 size = size.withColumns(Math.min(size.getColumns(), availableHorizontalSpace));
466 size = size.withRows(Math.min(size.getRows(), availableVerticalSpace));
467
468 switch (layoutData.horizontalAlignment) {
469 case CENTER:
470 position = position.withRelativeColumn((availableHorizontalSpace - size.getColumns()) / 2);
471 break;
472 case END:
473 position = position.withRelativeColumn(availableHorizontalSpace - size.getColumns());
474 break;
475 case FILL:
476 size = size.withColumns(availableHorizontalSpace);
477 break;
478 default:
479 break;
480 }
481 switch (layoutData.verticalAlignment) {
482 case CENTER:
483 position = position.withRelativeRow((availableVerticalSpace - size.getRows()) / 2);
484 break;
485 case END:
486 position = position.withRelativeRow(availableVerticalSpace - size.getRows());
487 break;
488 case FILL:
489 size = size.withRows(availableVerticalSpace);
490 break;
491 default:
492 break;
493 }
494
495 sizeMap.put(component, size);
496 positionMap.put(component, position);
497 }
498 tableCellTopLeft = tableCellTopLeft.withRelativeColumn(columnWidths[x] + horizontalSpacing);
499 }
500 tableCellTopLeft = tableCellTopLeft.withRelativeRow(rowHeights[y] + verticalSpacing);
501 }
502
503 //Apply the margins here
504 for(Component component: components) {
505 component.setPosition(positionMap.get(component).withRelative(leftMarginSize, topMarginSize));
506 component.setSize(sizeMap.get(component));
507 }
508 this.changed = false;
509 }
510
511 private int[] getPreferredColumnWidths(Component[][] table) {
512 //actualNumberOfColumns may be different from this.numberOfColumns since some columns may have been eliminated
513 int actualNumberOfColumns = table[0].length;
514 int columnWidths[] = new int[actualNumberOfColumns];
515
516 //Start by letting all span = 1 columns take what they need
517 for(Component[] row: table) {
518 for(int i = 0; i < actualNumberOfColumns; i++) {
519 Component component = row[i];
520 if(component == null) {
521 continue;
522 }
523 GridLayoutData layoutData = getLayoutData(component);
524 if (layoutData.horizontalSpan == 1) {
525 columnWidths[i] = Math.max(columnWidths[i], component.getPreferredSize().getColumns());
526 }
527 }
528 }
529
530 //Next, do span > 1 and enlarge if necessary
531 for(Component[] row: table) {
532 for(int i = 0; i < actualNumberOfColumns; ) {
533 Component component = row[i];
534 if(component == null) {
535 i++;
536 continue;
537 }
538 GridLayoutData layoutData = getLayoutData(component);
539 if(layoutData.horizontalSpan > 1) {
540 int accumWidth = 0;
541 for(int j = i; j < i + layoutData.horizontalSpan; j++) {
542 accumWidth += columnWidths[j];
543 }
544
545 int preferredWidth = component.getPreferredSize().getColumns();
546 if(preferredWidth > accumWidth) {
547 int columnOffset = 0;
548 do {
549 columnWidths[i + columnOffset++]++;
550 accumWidth++;
551 if(columnOffset == layoutData.horizontalSpan) {
552 columnOffset = 0;
553 }
554 }
555 while(preferredWidth > accumWidth);
556 }
557 }
558 i += layoutData.horizontalSpan;
559 }
560 }
561 return columnWidths;
562 }
563
564 private int[] getPreferredRowHeights(Component[][] table) {
565 int numberOfRows = table.length;
566 int rowHeights[] = new int[numberOfRows];
567
568 //Start by letting all span = 1 rows take what they need
569 int rowIndex = 0;
570 for(Component[] row: table) {
571 for(int i = 0; i < row.length; i++) {
572 Component component = row[i];
573 if(component == null) {
574 continue;
575 }
576 GridLayoutData layoutData = getLayoutData(component);
577 if(layoutData.verticalSpan == 1) {
578 rowHeights[rowIndex] = Math.max(rowHeights[rowIndex], component.getPreferredSize().getRows());
579 }
580 }
581 rowIndex++;
582 }
583
584 //Next, do span > 1 and enlarge if necessary
585 for(int x = 0; x < numberOfColumns; x++) {
586 for(int y = 0; y < numberOfRows && y < table.length; ) {
587 if(x >= table[y].length) {
588 y++;
589 continue;
590 }
591 Component component = table[y][x];
592 if(component == null) {
593 y++;
594 continue;
595 }
596 GridLayoutData layoutData = getLayoutData(component);
597 if(layoutData.verticalSpan > 1) {
598 int accumulatedHeight = 0;
599 for(int i = y; i < y + layoutData.verticalSpan; i++) {
600 accumulatedHeight += rowHeights[i];
601 }
602
603 int preferredHeight = component.getPreferredSize().getRows();
604 if(preferredHeight > accumulatedHeight) {
605 int rowOffset = 0;
606 do {
607 rowHeights[y + rowOffset++]++;
608 accumulatedHeight++;
609 if(rowOffset == layoutData.verticalSpan) {
610 rowOffset = 0;
611 }
612 }
613 while(preferredHeight > accumulatedHeight);
614 }
615 }
616 y += layoutData.verticalSpan;
617 }
618 }
619 return rowHeights;
620 }
621
622 private Set<Integer> getExpandableColumns(Component[][] table) {
623 Set<Integer> expandableColumns = new TreeSet<Integer>();
624 for(Component[] row: table) {
625 for (int i = 0; i < row.length; i++) {
626 if(row[i] == null) {
627 continue;
628 }
629 GridLayoutData layoutData = getLayoutData(row[i]);
630 if(layoutData.grabExtraHorizontalSpace) {
631 expandableColumns.add(i);
632 }
633 }
634 }
635 return expandableColumns;
636 }
637
638 private Set<Integer> getExpandableRows(Component[][] table) {
639 Set<Integer> expandableRows = new TreeSet<Integer>();
640 for(int rowIndex = 0; rowIndex < table.length; rowIndex++) {
641 Component[] row = table[rowIndex];
642 for (int columnIndex = 0; columnIndex < row.length; columnIndex++) {
643 if(row[columnIndex] == null) {
644 continue;
645 }
646 GridLayoutData layoutData = getLayoutData(row[columnIndex]);
647 if(layoutData.grabExtraVerticalSpace) {
648 expandableRows.add(rowIndex);
649 }
650 }
651 }
652 return expandableRows;
653 }
654
655 private int shrinkWidthToFitArea(TerminalSize area, int[] columnWidths) {
656 int totalWidth = 0;
657 for(int width: columnWidths) {
658 totalWidth += width;
659 }
660 if(totalWidth > area.getColumns()) {
661 int columnOffset = 0;
662 do {
663 if(columnWidths[columnOffset] > 0) {
664 columnWidths[columnOffset]--;
665 totalWidth--;
666 }
667 if(++columnOffset == numberOfColumns) {
668 columnOffset = 0;
669 }
670 }
671 while(totalWidth > area.getColumns());
672 }
673 return totalWidth;
674 }
675
676 private int shrinkHeightToFitArea(TerminalSize area, int[] rowHeights) {
677 int totalHeight = 0;
678 for(int height: rowHeights) {
679 totalHeight += height;
680 }
681 if(totalHeight > area.getRows()) {
682 int rowOffset = 0;
683 do {
684 if(rowHeights[rowOffset] > 0) {
685 rowHeights[rowOffset]--;
686 totalHeight--;
687 }
688 if(++rowOffset == rowHeights.length) {
689 rowOffset = 0;
690 }
691 }
692 while(totalHeight > area.getRows());
693 }
694 return totalHeight;
695 }
696
697 private int grabExtraHorizontalSpace(TerminalSize area, int[] columnWidths, Set<Integer> expandableColumns, int totalWidth) {
698 for(int columnIndex: expandableColumns) {
699 columnWidths[columnIndex]++;
700 totalWidth++;
701 if(area.getColumns() == totalWidth) {
702 break;
703 }
704 }
705 return totalWidth;
706 }
707
708 private int grabExtraVerticalSpace(TerminalSize area, int[] rowHeights, Set<Integer> expandableRows, int totalHeight) {
709 for(int rowIndex: expandableRows) {
710 rowHeights[rowIndex]++;
711 totalHeight++;
712 if(area.getColumns() == totalHeight) {
713 break;
714 }
715 }
716 return totalHeight;
717 }
718
719 private Component[][] buildTable(List<Component> components) {
720 List<Component[]> rows = new ArrayList<Component[]>();
721 List<int[]> hspans = new ArrayList<int[]>();
722 List<int[]> vspans = new ArrayList<int[]>();
723
724 int rowCount = 0;
725 int rowsExtent = 1;
726 Queue<Component> toBePlaced = new LinkedList<Component>(components);
727 while(!toBePlaced.isEmpty() || rowCount < rowsExtent) {
728 //Start new row
729 Component[] row = new Component[numberOfColumns];
730 int[] hspan = new int[numberOfColumns];
731 int[] vspan = new int[numberOfColumns];
732
733 for(int i = 0; i < numberOfColumns; i++) {
734 if(i > 0 && hspan[i - 1] > 1) {
735 row[i] = row[i-1];
736 hspan[i] = hspan[i - 1] - 1;
737 vspan[i] = vspan[i - 1];
738 }
739 else if(rowCount > 0 && vspans.get(rowCount - 1)[i] > 1) {
740 row[i] = rows.get(rowCount - 1)[i];
741 hspan[i] = hspans.get(rowCount - 1)[i];
742 vspan[i] = vspans.get(rowCount - 1)[i] - 1;
743 }
744 else if(!toBePlaced.isEmpty()) {
745 Component component = toBePlaced.poll();
746 GridLayoutData gridLayoutData = getLayoutData(component);
747
748 row[i] = component;
749 hspan[i] = gridLayoutData.horizontalSpan;
750 vspan[i] = gridLayoutData.verticalSpan;
751 rowsExtent = Math.max(rowsExtent, rowCount + gridLayoutData.verticalSpan);
752 }
753 else {
754 row[i] = null;
755 hspan[i] = 1;
756 vspan[i] = 1;
757 }
758 }
759
760 rows.add(row);
761 hspans.add(hspan);
762 vspans.add(vspan);
763 rowCount++;
764 }
765 return rows.toArray(new Component[rows.size()][]);
766 }
767
768 private Component[][] eliminateUnusedRowsAndColumns(Component[][] table) {
769 if(table.length == 0) {
770 return table;
771 }
772 //Could make this into a Set, but I doubt there will be any real gain in performance as these are probably going
773 //to be very small.
774 List<Integer> rowsToRemove = new ArrayList<Integer>();
775 List<Integer> columnsToRemove = new ArrayList<Integer>();
776
777 final int tableRows = table.length;
778 final int tableColumns = table[0].length;
779
780 //Scan for unnecessary columns
781 columnLoop:
782 for(int column = tableColumns - 1; column > 0; column--) {
783 for(int row = 0; row < tableRows; row++) {
784 if(table[row][column] != table[row][column - 1]) {
785 continue columnLoop;
786 }
787 }
788 columnsToRemove.add(column);
789 }
790
791 //Scan for unnecessary rows
792 rowLoop:
793 for(int row = tableRows - 1; row > 0; row--) {
794 for(int column = 0; column < tableColumns; column++) {
795 if(table[row][column] != table[row - 1][column]) {
796 continue rowLoop;
797 }
798 }
799 rowsToRemove.add(row);
800 }
801
802 //If there's nothing to remove, just return the same
803 if(rowsToRemove.isEmpty() && columnsToRemove.isEmpty()) {
804 return table;
805 }
806
807 //Build a new table with rows & columns eliminated
808 Component[][] newTable = new Component[tableRows - rowsToRemove.size()][];
809 int insertedRowCounter = 0;
810 for(int row = 0; row < tableRows; row++) {
811 Component[] newColumn = new Component[tableColumns - columnsToRemove.size()];
812 int insertedColumnCounter = 0;
813 for(int column = 0; column < tableColumns; column++) {
814 if(columnsToRemove.contains(column)) {
815 continue;
816 }
817 newColumn[insertedColumnCounter++] = table[row][column];
818 }
819 newTable[insertedRowCounter++] = newColumn;
820 }
821 return newTable;
822 }
823
824 private GridLayoutData getLayoutData(Component component) {
825 LayoutData layoutData = component.getLayoutData();
826 if(layoutData == null || !(layoutData instanceof GridLayoutData)) {
827 return DEFAULT;
828 }
829 else {
830 return (GridLayoutData)layoutData;
831 }
832 }
833 }