/* * 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 <http://www.gnu.org/licenses/>. * * Copyright (C) 2010-2012 Martin */ package com.googlecode.lanterna.gui.layout; import com.googlecode.lanterna.gui.Component; import com.googlecode.lanterna.gui.component.Panel; import com.googlecode.lanterna.terminal.TerminalPosition; import com.googlecode.lanterna.terminal.TerminalSize; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * * @author Martin */ public abstract class LinearLayout implements LayoutManager { public static final LayoutParameter MAXIMIZES_HORIZONTALLY = new LayoutParameter("LinearLayout.MAXIMIZES_HORIZONTALLY"); public static final LayoutParameter MAXIMIZES_VERTICALLY = new LayoutParameter("LinearLayout.MAXIMIZES_VERTICALLY"); public static final LayoutParameter GROWS_HORIZONTALLY = new LayoutParameter("LinearLayout.GROWS_HORIZONTALLY"); public static final LayoutParameter GROWS_VERTICALLY = new LayoutParameter("LinearLayout.GROWS_VERTICALLY"); private final List<LinearLayoutComponent> componentList; private int padding; LinearLayout() { this.componentList = new ArrayList<LinearLayoutComponent>(); this.padding = 0; } @Override public void addComponent(Component component, LayoutParameter... layoutParameters) { Set<LayoutParameter> asSet = new HashSet<LayoutParameter>(Arrays.asList(layoutParameters)); if(asSet.contains(MAXIMIZES_HORIZONTALLY) && asSet.contains(GROWS_HORIZONTALLY)) throw new IllegalArgumentException("Component " + component + " cannot be both maximizing and growing horizontally at the same time"); if(asSet.contains(MAXIMIZES_VERTICALLY) && asSet.contains(GROWS_VERTICALLY)) throw new IllegalArgumentException("Component " + component + " cannot be both maximizing and growing vertically at the same time"); componentList.add(new LinearLayoutComponent(component, asSet)); } @Override public void removeComponent(Component component) { Iterator<LinearLayoutComponent> iterator = componentList.iterator(); while (iterator.hasNext()) { if (iterator.next().component == component) { iterator.remove(); return; } } } public void setPadding(int padding) { this.padding = padding; } @Override public TerminalSize getPreferredSize() { if(componentList.isEmpty()) return new TerminalSize(0, 0); TerminalSize preferredSize = new TerminalSize(0, 0); for (LinearLayoutComponent axisLayoutComponent : componentList) { final TerminalSize componentPreferredSize = axisLayoutComponent.component.getPreferredSize(); setMajorAxis(preferredSize, getMajorAxis(preferredSize) + getMajorAxis(componentPreferredSize)); setMinorAxis(preferredSize, Math.max(getMinorAxis(preferredSize), getMinorAxis(componentPreferredSize))); } setMajorAxis(preferredSize, getMajorAxis(preferredSize) + (padding * (componentList.size() - 1))); return preferredSize; } @Override public List<? extends LaidOutComponent> layout(TerminalSize layoutArea) { List<DefaultLaidOutComponent> result = new ArrayList<DefaultLaidOutComponent>(); Map<Component, TerminalSize> minimumSizeMap = new IdentityHashMap<Component, TerminalSize>(); Map<Component, TerminalSize> preferredSizeMap = new IdentityHashMap<Component, TerminalSize>(); Map<Component, Set<LayoutParameter>> layoutParameterMap = new IdentityHashMap<Component, Set<LayoutParameter>>(); for(LinearLayoutComponent llc: componentList) { minimumSizeMap.put(llc.component, llc.component.getMinimumSize()); preferredSizeMap.put(llc.component, llc.component.getPreferredSize()); layoutParameterMap.put(llc.component, llc.layoutParameters); } int availableMajorAxisSpace = getMajorAxis(layoutArea); int availableMinorAxisSpace = getMinorAxis(layoutArea); for(LinearLayoutComponent llc: componentList) result.add(new DefaultLaidOutComponent(llc.component, new TerminalSize(0, 0), new TerminalPosition(0, 0))); //Set minor axis - easy! for(DefaultLaidOutComponent lloc: result) { if(layoutParameterMap.get(lloc.component).contains(getMinorMaximizesParameter()) || layoutParameterMap.get(lloc.component).contains(getMinorGrowingParameter()) || (lloc.component instanceof Panel && maximisesOnMinorAxis((Panel)lloc.component))) { setMinorAxis(lloc.size, availableMinorAxisSpace); } else { int preferred = getMinorAxis(preferredSizeMap.get(lloc.component)); setMinorAxis(lloc.size, preferred <= availableMinorAxisSpace ? preferred : availableMinorAxisSpace); } } //Start dividing the major axis - hard! while(availableMajorAxisSpace > 0) { boolean changedSomething = false; for(DefaultLaidOutComponent lloc: result) { int preferred = getMajorAxis(preferredSizeMap.get(lloc.component)); if(availableMajorAxisSpace > 0 && preferred > getMajorAxis(lloc.getSize())) { availableMajorAxisSpace--; setMajorAxis(lloc.getSize(), getMajorAxis(lloc.getSize()) + 1); changedSomething = true; } } if(!changedSomething) break; } //Add padding, if any (this could cause availableMajorAxisSpace to go negative!! Beware!) availableMajorAxisSpace -= ((result.size() - 1) * padding); //Now try to accomodate the growing major axis components List<DefaultLaidOutComponent> growingComponents = new ArrayList<DefaultLaidOutComponent>(); for(DefaultLaidOutComponent lloc: result) { if(layoutParameterMap.get(lloc.component).contains(getMajorMaximizesParameter()) || layoutParameterMap.get(lloc.component).contains(getMajorGrowingParameter())) { growingComponents.add(lloc); } if(lloc.component instanceof Panel && maximisesOnMajorAxis((Panel)lloc.component)) { growingComponents.add(lloc); } } while(availableMajorAxisSpace > 0 && !growingComponents.isEmpty()) { for(DefaultLaidOutComponent lloc: growingComponents) { if(availableMajorAxisSpace > 0) { availableMajorAxisSpace--; setMajorAxis(lloc.getSize(), getMajorAxis(lloc.getSize()) + 1); } } } //Finally, recalculate the topLeft position of each component int nextMajorPosition = 0; for(DefaultLaidOutComponent laidOutComponent: result) { setMajorAxis(laidOutComponent.topLeftPosition, nextMajorPosition); //Make sure not to add padding to the last component if(result.get(result.size() - 1) != laidOutComponent) nextMajorPosition += getMajorAxis(laidOutComponent.size) + padding; else nextMajorPosition += getMajorAxis(laidOutComponent.size); } return result; } @Override public boolean maximisesHorisontally() { for(LinearLayoutComponent llc: componentList) { if(llc.layoutParameters.contains(MAXIMIZES_HORIZONTALLY)) return true; } return false; } @Override public boolean maximisesVertically() { for(LinearLayoutComponent llc: componentList) { if(llc.layoutParameters.contains(MAXIMIZES_VERTICALLY)) return true; } return false; } protected abstract boolean maximisesOnMajorAxis(Panel panel); protected abstract boolean maximisesOnMinorAxis(Panel panel); protected abstract void setMajorAxis(TerminalSize terminalSize, int majorAxisValue); protected abstract void setMinorAxis(TerminalSize terminalSize, int minorAxisValue); protected abstract void setMajorAxis(TerminalPosition terminalPosition, int majorAxisValue); protected abstract int getMajorAxis(TerminalSize terminalSize); protected abstract int getMinorAxis(TerminalSize terminalSize); protected abstract LayoutParameter getMajorMaximizesParameter(); protected abstract LayoutParameter getMinorMaximizesParameter(); protected abstract LayoutParameter getMajorGrowingParameter(); protected abstract LayoutParameter getMinorGrowingParameter(); protected List<Panel> getSubPanels() { List<Panel> subPanels = new ArrayList<Panel>(); for (LinearLayoutComponent axisLayoutComponent : componentList) { if (axisLayoutComponent.component instanceof Panel) { subPanels.add((Panel) axisLayoutComponent.component); } } return subPanels; } protected static class LinearLayoutComponent { public Component component; public Set<LayoutParameter> layoutParameters; public LinearLayoutComponent(Component component, Set<LayoutParameter> layoutParameters) { this.component = component; this.layoutParameters = layoutParameters; } } }