/******************************************************************************* * 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.gui.impl; import java.awt.BorderLayout; import java.awt.Component; import javax.swing.JPanel; import javax.swing.JSplitPane; /** * A pane that can be split into two parts or not. This extends <code>JSplitPane</code> * by adding API calls to show and hide the split. Also, the state of the divider is * preserved when showing or hiding the split. * * @author mrose * */ @SuppressWarnings("serial") public class SplittablePane extends JPanel { /** Constant indicating the splittable pane should be split horizontally in two, * left and right. */ public static final int HORIZONTAL_SPLIT = JSplitPane.HORIZONTAL_SPLIT; /** Constant indicating the splittable pane should be split vertically in two, * top and bottom. */ public static final int VERTICAL_SPLIT = JSplitPane.VERTICAL_SPLIT; /** The divider is, by default, 40% of the way across or down. */ public static final double DEFAULT_DIVIDER_LOCATION = 0.4; /** If true, we're currently shown in split state. */ private boolean isSplit; /** The primary component, always shown. */ private Component mainComponent; /** The split pane component, if we're shown divided. */ private JSplitPane splitPane; /** The split divider position. */ private int dividerLocation; /** * Creates a new splittable pane with the default orientation and no components. */ public SplittablePane() { this(HORIZONTAL_SPLIT, new JPanel()); } /** * Creates a new splittable pane with the given orientation and main component (top or left). * * @param orientation the orientation of the split divider, when shown * @param mainComponent the top or left component, or the only component, when not split */ public SplittablePane(int orientation, Component mainComponent) { this(orientation, mainComponent, (Component) null); } /** * Creates a new splittable pane with the given orientation, main component (top or left), * and secondary component (right or bottom). * * @param orientation the orientation of the split divider, when shown * @param mainComponent the top or left component, or the only component, when not split * @param secondaryComponent the right or bottom component, when split */ public SplittablePane(int orientation, Component mainComponent, Component secondaryComponent) { setLayout(new BorderLayout()); splitPane = new JSplitPane(orientation); splitPane.setBorder(null); splitPane.setResizeWeight(0.5); // Divide extra or removed space evenly when resizing. this.mainComponent = mainComponent; this.isSplit = false; if (this.mainComponent != null) { add(this.mainComponent, BorderLayout.CENTER); } setSecondaryComponent(secondaryComponent); } /** * Sets the main component. This is either the left or top component, * depending on the orientation. * * @param component the left or top component */ public void setMainComponent(Component component) { if (!isSplit()) { if (mainComponent != null) { remove(mainComponent); } if (component != null) { add(component, BorderLayout.CENTER); } } else { if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { splitPane.setLeftComponent(component); } else { splitPane.setTopComponent(component); } } mainComponent = component; } /** * Sets the secondary component. This is either the right or bottom component, * depending on the orientation. * * @param secondaryComponent the right or bottom component */ public void setSecondaryComponent(Component secondaryComponent) { if (splitPane.getOrientation() == HORIZONTAL_SPLIT) { splitPane.setRightComponent(secondaryComponent); } else { splitPane.setBottomComponent(secondaryComponent); } } /** * Hides the split divider, showing only the main component. If not split, do nothing. */ public void hideSplit() { if (isSplit) { dividerLocation = splitPane.getDividerLocation(); remove(splitPane); splitPane.remove(mainComponent); add(mainComponent, BorderLayout.CENTER); isSplit = false; validate(); } } /** * Shows the split divider, main component and secondary component. If already split, * do nothing. */ public void showSplit() { if (!isSplit) { if (mainComponent != null) { remove(mainComponent); if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { splitPane.setLeftComponent(mainComponent); } else { splitPane.setTopComponent(mainComponent); } } add(splitPane, BorderLayout.CENTER); isSplit = true; validate(); adjustDivider(); } } /** * Restores a previous divider location, if we remembered one, else set * divider location to the default position. We have a valid divider * location whenever <code>dividerLocation</code> is greater than zero. */ protected void adjustDivider() { if (dividerLocation > 0) { splitPane.setDividerLocation(dividerLocation); } else { splitPane.setDividerLocation(DEFAULT_DIVIDER_LOCATION); } } /** * Remembers the current divider location, if we are split. */ public void saveDividerLocation() { if (isSplit()) { dividerLocation = splitPane.getDividerLocation(); } } /** * Checks whether the splittable pane is shown with the split divider. * * @return true, if the pane is shown with the split divider */ public boolean isSplit() { return isSplit; } /** * Gets the main component, left or top, depending on the split orientation. * * @return the main component */ public Component getMainComponent() { return mainComponent; } /** * Gets the secondary component, right or bottom, depending on the split orientation. * * @return the main component */ public Component getSecondaryComponent() { if (splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { return splitPane.getRightComponent(); } else { return splitPane.getBottomComponent(); } } /** * Sets the fractional position of the divider. 0 means all the way to the left or top, hiding * the main component, 1 means all the way to the right or bottom. Halfway is 0.5, for example. * * Because of limitations in <code>JSplitPane</code>, this method does not correctly set * the divider location unless the pane is split and visible on the screen. * * @param fraction the fractional position of the divider */ public void setDividerFraction(double fraction) { splitPane.setDividerLocation(fraction); } /** * Gets the current fractional position of the divider. * * @return the divider position, as a fractional value from 0 to 1 */ public double getDividerFraction() { return (double) splitPane.getDividerLocation() / (double) splitPane.getWidth(); } @Override public void setSize(int width, int height) { super.setSize(width, height); splitPane.setSize(width, height); doLayout(); } }