Fix UTF8 bug, create first executable JAR file
[jvcard.git] / src / com / googlecode / lanterna / gui2 / Panel.java
... / ...
CommitLineData
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 */
19package com.googlecode.lanterna.gui2;
20
21import com.googlecode.lanterna.TerminalSize;
22import com.googlecode.lanterna.input.KeyStroke;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.List;
27
28/**
29 * This class is the basic building block for creating user interfaces, being the standard implementation of
30 * {@code Container} that supports multiple children. A {@code Panel} is a component that can contain one or more
31 * other components, including nested panels. The panel itself doesn't have any particular appearance and isn't
32 * interactable by itself, although you can set a border for the panel and interactable components inside the panel will
33 * receive input focus as expected.
34 *
35 * @author Martin
36 */
37public class Panel extends AbstractComponent<Panel> implements Container {
38 private final List<Component> components;
39 private LayoutManager layoutManager;
40 private TerminalSize cachedPreferredSize;
41
42 /**
43 * Default constructor, creates a new panel with no child components and by default set to a vertical
44 * {@code LinearLayout} layout manager.
45 */
46 public Panel() {
47 components = new ArrayList<Component>();
48 layoutManager = new LinearLayout();
49 cachedPreferredSize = null;
50 }
51
52 /**
53 * Adds a new child component to the panel. Where within the panel the child will be displayed is up to the layout
54 * manager assigned to this panel.
55 * @param component Child component to add to this panel
56 * @return Itself
57 */
58 public synchronized Panel addComponent(Component component) {
59 if(component == null) {
60 throw new IllegalArgumentException("Cannot add null component");
61 }
62 if(components.contains(component)) {
63 return this;
64 }
65 components.add(component);
66 component.onAdded(this);
67 invalidate();
68 return this;
69 }
70
71 /**
72 * This method is a shortcut for calling:
73 * <pre>
74 * {@code
75 * component.setLayoutData(layoutData);
76 * panel.addComponent(component);
77 * }
78 * </pre>
79 * @param component Component to add to the panel
80 * @param layoutData Layout data to assign to the component
81 * @return Itself
82 */
83 public Panel addComponent(Component component, LayoutData layoutData) {
84 if(component != null) {
85 component.setLayoutData(layoutData);
86 addComponent(component);
87 }
88 return this;
89 }
90
91 @Override
92 public boolean containsComponent(Component component) {
93 return component != null && component.hasParent(this);
94 }
95
96 @Override
97 public synchronized boolean removeComponent(Component component) {
98 if(component == null) {
99 throw new IllegalArgumentException("Cannot remove null component");
100 }
101 int index = components.indexOf(component);
102 if(index == -1) {
103 return false;
104 }
105 if(getBasePane() != null && getBasePane().getFocusedInteractable() == component) {
106 getBasePane().setFocusedInteractable(null);
107 }
108 components.remove(index);
109 component.onRemoved(this);
110 invalidate();
111 return true;
112 }
113
114 /**
115 * Removes all child components from this panel
116 * @return Itself
117 */
118 public synchronized Panel removeAllComponents() {
119 for(Component component: new ArrayList<Component>(components)) {
120 removeComponent(component);
121 }
122 return this;
123 }
124
125 /**
126 * Assigns a new layout manager to this panel, replacing the previous layout manager assigned. Please note that if
127 * the panel is not empty at the time you assign a new layout manager, the existing components might not show up
128 * where you expect them and their layout data property might need to be re-assigned.
129 * @param layoutManager New layout manager this panel should be using
130 * @return Itself
131 */
132 public synchronized Panel setLayoutManager(LayoutManager layoutManager) {
133 if(layoutManager == null) {
134 layoutManager = new AbsoluteLayout();
135 }
136 this.layoutManager = layoutManager;
137 invalidate();
138 return this;
139 }
140
141 /**
142 * Returns the layout manager assigned to this panel
143 * @return
144 */
145 public LayoutManager getLayoutManager() {
146 return layoutManager;
147 }
148
149 @Override
150 public int getChildCount() {
151 synchronized(components) {
152 return components.size();
153 }
154 }
155
156 @Override
157 public Collection<Component> getChildren() {
158 synchronized(components) {
159 return new ArrayList<Component>(components);
160 }
161 }
162
163 @Override
164 protected ComponentRenderer<Panel> createDefaultRenderer() {
165 return new ComponentRenderer<Panel>() {
166
167 @Override
168 public TerminalSize getPreferredSize(Panel component) {
169 cachedPreferredSize = layoutManager.getPreferredSize(components);
170 return cachedPreferredSize;
171 }
172
173 @Override
174 public void drawComponent(TextGUIGraphics graphics, Panel component) {
175 if(isInvalid()) {
176 layout(graphics.getSize());
177 }
178 for(Component child: components) {
179 TextGUIGraphics componentGraphics = graphics.newTextGraphics(child.getPosition(), child.getSize());
180 child.draw(componentGraphics);
181 }
182 }
183 };
184 }
185
186 @Override
187 public TerminalSize calculatePreferredSize() {
188 if(cachedPreferredSize != null && !isInvalid()) {
189 return cachedPreferredSize;
190 }
191 return super.calculatePreferredSize();
192 }
193
194 @Override
195 public boolean isInvalid() {
196 for(Component component: components) {
197 if(component.isInvalid()) {
198 return true;
199 }
200 }
201 return super.isInvalid() || layoutManager.hasChanged();
202 }
203
204 @Override
205 public Interactable nextFocus(Interactable fromThis) {
206 boolean chooseNextAvailable = (fromThis == null);
207
208 for (Component component : components) {
209 if (chooseNextAvailable) {
210 if (component instanceof Interactable) {
211 return (Interactable) component;
212 }
213 else if (component instanceof Container) {
214 Interactable firstInteractable = ((Container)(component)).nextFocus(null);
215 if (firstInteractable != null) {
216 return firstInteractable;
217 }
218 }
219 continue;
220 }
221
222 if (component == fromThis) {
223 chooseNextAvailable = true;
224 continue;
225 }
226
227 if (component instanceof Container) {
228 Container container = (Container) component;
229 if (fromThis.isInside(container)) {
230 Interactable next = container.nextFocus(fromThis);
231 if (next == null) {
232 chooseNextAvailable = true;
233 } else {
234 return next;
235 }
236 }
237 }
238 }
239 return null;
240 }
241
242 @Override
243 public Interactable previousFocus(Interactable fromThis) {
244 boolean chooseNextAvailable = (fromThis == null);
245
246 List<Component> revComponents = new ArrayList<Component>(components);
247 Collections.reverse(revComponents);
248
249 for (Component component : revComponents) {
250 if (chooseNextAvailable) {
251 if (component instanceof Interactable) {
252 return (Interactable) component;
253 }
254 if (component instanceof Container) {
255 Interactable lastInteractable = ((Container)(component)).previousFocus(null);
256 if (lastInteractable != null) {
257 return lastInteractable;
258 }
259 }
260 continue;
261 }
262
263 if (component == fromThis) {
264 chooseNextAvailable = true;
265 continue;
266 }
267
268 if (component instanceof Container) {
269 Container container = (Container) component;
270 if (fromThis.isInside(container)) {
271 Interactable next = container.previousFocus(fromThis);
272 if (next == null) {
273 chooseNextAvailable = true;
274 } else {
275 return next;
276 }
277 }
278 }
279 }
280 return null;
281 }
282
283 @Override
284 public boolean handleInput(KeyStroke key) {
285 return false;
286 }
287
288 @Override
289 public void updateLookupMap(InteractableLookupMap interactableLookupMap) {
290 for(Component component: components) {
291 if(component instanceof Container) {
292 ((Container)component).updateLookupMap(interactableLookupMap);
293 }
294 else if(component instanceof Interactable) {
295 interactableLookupMap.add((Interactable)component);
296 }
297 }
298 }
299
300 @Override
301 public void invalidate() {
302 super.invalidate();
303
304 //Propagate
305 for(Component component: components) {
306 component.invalidate();
307 }
308 }
309
310 private void layout(TerminalSize size) {
311 layoutManager.doLayout(size, components);
312 }
313}