package org.japura.gui; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.JPanel; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.UIManager; /** * Copyright (C) 2008-2012 Carlos Eduardo Leite de Andrade * <P> * This library 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. * <P> * 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. * <P> * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <A * HREF="www.gnu.org/licenses/">www.gnu.org/licenses/</A> * <P> * For more information, contact: <A HREF="www.japura.org">www.japura.org</A> * <P> * * @author Carlos Eduardo Leite de Andrade */ public class CollapsibleRootPanel extends JPanel implements Scrollable{ private static final long serialVersionUID = 1L; public static final Mode FILL = Mode.FILL; public static final Mode SCROLL_BAR = Mode.SCROLL_BAR; private Mode mode; private List<CollapsiblePanel> panels; private int maxWidth; private int maxHeight; private Insets margin; private int panelGap = 10; public CollapsibleRootPanel() { this(Mode.SCROLL_BAR); } public CollapsibleRootPanel(Mode mode) { this.mode = mode; setMargin(new Insets(10, 10, 10, 10)); setBackground(Color.WHITE); panels = new ArrayList<CollapsiblePanel>(); super.setLayout(null); if (mode.equals(FILL)) { addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { adjustEndHeights(); } }); } } @Override public Dimension getMinimumSize() { return getPreferredSize(); } @Override public Dimension getPreferredSize() { Dimension dim = null; if (isPreferredSizeSet()) { dim = super.getPreferredSize(); } else { dim = new Dimension(); for (int i = 0; i < panels.size(); i++) { CollapsiblePanel cp = panels.get(i); if (cp.isVisible()) { Dimension cpDim = cp.getPreferredSize(); dim.width = Math.max(dim.width, cpDim.width); dim.height += cpDim.height; if (i < panels.size() - 1) { dim.height += panelGap; } } } Insets insets = getInsets(); dim.width += insets.left + insets.right; dim.height += insets.bottom + insets.top; dim.width += margin.left + margin.right; dim.height += margin.top + margin.bottom; } if (!(getParent() instanceof JViewport)) { dim.width += UIManager.getInt("ScrollBar.width"); applyMax(dim); } return dim; } /** * Applies the maximum height and width to the dimension. * * @param dim * the dimension to be limited */ private void applyMax(Dimension dim) { if (getMaxHeight() > 0) { dim.height = Math.min(dim.height, getMaxHeight()); } if (getMaxWidth() > 0) { dim.width = Math.min(dim.width, getMaxWidth()); } } @Override public void doLayout() { if (mode.equals(Mode.FILL)) { adjustCalculatedHeights(); } int x = margin.left; int y = margin.top; for (int i = 0; i < panels.size(); i++) { CollapsiblePanel cp = panels.get(i); if (cp.isVisible()) { int height = cp.getCalculatedHeight(); cp.setBounds(x, y, getWidth() - (margin.left + margin.right), height); y += height; if (i < panels.size() - 1) { y += panelGap; } } } } @Override public void setLayout(LayoutManager mgr) {} @Override public Component add(Component comp, int index) { return null; } @Override public void add(Component comp, Object constraints, int index) {} @Override public void add(Component comp, Object constraints) {} @Override public Component add(Component comp) { return null; } @Override public Component add(String name, Component comp) { return null; } /** * Adds a {@link CollapsiblePanel} to the container. The order of the * components inside the container is from top to bottom. * * @param collapsiblePanel * the {@link CollapsiblePanel} to be added * @param weight * the weight for the {@link CollapsiblePanel} in a fill mode. * <P> * Value 0 disables the fill, the height's component is defined for * his preferred size. */ public void add(CollapsiblePanel collapsiblePanel, int weight) { if (panels.contains(collapsiblePanel) == false) { collapsiblePanel.weightY = Math.max(0, weight); panels.add(collapsiblePanel); super.add(collapsiblePanel); } } public void remove(CollapsiblePanel collapsiblePanel) { panels.remove(collapsiblePanel); super.remove(collapsiblePanel); } @Override public void removeAll() { panels.clear(); super.removeAll(); } void adjustEndHeights() { double total = 0; int visiblePanels = 0; for (CollapsiblePanel cp : panels) { if (cp.isVisible()) { visiblePanels++; if (cp.isExpanded() || cp.isExpanding()) { total += cp.weightY; } } } int height = getHeight(); height -= (margin.top + margin.bottom); height -= (Math.max(0, visiblePanels - 1) * panelGap); for (CollapsiblePanel cp : panels) { if (cp.isVisible()) { if (cp.isCollapsed()) { if (cp.weightY > 0) { height -= cp.getCalculatedHeight(); } else { height -= cp.getPreferredSize().height; } } else if (cp.isCollapsing()) { height -= cp.getCalculatedHeight(); } else if (cp.isCollapsed() || cp.weightY == 0) { height -= cp.getPreferredSize().height; } } } for (CollapsiblePanel cp : panels) { if (cp.isVisible() && (cp.isExpanding())) { if (cp.weightY > 0) { double per = (100D * cp.weightY) / total; double endHeight = (height * per) / 100D; cp.setEndHeight((int) endHeight); } else { cp.setEndHeight(cp.getPreferredSize(false).height); } } } } private void adjustCalculatedHeights() { double total = 0; int visiblePanels = 0; for (CollapsiblePanel cp : panels) { if (cp.isVisible()) { visiblePanels++; if (cp.isExpanded()) { total += cp.weightY; } } } int height = getHeight(); height -= (margin.top + margin.bottom); height -= (Math.max(0, visiblePanels - 1) * panelGap); for (CollapsiblePanel cp : panels) { if (cp.isVisible()) { if (cp.weightY > 0) { if (cp.isExpanded() == false) height -= cp.getCalculatedHeight(); } else { if (cp.isCollapsed() || cp.isExpanded()) { height -= cp.getPreferredSize().height; } else { height -= cp.getCalculatedHeight(); } } } } for (CollapsiblePanel cp : panels) { if (cp.isVisible()) { if (cp.isExpanded()) { if (cp.weightY > 0) { double per = (100D * cp.weightY) / total; double endHeight = (height * per) / 100D; cp.setCalculatedHeight((int) endHeight); } else { cp.setCalculatedHeight(cp.getPreferredSize(false).height); } } } } } /** * Adds a {@link CollapsiblePanel} to the container. The order of the * components inside the container is from top to bottom. * <P> * The {@link CollapsiblePanel} is added with weight value 1. * * @param collapsiblePanel * the {@link CollapsiblePanel} to be added */ public void add(CollapsiblePanel collapsiblePanel) { add(collapsiblePanel, 1); } /** * Expands all panels inside the container. * * @see #collapseAll() */ public void expandAll() { for (CollapsiblePanel cp : panels) { cp.expand(); } } /** * Collapses all panels inside the container. * * @see #expandAll() */ public void collapseAll() { for (CollapsiblePanel cp : panels) { cp.collapse(); } } public void setLockAllEnabled(boolean enabled) { for (CollapsiblePanel cp : panels) { cp.setLockEnabled(enabled); } } @Override public Dimension getPreferredScrollableViewportSize() { Dimension dim = getPreferredSize(); dim.width += UIManager.getInt("ScrollBar.width"); applyMax(dim); return dim; } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 15; } @Override public boolean getScrollableTracksViewportHeight() { if (getParent() instanceof JViewport) { JViewport vp = (JViewport) getParent(); if (vp.getHeight() > getPreferredSize().height) { return true; } } return false; } @Override public boolean getScrollableTracksViewportWidth() { return true; } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 15; } public int getMaxWidth() { return maxWidth; } public void setMaxWidth(int maxWidth) { this.maxWidth = Math.max(maxWidth, 0); } public int getMaxHeight() { return maxHeight; } public void setMaxHeight(int maxHeight) { this.maxHeight = Math.max(maxHeight, 0); } public Mode getMode() { return mode; } public Insets getMargin() { return margin; } /** * Defines the margin. * * @param margin * {@link Insets} */ public final void setMargin(Insets margin) { if (margin == null) { margin = new Insets(0, 0, 0, 0); } else { margin.bottom = Math.max(margin.bottom, 0); margin.left = Math.max(margin.left, 0); margin.top = Math.max(margin.top, 0); margin.right = Math.max(margin.right, 0); } this.margin = margin; } public int getPanelGap() { return panelGap; } /** * Defines the gap between the panels. * * @param panelGap * the value for the gap */ public void setPanelGap(int panelGap) { this.panelGap = Math.max(0, panelGap); } public List<CollapsiblePanel> getCollapsiblePanels() { return Collections.unmodifiableList(panels); } public static enum Mode { SCROLL_BAR, FILL; } }