2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
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.
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.
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/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.gui2
;
21 import com
.googlecode
.lanterna
.TerminalSize
;
22 import com
.googlecode
.lanterna
.input
.KeyStroke
;
23 import java
.util
.ArrayList
;
24 import java
.util
.Collection
;
25 import java
.util
.Collections
;
26 import java
.util
.List
;
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.
37 public class Panel
extends AbstractComponent
<Panel
> implements Container
{
38 private final List
<Component
> components
;
39 private LayoutManager layoutManager
;
40 private TerminalSize cachedPreferredSize
;
43 * Default constructor, creates a new panel with no child components and by default set to a vertical
44 * {@code LinearLayout} layout manager.
47 components
= new ArrayList
<Component
>();
48 layoutManager
= new LinearLayout();
49 cachedPreferredSize
= null;
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
58 public synchronized Panel
addComponent(Component component
) {
59 if(component
== null) {
60 throw new IllegalArgumentException("Cannot add null component");
62 if(components
.contains(component
)) {
65 components
.add(component
);
66 component
.onAdded(this);
72 * This method is a shortcut for calling:
75 * component.setLayoutData(layoutData);
76 * panel.addComponent(component);
79 * @param component Component to add to the panel
80 * @param layoutData Layout data to assign to the component
83 public Panel
addComponent(Component component
, LayoutData layoutData
) {
84 if(component
!= null) {
85 component
.setLayoutData(layoutData
);
86 addComponent(component
);
92 public boolean containsComponent(Component component
) {
93 return component
!= null && component
.hasParent(this);
97 public synchronized boolean removeComponent(Component component
) {
98 if(component
== null) {
99 throw new IllegalArgumentException("Cannot remove null component");
101 int index
= components
.indexOf(component
);
105 if(getBasePane() != null && getBasePane().getFocusedInteractable() == component
) {
106 getBasePane().setFocusedInteractable(null);
108 components
.remove(index
);
109 component
.onRemoved(this);
115 * Removes all child components from this panel
118 public synchronized Panel
removeAllComponents() {
119 for(Component component
: new ArrayList
<Component
>(components
)) {
120 removeComponent(component
);
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
132 public synchronized Panel
setLayoutManager(LayoutManager layoutManager
) {
133 if(layoutManager
== null) {
134 layoutManager
= new AbsoluteLayout();
136 this.layoutManager
= layoutManager
;
142 * Returns the layout manager assigned to this panel
145 public LayoutManager
getLayoutManager() {
146 return layoutManager
;
150 public int getChildCount() {
151 synchronized(components
) {
152 return components
.size();
157 public Collection
<Component
> getChildren() {
158 synchronized(components
) {
159 return new ArrayList
<Component
>(components
);
164 protected ComponentRenderer
<Panel
> createDefaultRenderer() {
165 return new ComponentRenderer
<Panel
>() {
168 public TerminalSize
getPreferredSize(Panel component
) {
169 cachedPreferredSize
= layoutManager
.getPreferredSize(components
);
170 return cachedPreferredSize
;
174 public void drawComponent(TextGUIGraphics graphics
, Panel component
) {
176 layout(graphics
.getSize());
178 for(Component child
: components
) {
179 TextGUIGraphics componentGraphics
= graphics
.newTextGraphics(child
.getPosition(), child
.getSize());
180 child
.draw(componentGraphics
);
187 public TerminalSize
calculatePreferredSize() {
188 if(cachedPreferredSize
!= null && !isInvalid()) {
189 return cachedPreferredSize
;
191 return super.calculatePreferredSize();
195 public boolean isInvalid() {
196 for(Component component
: components
) {
197 if(component
.isInvalid()) {
201 return super.isInvalid() || layoutManager
.hasChanged();
205 public Interactable
nextFocus(Interactable fromThis
) {
206 boolean chooseNextAvailable
= (fromThis
== null);
208 for (Component component
: components
) {
209 if (chooseNextAvailable
) {
210 if (component
instanceof Interactable
) {
211 return (Interactable
) component
;
213 else if (component
instanceof Container
) {
214 Interactable firstInteractable
= ((Container
)(component
)).nextFocus(null);
215 if (firstInteractable
!= null) {
216 return firstInteractable
;
222 if (component
== fromThis
) {
223 chooseNextAvailable
= true;
227 if (component
instanceof Container
) {
228 Container container
= (Container
) component
;
229 if (fromThis
.isInside(container
)) {
230 Interactable next
= container
.nextFocus(fromThis
);
232 chooseNextAvailable
= true;
243 public Interactable
previousFocus(Interactable fromThis
) {
244 boolean chooseNextAvailable
= (fromThis
== null);
246 List
<Component
> revComponents
= new ArrayList
<Component
>(components
);
247 Collections
.reverse(revComponents
);
249 for (Component component
: revComponents
) {
250 if (chooseNextAvailable
) {
251 if (component
instanceof Interactable
) {
252 return (Interactable
) component
;
254 if (component
instanceof Container
) {
255 Interactable lastInteractable
= ((Container
)(component
)).previousFocus(null);
256 if (lastInteractable
!= null) {
257 return lastInteractable
;
263 if (component
== fromThis
) {
264 chooseNextAvailable
= true;
268 if (component
instanceof Container
) {
269 Container container
= (Container
) component
;
270 if (fromThis
.isInside(container
)) {
271 Interactable next
= container
.previousFocus(fromThis
);
273 chooseNextAvailable
= true;
284 public boolean handleInput(KeyStroke key
) {
289 public void updateLookupMap(InteractableLookupMap interactableLookupMap
) {
290 for(Component component
: components
) {
291 if(component
instanceof Container
) {
292 ((Container
)component
).updateLookupMap(interactableLookupMap
);
294 else if(component
instanceof Interactable
) {
295 interactableLookupMap
.add((Interactable
)component
);
301 public void invalidate() {
305 for(Component component
: components
) {
306 component
.invalidate();
310 private void layout(TerminalSize size
) {
311 layoutManager
.doLayout(size
, components
);