/* * This file is part of lanterna (http://code.google.com/p/lanterna/). * * lanterna is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Copyright (C) 2010-2015 Martin */ package com.googlecode.lanterna.gui2; import com.googlecode.lanterna.TerminalSize; import com.googlecode.lanterna.input.KeyStroke; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * This class is the basic building block for creating user interfaces, being the standard implementation of * {@code Container} that supports multiple children. A {@code Panel} is a component that can contain one or more * other components, including nested panels. The panel itself doesn't have any particular appearance and isn't * interactable by itself, although you can set a border for the panel and interactable components inside the panel will * receive input focus as expected. * * @author Martin */ public class Panel extends AbstractComponent implements Container { private final List components; private LayoutManager layoutManager; private TerminalSize cachedPreferredSize; /** * Default constructor, creates a new panel with no child components and by default set to a vertical * {@code LinearLayout} layout manager. */ public Panel() { components = new ArrayList(); layoutManager = new LinearLayout(); cachedPreferredSize = null; } /** * Adds a new child component to the panel. Where within the panel the child will be displayed is up to the layout * manager assigned to this panel. * @param component Child component to add to this panel * @return Itself */ public synchronized Panel addComponent(Component component) { if(component == null) { throw new IllegalArgumentException("Cannot add null component"); } if(components.contains(component)) { return this; } components.add(component); component.onAdded(this); invalidate(); return this; } /** * This method is a shortcut for calling: *
     *     {@code
     *     component.setLayoutData(layoutData);
     *     panel.addComponent(component);
     *     }
     * 
* @param component Component to add to the panel * @param layoutData Layout data to assign to the component * @return Itself */ public Panel addComponent(Component component, LayoutData layoutData) { if(component != null) { component.setLayoutData(layoutData); addComponent(component); } return this; } @Override public boolean containsComponent(Component component) { return component != null && component.hasParent(this); } @Override public synchronized boolean removeComponent(Component component) { if(component == null) { throw new IllegalArgumentException("Cannot remove null component"); } int index = components.indexOf(component); if(index == -1) { return false; } if(getBasePane() != null && getBasePane().getFocusedInteractable() == component) { getBasePane().setFocusedInteractable(null); } components.remove(index); component.onRemoved(this); invalidate(); return true; } /** * Removes all child components from this panel * @return Itself */ public synchronized Panel removeAllComponents() { for(Component component: new ArrayList(components)) { removeComponent(component); } return this; } /** * Assigns a new layout manager to this panel, replacing the previous layout manager assigned. Please note that if * the panel is not empty at the time you assign a new layout manager, the existing components might not show up * where you expect them and their layout data property might need to be re-assigned. * @param layoutManager New layout manager this panel should be using * @return Itself */ public synchronized Panel setLayoutManager(LayoutManager layoutManager) { if(layoutManager == null) { layoutManager = new AbsoluteLayout(); } this.layoutManager = layoutManager; invalidate(); return this; } /** * Returns the layout manager assigned to this panel * @return */ public LayoutManager getLayoutManager() { return layoutManager; } @Override public int getChildCount() { synchronized(components) { return components.size(); } } @Override public Collection getChildren() { synchronized(components) { return new ArrayList(components); } } @Override protected ComponentRenderer createDefaultRenderer() { return new ComponentRenderer() { @Override public TerminalSize getPreferredSize(Panel component) { cachedPreferredSize = layoutManager.getPreferredSize(components); return cachedPreferredSize; } @Override public void drawComponent(TextGUIGraphics graphics, Panel component) { if(isInvalid()) { layout(graphics.getSize()); } for(Component child: components) { TextGUIGraphics componentGraphics = graphics.newTextGraphics(child.getPosition(), child.getSize()); child.draw(componentGraphics); } } }; } @Override public TerminalSize calculatePreferredSize() { if(cachedPreferredSize != null && !isInvalid()) { return cachedPreferredSize; } return super.calculatePreferredSize(); } @Override public boolean isInvalid() { for(Component component: components) { if(component.isInvalid()) { return true; } } return super.isInvalid() || layoutManager.hasChanged(); } @Override public Interactable nextFocus(Interactable fromThis) { boolean chooseNextAvailable = (fromThis == null); for (Component component : components) { if (chooseNextAvailable) { if (component instanceof Interactable) { return (Interactable) component; } else if (component instanceof Container) { Interactable firstInteractable = ((Container)(component)).nextFocus(null); if (firstInteractable != null) { return firstInteractable; } } continue; } if (component == fromThis) { chooseNextAvailable = true; continue; } if (component instanceof Container) { Container container = (Container) component; if (fromThis.isInside(container)) { Interactable next = container.nextFocus(fromThis); if (next == null) { chooseNextAvailable = true; } else { return next; } } } } return null; } @Override public Interactable previousFocus(Interactable fromThis) { boolean chooseNextAvailable = (fromThis == null); List revComponents = new ArrayList(components); Collections.reverse(revComponents); for (Component component : revComponents) { if (chooseNextAvailable) { if (component instanceof Interactable) { return (Interactable) component; } if (component instanceof Container) { Interactable lastInteractable = ((Container)(component)).previousFocus(null); if (lastInteractable != null) { return lastInteractable; } } continue; } if (component == fromThis) { chooseNextAvailable = true; continue; } if (component instanceof Container) { Container container = (Container) component; if (fromThis.isInside(container)) { Interactable next = container.previousFocus(fromThis); if (next == null) { chooseNextAvailable = true; } else { return next; } } } } return null; } @Override public boolean handleInput(KeyStroke key) { return false; } @Override public void updateLookupMap(InteractableLookupMap interactableLookupMap) { for(Component component: components) { if(component instanceof Container) { ((Container)component).updateLookupMap(interactableLookupMap); } else if(component instanceof Interactable) { interactableLookupMap.add((Interactable)component); } } } @Override public void invalidate() { super.invalidate(); //Propagate for(Component component: components) { component.invalidate(); } } private void layout(TerminalSize size) { layoutManager.doLayout(size, components); } }