/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package gov.nasa.arc.mct.plot.settings;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
/**
* This layout manager allows a lower panel to remain visible and on top of a middle panel
* when the parent cannot accommodate the heights of both. It also allows the lower panel to
* remain adjacent to the middle panel when the parent has more height than its children require.
*/
public class PlotControlsLayout implements LayoutManager2 {
/**
* This scroll pane class provides a convenient way to access the resizeable components
* that reside in this scroll pane, eliminating a search.
*/
@SuppressWarnings("serial")
public static class ResizersScrollPane extends JScrollPane {
private JComponent[] resizers;
public ResizersScrollPane(JComponent component, JComponent... resizerWidget) {
super(component);
int numResizers = resizerWidget.length;
resizers = new JComponent[numResizers];
for (int k = 0; k < numResizers; k++) {
resizers[k] = resizerWidget[k];
}
}
public JComponent[] getResizers() {
return resizers;
}
}
private static final int DEFAULT_PADDING = 0;
public static final String MIDDLE = "Middle";
public static final String LOWER = "Lower";
private int minWidth = 0;
private int minHeight = 0;
private boolean sizeUnknown = true;
private int preferredWidth;
private int preferredHeight;
/*
* The padding between the middle panel and the lower panel
*/
private int innerPadding = 0;
private Component middleComponent;
private Component lowerComponent;
public PlotControlsLayout() {
this(DEFAULT_PADDING);
}
public PlotControlsLayout(int padding) {
innerPadding = padding;
}
/*
* LayoutManager2
* NOTE: there is another method with same name in LayoutManager
*/
@Override
public void addLayoutComponent(final Component comp, Object constraints) {
if (constraints instanceof String) {
String name = (String) constraints;
if (MIDDLE.equals(name)) {
middleComponent = comp;
if (comp instanceof ResizersScrollPane) {
final JComponent parent = (JComponent) comp.getParent();
JComponent[] children = ((ResizersScrollPane) comp).getResizers();
for (JComponent child : children) {
if (child.getComponentListeners().length == 0) {
child.addComponentListener(new ComponentListener() {
@Override
public void componentResized(ComponentEvent e) {
parent.revalidate();
}
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentMoved(ComponentEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
});
}
}
}
} else
if (LOWER.equals(name)) {
lowerComponent = comp;
} else {
throw new IllegalArgumentException("Cannot use unknown constraint in layout: " + name);
}
}
}
/* LayoutManager2 */
@Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
/* LayoutManager2 */
@Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
/* LayoutManager2 */
@Override
public void invalidateLayout(Container target) {
}
/* LayoutManager2 */
@Override
public Dimension maximumLayoutSize(Container target) {
if (sizeUnknown) {
setSizes(target);
}
return new Dimension(preferredWidth, preferredHeight);
}
/*
* LayoutManager
* NOTE: another method with same name in LayoutManager2
*/
@Override
public void addLayoutComponent(String name, Component comp) {
}
/* LayoutManager */
/*
* This is called when the panel is first displayed, and every time its size changes.
* Note: You can't assume preferredLayoutSize or minimumLayoutSize will be called.
*/
@Override
public void layoutContainer(Container parent) {
if (sizeUnknown) {
setSizes(parent);
}
if (middleComponent != null && lowerComponent != null ) {
doLayout(parent, middleComponent, lowerComponent);
} else
if (middleComponent != null) {
middleComponent.setBounds(0, 0, parent.getSize().width, middleComponent.getPreferredSize().height);
} else
if (lowerComponent != null) {
lowerComponent.setBounds(0, 0, parent.getSize().width, lowerComponent.getPreferredSize().height);
}
}
private void doLayout(Container parent, Component middleComp, Component lowerComp) {
Dimension parentDim = parent.getSize();
Dimension middleDim = middleComp.getPreferredSize();
Dimension lowerDim = lowerComp.getPreferredSize();
// In the following, the middle component is a scroll pane and the lower component is a panel.
// a) The widths of both the scroll pane and the panel are expanded to the width of the parent
// b) Maintain a buffer (INNER_PADDING) between the scroll pane and the panel
// c) The panel is positioned at the bottom of the parent, and is allowed its preferred height
// d) The scroll pane shrinks to fit the remaining space after the panel and buffer are handled
// Does the parent have enough space for both components ?
if (middleDim.height + lowerDim.height + innerPadding < parentDim.height) {
// If so, position the scroll pane at the top of the parent
middleComp.setBounds(0, 0, parentDim.width, middleDim.height);
lowerComp.setBounds(0, middleDim.height + innerPadding - 1, parentDim.width, lowerDim.height);
} else {
// If not, shrink the height of the scroll pane, and ensure the panel is completely visible
// as long as possible while parent shrinks.
int overlapY = middleDim.height + lowerDim.height + innerPadding - parentDim.height;
middleComp.setBounds(0, 0, parentDim.width, middleDim.height - overlapY);
lowerComp.setBounds(0, middleDim.height + innerPadding - overlapY - 1, parentDim.width, lowerDim.height);
parent.setComponentZOrder(lowerComp, 0);
}
}
/* LayoutManager */
@Override
public Dimension minimumLayoutSize(Container parent) {
if (sizeUnknown) {
setSizes(parent);
}
Dimension dim = new Dimension(0, 0);
// Always add the container's insets
Insets insets = parent.getInsets();
dim.width = minWidth + insets.left + insets.right;
dim.height = minHeight + insets.top + insets.bottom;
return dim;
}
/* LayoutManager */
@Override
public Dimension preferredLayoutSize(Container parent) {
if (sizeUnknown) {
setSizes(parent);
}
Dimension dim = new Dimension(0, 0);
// Always add the container's insets
Insets insets = parent.getInsets();
dim.width = preferredWidth + insets.left + insets.right;
dim.height = preferredHeight + insets.top + insets.bottom;
return dim;
}
private void setSizes(Container parent) {
int nComps = parent.getComponentCount();
// Reset preferred/minimum width and height.
preferredWidth = 0;
preferredHeight = 0;
minWidth = 0;
minHeight = 0;
for (int i = 0; i < nComps; i++) {
Component comp = parent.getComponent(i);
if (comp.isVisible()) {
Dimension prefSize = comp.getPreferredSize();
preferredWidth = (prefSize.width > preferredWidth) ? prefSize.width : preferredWidth;
preferredHeight += prefSize.height;
minWidth = Math.max(comp.getMinimumSize().width, minWidth);
minHeight += comp.getMinimumSize().height;
}
}
sizeUnknown = false;
}
/* LayoutManager */
@Override
public void removeLayoutComponent(Component comp) {
if (comp == middleComponent) {
middleComponent = null;
} else
if (comp == lowerComponent) {
lowerComponent = null;
}
}
void resetSizeFlag() {
sizeUnknown = true;
}
}