/*
* $Id$
*
* Copyright (c) 2003 by Rodney Kinney
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.plaf.basic.BasicSplitPaneUI;
import VASSAL.Info;
/**
* Provides support for hidden panels. Use the split methods to create an instance of {@link SplitPane}, which can then
* be manipulated to show and hide the panel
*/
public class ComponentSplitter {
/**
* Create a new hideable panel to the right of the base component. The base component is replaced by a
* {@link SplitPane}
*
* @param base
* the base component
* @param hideableComponent
* the hideable component
* @param resizeOnVisibilityChange
* If true, the containing window will expand or shrink to an appropriate size when the hideable component is
* shown or hidden
* @return the {@link SplitPane} containing the two components
*/
public SplitPane splitRight(Component base, Component hideableComponent, boolean resizeOnVisibilityChange) {
return split(base, hideableComponent, SplitPane.HIDE_RIGHT, resizeOnVisibilityChange);
}
/**
* Create a new hideable panel to the left of the base component. The base component is replaced by a
* {@link SplitPane}
*
* @param base
* the base component
* @param hideableComponent
* the hideable component
* @param resizeOnVisibilityChange
* If true, the containing window will expand or shrink to an appropriate size when the hideable component is
* shown or hidden
* @return the {@link SplitPane} containing the two components
*/
public SplitPane splitLeft(Component base, Component hideableComponent, boolean resizeOnVisibilityChange) {
return split(base, hideableComponent, SplitPane.HIDE_LEFT, resizeOnVisibilityChange);
}
/**
* Create a new hideable panel to the bottom of the base component. The base component is replaced by a
* {@link SplitPane}
*
* @param base
* the base component
* @param hideableComponent
* the hideable component
* @param resizeOnVisibilityChange
* If true, the containing window will expand or shrink to an appropriate size when the hideable component is
* shown or hidden
* @return the {@link SplitPane} containing the two components
*/
public SplitPane splitBottom(Component base, Component hideableComponent, boolean resizeOnVisibilityChange) {
return split(base, hideableComponent, SplitPane.HIDE_BOTTOM, resizeOnVisibilityChange);
}
/**
* Create a new hideable panel to the top of the base component. The base component is replaced by a {@link SplitPane}
*
* @param base
* the base component
* @param hideableComponent
* the hideable component
* @param resizeOnVisibilityChange
* If true, the containing window will expand or shrink to an appropriate size when the hideable component is
* shown or hidden
* @return the {@link SplitPane} containing the two components
*/
public SplitPane splitTop(Component base, Component hideableComponent, boolean resizeOnVisibilityChange) {
return split(base, hideableComponent, SplitPane.HIDE_TOP, resizeOnVisibilityChange);
}
/**
* Search the containment hierarchy for the index-th {@link SplitPane} ancestor of a target component
*
* @param c
* the target component
* @param index
* If -1, return the last {@link SplitPane} ancestor
* @return the {@link SplitPane} ancestor, or the original component if none is found
*/
public Component getSplitAncestor(Component c, int index) {
Component next = SwingUtilities.getAncestorOfClass(SplitPane.class, c);
int count = -1;
while (next != null && (index < 0 || count++ < index)) {
c = next;
next = SwingUtilities.getAncestorOfClass(SplitPane.class, c);
}
return c;
}
private SplitPane split(Component base, final Component newComponent, int hideablePosition, boolean resize) {
int index = -1;
Container parent = base.getParent();
if (base.getParent() != null) {
for (int i = 0, n = base.getParent().getComponentCount(); i < n; ++i) {
if (base == base.getParent().getComponent(i)) {
index = i;
break;
}
}
}
final SplitPane split = new SplitPane(newComponent, base, hideablePosition, resize);
if (index >= 0) {
parent.add(split, index);
}
return split;
}
/**
* Contains methods to automatically show/hide one of its components (the "hideable" component) while the other (the
* "base" component) remains always visible. Can optionally change the size of its top level ancestorExtension when
* the component is shown/hidden. The hideable component is initially hidden
*/
public static class SplitPane extends JSplitPane {
private static final long serialVersionUID = 1L;
private boolean resizeOnVisibilityChange;
private int hideablePosition;
public static final int HIDE_TOP = 0;
public static final int HIDE_BOTTOM = 1;
public static final int HIDE_LEFT = 2;
public static final int HIDE_RIGHT = 3;
private List<SplitPane> showingTransverseComponents =
new ArrayList<SplitPane>();
private int transverseHiddenSize;
/**
* Initialize the SplitPane with the two component
*
* @param hideableComponent
* @param baseComponent
* @param hideablePosition
* one of {@link #HIDE_TOP}, {@link #HIDE_BOTTOM}, {@link #HIDE_LEFT} or {@link #HIDE_RIGHT}
* @param resizeOnVisibilityChange
* If true, resize the top-level ancestor when the hideable component is shown/hidden
*/
public SplitPane(Component hideableComponent, Component baseComponent, int hideablePosition, boolean resizeOnVisibilityChange) {
super(HIDE_TOP == hideablePosition || HIDE_BOTTOM == hideablePosition ? VERTICAL_SPLIT : HORIZONTAL_SPLIT);
this.resizeOnVisibilityChange = resizeOnVisibilityChange;
this.hideablePosition = hideablePosition;
if (hideableComponent instanceof JComponent) {
((JComponent) hideableComponent).setMinimumSize(new Dimension(0, 0));
}
switch (hideablePosition) {
case HIDE_LEFT:
setLeftComponent(hideableComponent);
setRightComponent(baseComponent);
break;
case HIDE_RIGHT:
setRightComponent(hideableComponent);
setLeftComponent(baseComponent);
break;
case HIDE_TOP:
setTopComponent(hideableComponent);
setBottomComponent(baseComponent);
break;
case HIDE_BOTTOM:
setBottomComponent(hideableComponent);
setTopComponent(baseComponent);
}
setBorder(null);
setResizeWeight(HIDE_LEFT == hideablePosition || HIDE_TOP == hideablePosition ? 0.0 : 1.0);
hideComponent();
}
/** Toggle the visibility of the hideable component */
public void toggleVisibility() {
if (getHideableComponent().isVisible()) {
hideComponent();
}
else {
showComponent();
}
}
/**
* @return the Component that can be shown/hidden
*/
public Component getHideableComponent() {
Component c = null;
switch (hideablePosition) {
case HIDE_LEFT:
c = getLeftComponent();
break;
case HIDE_RIGHT:
c = getRightComponent();
break;
case HIDE_TOP:
c = getTopComponent();
break;
case HIDE_BOTTOM:
c = getBottomComponent();
}
return c;
}
/**
* @return the Component that remains always visible
*/
public Component getBaseComponent() {
Component c = null;
switch (hideablePosition) {
case HIDE_LEFT:
c = getRightComponent();
break;
case HIDE_RIGHT:
c = getLeftComponent();
break;
case HIDE_TOP:
c = getBottomComponent();
break;
case HIDE_BOTTOM:
c = getTopComponent();
}
return c;
}
/**
* @return the size of the base component along the axis of orientation
*/
protected int getBaseComponentSize() {
int size = -1;
switch (getOrientation()) {
case VERTICAL_SPLIT:
size = getBaseComponent().getSize().height;
break;
case HORIZONTAL_SPLIT:
size = getBaseComponent().getSize().width;
}
return size;
}
/**
*
* @return the size of the hideable component along the axis of orientation
*/
protected int getHideableComponentSize() {
int size = -1;
switch (getOrientation()) {
case VERTICAL_SPLIT:
size = getHideableComponent().getSize().height;
break;
case HORIZONTAL_SPLIT:
size = getHideableComponent().getSize().width;
}
return size;
}
/** Hide the hideable component */
public void hideComponent() {
if (getHideableComponent().isVisible()) {
if (resizeOnVisibilityChange) {
Container ancestor = getTopLevelAncestor();
if (ancestor != null) {
switch (hideablePosition) {
case HIDE_LEFT:
case HIDE_RIGHT:
ancestor.setSize(new Dimension(ancestor.getSize().width - getHideableComponent().getSize().width, ancestor.getSize().height - getDividerSize()));
break;
case HIDE_TOP:
case HIDE_BOTTOM:
ancestor.setSize(new Dimension(ancestor.getSize().width, ancestor.getSize().height - getHideableComponent().getSize().height - getDividerSize()));
break;
}
ancestor.validate();
}
}
// Running later causes race conditions in the Module Manager
//Runnable runnable = new Runnable() {
// public void run() {
((BasicSplitPaneUI) getUI()).getDivider().setVisible(false);
getHideableComponent().setVisible(false);
switch (hideablePosition) {
case HIDE_LEFT:
case HIDE_TOP:
setDividerLocation(0.0);
break;
case HIDE_RIGHT:
case HIDE_BOTTOM:
setDividerLocation(1.0);
}
// }
//};
//SwingUtilities.invokeLater(runnable);
SplitPane split = getTransverseSplit();
if (split != null) {
split.hideTransverseComponent(this);
}
}
}
/**
* Set the divider location and/or the top-level ancestor size to be large enough to display the argument
* {@link SplitPane}'s hideable component
*
* @param split
*/
protected void showTransverseComponent(SplitPane split) {
if (showingTransverseComponents.isEmpty()) {
transverseHiddenSize = getBaseComponentSize();
}
showingTransverseComponents.add(split);
resizeBaseComponent();
}
/**
* Set the base component size to be large enough to accomodate all descendant SplitPane's showing components
*/
protected void resizeBaseComponent() {
if (getHideableComponent().isVisible()) {
switch (hideablePosition) {
case HIDE_BOTTOM:
case HIDE_RIGHT:
setDividerLocation(getPreferredBaseComponentSize());
break;
case HIDE_TOP:
setDividerLocation(getSize().height - getPreferredBaseComponentSize());
break;
case HIDE_LEFT:
setDividerLocation(getSize().width - getPreferredBaseComponentSize());
break;
}
}
else if (resizeOnVisibilityChange && getTopLevelAncestor() != null) {
getTopLevelAncestor().setSize(getTransverseSize());
getTopLevelAncestor().validate();
}
}
/**
* @return the preferred size of the base component along the orientation axis
*/
protected int getPreferredBaseComponentSize() {
int size = transverseHiddenSize;
for (SplitPane split : showingTransverseComponents) {
switch (getOrientation()) {
case VERTICAL_SPLIT:
size = Math.max(size,
split.getHideableComponent().getPreferredSize().height);
break;
case HORIZONTAL_SPLIT:
size = Math.max(size,
split.getHideableComponent().getPreferredSize().width);
}
}
return size;
}
/**
* Set the divider location and/or the top-level ancestor size to
* the preferred transverse size.
*
* @param split
*/
protected void hideTransverseComponent(SplitPane split) {
showingTransverseComponents.remove(split);
resizeBaseComponent();
}
/**
* Return the preferred size of the top-level container in the
* direction transverse to this SplitPane's orientation.
* Depends on which ancestors have been shown using
* {@link #showTransverseComponent}.
*/
protected Dimension getTransverseSize() {
Dimension newSize = getTopLevelAncestor().getSize();
switch (getOrientation()) {
case VERTICAL_SPLIT:
newSize.height += getPreferredBaseComponentSize()
- getBaseComponentSize();
break;
case HORIZONTAL_SPLIT:
newSize.width += getPreferredBaseComponentSize()
- getBaseComponentSize();
}
return newSize;
}
/**
* Show the hideable component
*/
public void showComponent() {
if (getHideableComponent().isVisible()) {
return;
}
if (resizeOnVisibilityChange) {
final Container ancestor = getTopLevelAncestor();
if (ancestor == null) {
return;
}
final Rectangle screenBounds = Info.getScreenBounds(ancestor);
final Point ancestorPos = ancestor.getLocation();
final Dimension ancestorSize = ancestor.getSize();
final Dimension prefHSize = getHideableComponent().getPreferredSize();
final Dimension prefBSize = getBaseComponent().getPreferredSize();
double div = 0.0;
int w = 0, h = 0;
switch (getOrientation()) {
case JSplitPane.HORIZONTAL_SPLIT:
w = Math.min(
ancestorSize.width + prefHSize.width,
screenBounds.width - ancestorPos.x
);
h = ancestorSize.height;
div = prefBSize.width/(double)(prefBSize.width + prefHSize.width);
break;
case JSplitPane.VERTICAL_SPLIT:
w = ancestorSize.width;
h = Math.min(
ancestorSize.height + prefHSize.height,
screenBounds.height - ancestorPos.y
);
div = prefBSize.height/(double)(prefBSize.height + prefHSize.height);
break;
}
ancestor.setSize(w, h);
ancestor.validate();
getHideableComponent().setVisible(true);
((BasicSplitPaneUI) getUI()).getDivider().setVisible(true);
final double divPos = div;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setDividerLocation(divPos);
}
});
}
else {
getHideableComponent().setVisible(true);
((BasicSplitPaneUI) getUI()).getDivider().setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setDividerLocation(getPreferredDividerLocation());
}
});
final SplitPane split = getTransverseSplit();
if (split != null) {
split.showTransverseComponent(ComponentSplitter.SplitPane.this);
}
}
}
/**
* @return the preferred location of the divider when the hideable component is visible
*/
protected int getPreferredDividerLocation() {
int loc = 0;
switch (hideablePosition) {
case HIDE_LEFT:
loc = getInsets().left + getLeftComponent().getPreferredSize().width;
break;
case HIDE_RIGHT:
loc = getSize().width - getInsets().right - getDividerSize() - getRightComponent().getPreferredSize().width;
break;
case HIDE_TOP:
loc = getInsets().top + getLeftComponent().getPreferredSize().height;
break;
case HIDE_BOTTOM:
loc = getSize().height - getInsets().bottom - getDividerSize() - getRightComponent().getPreferredSize().height;
}
return loc;
}
/**
* Return the first SplitPane ancestor with a different orientation from this SplitPane
*
* @return
*/
public SplitPane getTransverseSplit() {
SplitPane split = null;
for (Component c = getParent(); c != null; c = c.getParent()) {
if (c instanceof SplitPane) {
SplitPane p = (SplitPane) c;
if (p.getOrientation() != getOrientation() && SwingUtilities.isDescendingFrom(this, p.getBaseComponent())) {
split = p;
break;
}
}
}
return split;
}
/**
* If the hideable component is not visible, use the base component's preferred size
*/
public Dimension getPreferredSize() {
Dimension d = null;
if (getHideableComponent() == null || getHideableComponent().isVisible()) {
d = super.getPreferredSize();
}
else {
switch (hideablePosition) {
case HIDE_LEFT:
d = getRightComponent().getPreferredSize();
break;
case HIDE_RIGHT:
d = getLeftComponent().getPreferredSize();
break;
case HIDE_TOP:
d = getBottomComponent().getPreferredSize();
break;
case HIDE_BOTTOM:
d = getTopComponent().getPreferredSize();
}
}
return d;
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JTextField status = new JTextField("status");
status.setEditable(false);
f.setLayout(new BorderLayout());
Box box = Box.createVerticalBox();
box.add(status);
JPanel main = new JPanel(new BorderLayout());
f.add(main, BorderLayout.CENTER);
JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
toolbar.setAlignmentX(0.0F);
box.add(toolbar);
f.add(box, BorderLayout.NORTH);
final JLabel smallLeft = new JLabel(new ImageIcon("small.gif"));
final JLabel smallRight = new JLabel(new ImageIcon("smallRight.gif"));
final JLabel large = new JLabel(new ImageIcon("large.jpg"));
JPanel text = new JPanel();
text.setLayout(new BoxLayout(text, BoxLayout.Y_AXIS));
text.add(new ScrollPane(new JTextArea(15, 60)));
JTextField input = new JTextField(60);
input.setMaximumSize(new Dimension(input.getMaximumSize().width, input.getPreferredSize().height));
text.add(input);
ComponentSplitter splitter = new ComponentSplitter();
final SplitPane splitRight = splitter.splitRight(main, smallRight, false);
final SplitPane splitLeft = splitter.splitLeft(main, smallLeft, false);
final SplitPane splitBottom = splitter.splitBottom(splitter.getSplitAncestor(main, -1), new ScrollPane(large), true);
splitBottom.setResizeWeight(0.0);
main.add(text, BorderLayout.CENTER);
toolbar.add(new AbstractAction("Left") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
splitLeft.toggleVisibility();
}
});
toolbar.add(new AbstractAction("Right") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
splitRight.toggleVisibility();
}
});
toolbar.add(new AbstractAction("Bottom") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
splitBottom.toggleVisibility();
}
});
f.pack();
f.setVisible(true);
}
}