Update lanterna, fix bugs, implement save...
[jvcard.git] / src / com / googlecode / lanterna / gui2 / BorderLayout.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 * BorderLayout imitates the BorderLayout class from AWT, allowing you to add a center component with optional
28 * components around it in top, bottom, left and right locations. The edge components will be sized at their preferred
29 * size and the center component will take up whatever remains.
30 * @author martin
31 */
32 public class BorderLayout implements LayoutManager {
33
34 /**
35 * This type is what you use as the layout data for components added to a panel using {@code BorderLayout} for its
36 * layout manager. This values specified where inside the panel the component should be added.
37 */
38 public enum Location implements LayoutData {
39 /**
40 * The component with this value as its layout data will occupy the center space, whatever is remaining after
41 * the other components (if any) have allocated their space.
42 */
43 CENTER,
44 /**
45 * The component with this value as its layout data will occupy the left side of the container, attempting to
46 * allocate the preferred width of the component and at least the preferred height, but could be more depending
47 * on the other components added.
48 */
49 LEFT,
50 /**
51 * The component with this value as its layout data will occupy the right side of the container, attempting to
52 * allocate the preferred width of the component and at least the preferred height, but could be more depending
53 * on the other components added.
54 */
55 RIGHT,
56 /**
57 * The component with this value as its layout data will occupy the top side of the container, attempting to
58 * allocate the preferred height of the component and at least the preferred width, but could be more depending
59 * on the other components added.
60 */
61 TOP,
62 /**
63 * The component with this value as its layout data will occupy the bottom side of the container, attempting to
64 * allocate the preferred height of the component and at least the preferred width, but could be more depending
65 * on the other components added.
66 */
67 BOTTOM,
68 ;
69 }
70
71 //When components don't have a location, we'll assign an available location based on this order
72 private static final List<Location> AUTO_ASSIGN_ORDER = Collections.unmodifiableList(Arrays.asList(
73 Location.CENTER,
74 Location.TOP,
75 Location.BOTTOM,
76 Location.LEFT,
77 Location.RIGHT));
78
79 @Override
80 public TerminalSize getPreferredSize(List<Component> components) {
81 EnumMap<Location, Component> layout = makeLookupMap(components);
82 int preferredHeight =
83 (layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getRows() : 0)
84 +
85 Math.max(
86 layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getRows() : 0,
87 Math.max(
88 layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getRows() : 0,
89 layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getRows() : 0))
90 +
91 (layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getRows() : 0);
92
93 int preferredWidth =
94 Math.max(
95 (layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getColumns() : 0) +
96 (layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getColumns() : 0) +
97 (layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getColumns() : 0),
98 Math.max(
99 layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getColumns() : 0,
100 layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getColumns() : 0));
101 return new TerminalSize(preferredWidth, preferredHeight);
102 }
103
104 @Override
105 public void doLayout(TerminalSize area, List<Component> components) {
106 EnumMap<Location, Component> layout = makeLookupMap(components);
107 int availableHorizontalSpace = area.getColumns();
108 int availableVerticalSpace = area.getRows();
109
110 //We'll need this later on
111 int topComponentHeight = 0;
112 int leftComponentWidth = 0;
113
114 //First allocate the top
115 if(layout.containsKey(Location.TOP)) {
116 Component topComponent = layout.get(Location.TOP);
117 topComponentHeight = Math.min(topComponent.getPreferredSize().getRows(), availableVerticalSpace);
118 topComponent.setPosition(TerminalPosition.TOP_LEFT_CORNER);
119 topComponent.setSize(new TerminalSize(availableHorizontalSpace, topComponentHeight));
120 availableVerticalSpace -= topComponentHeight;
121 }
122
123 //Next allocate the bottom
124 if(layout.containsKey(Location.BOTTOM)) {
125 Component bottomComponent = layout.get(Location.BOTTOM);
126 int bottomComponentHeight = Math.min(bottomComponent.getPreferredSize().getRows(), availableVerticalSpace);
127 bottomComponent.setPosition(new TerminalPosition(0, area.getRows() - bottomComponentHeight));
128 bottomComponent.setSize(new TerminalSize(availableHorizontalSpace, bottomComponentHeight));
129 availableVerticalSpace -= bottomComponentHeight;
130 }
131
132 //Now divide the remaining space between LEFT, CENTER and RIGHT
133 if(layout.containsKey(Location.LEFT)) {
134 Component leftComponent = layout.get(Location.LEFT);
135 leftComponentWidth = Math.min(leftComponent.getPreferredSize().getColumns(), availableHorizontalSpace);
136 leftComponent.setPosition(new TerminalPosition(0, topComponentHeight));
137 leftComponent.setSize(new TerminalSize(leftComponentWidth, availableVerticalSpace));
138 availableHorizontalSpace -= leftComponentWidth;
139 }
140 if(layout.containsKey(Location.RIGHT)) {
141 Component rightComponent = layout.get(Location.RIGHT);
142 int rightComponentWidth = Math.min(rightComponent.getPreferredSize().getColumns(), availableHorizontalSpace);
143 rightComponent.setPosition(new TerminalPosition(area.getColumns() - rightComponentWidth, topComponentHeight));
144 rightComponent.setSize(new TerminalSize(rightComponentWidth, availableVerticalSpace));
145 availableHorizontalSpace -= rightComponentWidth;
146 }
147 if(layout.containsKey(Location.CENTER)) {
148 Component centerComponent = layout.get(Location.CENTER);
149 centerComponent.setPosition(new TerminalPosition(leftComponentWidth, topComponentHeight));
150 centerComponent.setSize(new TerminalSize(availableHorizontalSpace, availableVerticalSpace));
151 }
152
153 //Set the remaining components to 0x0
154 for(Component component: components) {
155 if(!layout.values().contains(component)) {
156 component.setPosition(TerminalPosition.TOP_LEFT_CORNER);
157 component.setSize(TerminalSize.ZERO);
158 }
159 }
160 }
161
162 private EnumMap<Location, Component> makeLookupMap(List<Component> components) {
163 EnumMap<Location, Component> map = new EnumMap<BorderLayout.Location, Component>(Location.class);
164 List<Component> unassignedComponents = new ArrayList<Component>();
165 for(Component component: components) {
166 if(component.getLayoutData() instanceof Location) {
167 map.put((Location)component.getLayoutData(), component);
168 }
169 else {
170 unassignedComponents.add(component);
171 }
172 }
173 //Try to assign components to available locations
174 for(Component component: unassignedComponents) {
175 for(Location location: AUTO_ASSIGN_ORDER) {
176 if(!map.containsKey(location)) {
177 map.put(location, component);
178 break;
179 }
180 }
181 }
182 return map;
183 }
184
185 @Override
186 public boolean hasChanged() {
187 //No internal state
188 return false;
189 }
190 }