/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.swing;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class CollapsiblePane extends JPanel {
private static final Icon collapsedIcon = createHeaderIcon(true);
private static final Icon expandedIcon = createHeaderIcon(false);
private static final int iconSize = 11;
private Component contentPane;
private boolean collapsed;
private boolean fixedSize;
private JLabel titleLabel;
private JLabel iconLabel;
private JPanel headerRow;
private JPanel contentRow;
public static JPanel createCollapsiblePaneContainer() {
return new CollapsiblePaneContainer();
}
/**
* Constructs a new <code>CollapsiblePane</code>.
*/
public CollapsiblePane(String title, Component contentPane, boolean collapsed, boolean fixedSize) {
super();
this.titleLabel = new JLabel(title);
this.collapsed = collapsed;
this.contentPane = contentPane;
this.fixedSize = fixedSize;
iconLabel = new JLabel();
iconLabel.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
setCollapsed(!isCollapsed());
updateState();
}
});
titleLabel = new JLabel(title);
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.ITALIC));
JPanel iconDummy = new JPanel();
iconDummy.setPreferredSize(new Dimension(2 * iconSize, iconSize));
headerRow = new JPanel(new BorderLayout(2, 0));
headerRow.add(iconLabel, BorderLayout.WEST);
headerRow.add(titleLabel, BorderLayout.CENTER);
contentRow = new JPanel(new BorderLayout(2, 0));
contentRow.add(iconDummy, BorderLayout.WEST);
if (contentPane != null) {
contentRow.add(contentPane, BorderLayout.CENTER);
}
setLayout(new BorderLayout(2, 2));
add(headerRow, BorderLayout.NORTH);
add(contentRow, BorderLayout.CENTER);
updateState();
}
public Component getContentPane() {
return contentPane;
}
public void setContentPane(Component contentPane) {
Component oldValue = this.contentPane;
this.contentPane = contentPane;
if (oldValue != getContentPane()) {
if (oldValue != null) {
contentRow.remove(oldValue);
}
if (getContentPane() != null) {
contentRow.add(getContentPane(), BorderLayout.CENTER);
}
updateState();
firePropertyChange("contentPane", oldValue, getContentPane());
}
}
public boolean isFixedSize() {
return fixedSize;
}
public void setFixedSize(boolean fixedSize) {
boolean oldValue = isFixedSize();
this.fixedSize = fixedSize;
if (oldValue != isFixedSize()) {
revalidateParent();
firePropertyChange("fixedSize", oldValue, isFixedSize());
}
}
public boolean isCollapsed() {
return collapsed;
}
public void setCollapsed(boolean collapsed) {
boolean oldValue = isCollapsed();
this.collapsed = collapsed;
if (oldValue != isCollapsed()) {
updateState();
firePropertyChange("collapsed", oldValue, isCollapsed());
}
}
private void updateState() {
updateHeaderRow();
updateContentRow();
}
private void updateHeaderRow() {
iconLabel.setIcon(collapsed ? collapsedIcon : expandedIcon);
}
private void updateContentRow() {
if (isCollapsed()) {
remove(contentRow);
} else {
add(contentRow, BorderLayout.CENTER);
}
revalidateParent();
}
private void revalidateParent() {
Container parent = getParent();
if (parent != null) {
parent.invalidate();
parent.validate();
}
}
public String getTitle() {
return titleLabel.getText();
}
public void setTitle(String title) {
String oldValue = getTitle();
this.titleLabel.setText(title);
firePropertyChange("title", oldValue, getTitle());
repaint();
}
private static Icon createHeaderIcon(boolean collapsed) {
BufferedImage bi = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
int n = iconSize - 1;
int n2 = n / 2;
g.setColor(Color.WHITE);
g.fillRect(0, 0, n, n);
g.setColor(Color.GRAY);
g.drawRect(0, 0, n, n);
if (collapsed) {
g.drawLine(n2, 2, n2, n - 2);
}
g.drawLine(2, n2, n - 2, n2);
g.dispose();
return new ImageIcon(bi);
}
private int getPreferredHeight() {
return getPreferredSize().height;
}
private int getPreferredHeaderHeight() {
return headerRow.getPreferredSize().height;
}
private static class CollapsiblePaneContainer extends JPanel {
public CollapsiblePaneContainer() {
super(null);
}
@Override
protected void addImpl(Component component, Object constraints, int index) {
if (!(component instanceof CollapsiblePane)) {
throw new IllegalArgumentException("component not a " + CollapsiblePane.class.getName());
}
super.addImpl(component, constraints, index);
}
@Override
public void doLayout() {
Component[] components = getComponents();
ArrayList<Float> weights = new ArrayList<Float>();
int collapsedHeightSum = 0;
int expandedHeightSum = 0;
for (Component component : components) {
CollapsiblePane collapsiblePane = (CollapsiblePane) component;
int height = component.getPreferredSize().height;
if (collapsiblePane.isCollapsed() || collapsiblePane.isFixedSize()) {
collapsedHeightSum += height;
} else {
expandedHeightSum += height;
weights.add((float) height);
}
}
for (int i = 0; i < weights.size(); i++) {
weights.set(i, weights.get(i) / (float) expandedHeightSum);
}
Insets insets = getInsets();
int availableWidth = getWidth() - (insets.left + insets.right);
int availableHeight = getHeight() - (insets.top + insets.bottom);
int distributableSpace = availableHeight - collapsedHeightSum;
int x = insets.left;
int y = insets.top;
int currentHeight;
int i = 0;
for (Component component : components) {
CollapsiblePane collapsiblePane = (CollapsiblePane) component;
if (collapsiblePane.isCollapsed() || collapsiblePane.isFixedSize()) {
currentHeight = collapsiblePane.getPreferredHeight();
} else {
currentHeight = Math.round(weights.get(i++) * distributableSpace);
int headerHeight = collapsiblePane.getPreferredHeaderHeight();
if (currentHeight < headerHeight) {
currentHeight = headerHeight;
}
}
component.setBounds(x, y, availableWidth, currentHeight);
y += currentHeight;
}
}
}
}