/*
* FilterBar.java
* Copyright 2010 Connor Petty <cpmeister@users.sourceforge.net>
*
* 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 2.1 of the License, or (at your option) any later version.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on May 15, 2010, 5:41:26 PM
*/
package pcgen.gui2.filter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SizeRequirements;
import javax.swing.UIManager;
import javax.swing.border.Border;
import org.apache.commons.lang3.ArrayUtils;
/**
* This class represents the highest level DisplayableFilter in the filter hierarchy. A FilterBar
* is a filter which contains a set of other DisplayableFilters. At the bottom of a FilterBar is a
* region of space with an arrow at the center. When this is clicked all of the children filters will
* be hidden from view.
* @author Connor Petty <cpmeister@users.sourceforge.net>
*/
public class FilterBar<C, E> extends JPanel implements DisplayableFilter<C, E>
{
private JPanel filterPanel = new JPanel(new FilterLayout());
private List<DisplayableFilter<? super C, ? super E>> filters = new ArrayList<>();
private FilterHandler filterHandler;
public FilterBar()
{
this(true);
}
public FilterBar(boolean collapsable)
{
setLayout(new BorderLayout());
add(filterPanel, BorderLayout.CENTER);
final ArrowButton arrowbutton = new ArrowButton();
arrowbutton.addMouseListener(
new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
boolean closed = !arrowbutton.isOpen();
arrowbutton.setOpen(closed);
filterPanel.setVisible(closed);
}
});
if (collapsable)
{
add(arrowbutton, BorderLayout.SOUTH);
}
}
public void addDisplayableFilter(DisplayableFilter<? super C, ? super E> filter)
{
filterPanel.add(filter.getFilterComponent());
filters.add(filter);
filter.setFilterHandler(filterHandler);
}
public void removeDisplayableFilter(DisplayableFilter<C, E> filter)
{
filterPanel.remove(filter.getFilterComponent());
filters.remove(filter);
filter.setFilterHandler(null);
}
@Override
public Component getFilterComponent()
{
return this;
}
@Override
public void setFilterHandler(FilterHandler handler)
{
this.filterHandler = handler;
for (DisplayableFilter<? super C, ? super E> displayableFilter : filters)
{
displayableFilter.setFilterHandler(handler);
}
}
@Override
public boolean accept(C context, E element)
{
for (DisplayableFilter<? super C, ? super E> displayableFilter : filters)
{
if (!displayableFilter.accept(context, element))
{
return false;
}
}
return true;
}
private static class ArrowButton extends JButton
{
private boolean entered = false;
private boolean open = true;
public ArrowButton()
{
setMinimumSize(new Dimension(6, 6));
setPreferredSize(new Dimension(6, 6));
setFocusPainted(false);
setBorderPainted(false);
setRequestFocusEnabled(false);
addMouseListener(
new MouseAdapter()
{
@Override
public void mouseEntered(MouseEvent e)
{
entered = true;
repaint();
}
@Override
public void mouseExited(MouseEvent e)
{
entered = false;
repaint();
}
});
}
@Override
public void setBorder(Border border)
{
}
private static final int[] yup =
{
1,
4,
4
};
private static final int[] ydown =
{
4,
1,
1
};
@Override
public void paint(Graphics g)
{
Color b;
Color f;
if (entered)
{
b = UIManager.getColor("controlDkShadow");
f = Color.BLACK;
}
else
{
b = UIManager.getColor("control");
f = UIManager.getColor("controlDkShadow");
}
g.setColor(b);
g.fillRect(0, 0, getWidth(), getHeight());
int center = getWidth() / 2;
int[] xs =
{
center,
center - 3,
center + 3
};
int[] ys;
if (open)
{
ys = yup;
}
else
{
ys = ydown;
}
g.setColor(f);
g.drawPolygon(xs, ys, 3);
g.fillPolygon(xs, ys, 3);
}
public void setOpen(boolean open)
{
this.open = open;
}
public boolean isOpen()
{
return open;
}
}
/*
* this layout functions like left to right FlowLayout with the exception
* that it treats the Container's width as absolute and will change the
* height of the container to fit container's children.
*/
private static class FilterLayout extends FlowLayout
{
public FilterLayout()
{
super(FlowLayout.LEFT, 5, 2);
}
@Override
public void layoutContainer(Container target)
{
synchronized (target.getTreeLock())
{
Insets insets = target.getInsets();
int maxwidth = target.getWidth() - (insets.left + insets.right + getHgap() * 2);
int nmembers = target.getComponentCount();
int x = 0, y = insets.top + getVgap();
int rowh = 0, start = 0;
boolean ltr = target.getComponentOrientation().isLeftToRight();
SizeRequirements[] xChildren = new SizeRequirements[nmembers];
SizeRequirements[] yChildren = new SizeRequirements[nmembers];
for (int i = 0; i < nmembers; i++)
{
Component c = target.getComponent(i);
if (!c.isVisible())
{
xChildren[i] = new SizeRequirements(0, 0, 0, c.getAlignmentX());
yChildren[i] = new SizeRequirements(0, 0, 0, c.getAlignmentY());
}
else
{
Dimension min = c.getMinimumSize();
Dimension typ = c.getPreferredSize();
Dimension max = c.getMaximumSize();
xChildren[i] = new SizeRequirements(min.width, typ.width,
max.width,
c.getAlignmentX());
yChildren[i] = new SizeRequirements(min.height, typ.height,
max.height,
c.getAlignmentY());
if ((x == 0) || ((x + typ.width) <= maxwidth))
{
if (x > 0)
{
x += getHgap();
}
x += typ.width;
rowh = Math.max(rowh, typ.height);
}
else
{
layoutComponents(target, insets.left + getHgap(), y,
maxwidth, rowh, xChildren, yChildren, start, i, ltr);
x = typ.width;
y += getVgap() + rowh;
rowh = typ.height;
start = i;
}
}
}
layoutComponents(target, insets.left + getHgap(), y,
maxwidth, rowh, xChildren, yChildren, start, nmembers, ltr);
}
}
private void layoutComponents(Container target, int xOffset, int yOffset, int maxwidth, int rowheight,
SizeRequirements[] xChildren, SizeRequirements[] yChildren,
int start, int end, boolean ltr)
{
SizeRequirements[] children = (SizeRequirements[]) ArrayUtils.subarray(xChildren, start, end);
int[] xOffsets = new int[children.length];
int[] xSpans = new int[children.length];
SizeRequirements.calculateTiledPositions(maxwidth, null, children, xOffsets, xSpans, ltr);
children = (SizeRequirements[]) ArrayUtils.subarray(yChildren, start, end);
int[] yOffsets = new int[children.length];
int[] ySpans = new int[children.length];
SizeRequirements total = new SizeRequirements(rowheight, rowheight, rowheight, 0.5f);
SizeRequirements.calculateAlignedPositions(rowheight, total, children, yOffsets, ySpans, ltr);
Insets in = target.getInsets();
for (int i = 0; i < children.length; i++)
{
Component c = target.getComponent(i + start);
c.setBounds((int) Math.min((long) xOffset + (long) xOffsets[i], Integer.MAX_VALUE),
(int) Math.min((long) yOffset + (long) yOffsets[i], Integer.MAX_VALUE),
xSpans[i], ySpans[i]);
}
}
@Override
public Dimension preferredLayoutSize(Container target)
{
synchronized (target.getTreeLock())
{
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
Insets insets = target.getInsets();
// Provide a default if the panel has not been displayed yet (i.e. in a dialog)
int targetWidth = target.getWidth() == 0? 400 : target.getWidth();
int maxwidth = targetWidth - (insets.left + insets.right +
getHgap() * 2);
int width = 0;
int height = 0;
int component = 0;
for (int i = 0; i < nmembers; i++, component++)
{
Component m = target.getComponent(i);
if (m.isVisible())
{
Dimension d = m.getPreferredSize();
if (component > 0)
{
if (width + d.width > maxwidth)
{
dim.width = Math.max(dim.width, width);
dim.height += height + getVgap();
width = 0;
height = 0;
component = 0;
}
width += getHgap();
}
height = Math.max(height, d.height);
width += d.width;
}
}
dim.width = Math.max(dim.width, width);
dim.height += height;
dim.width += insets.left + insets.right + getHgap() * 2;
dim.height += insets.top + insets.bottom + getVgap() * 2;
return dim;
}
}
@Override
public Dimension minimumLayoutSize(Container target)
{
synchronized (target.getTreeLock())
{
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
Insets insets = target.getInsets();
int maxwidth = target.getWidth() - (insets.left + insets.right +
getHgap() * 2);
int width = 0;
int height = 0;
int component = 0;
for (int i = 0; i < nmembers; i++, component++)
{
Component m = target.getComponent(i);
if (m.isVisible())
{
Dimension d = m.getMinimumSize();
if (component > 0)
{
if (width + d.width > maxwidth)
{
dim.width = Math.max(dim.width, width);
dim.height += height + getVgap();
width = 0;
height = 0;
component = 0;
}
width += getHgap();
}
height = Math.max(height, d.height);
width += d.width;
}
}
dim.width = Math.max(dim.width, width);
dim.height += height;
dim.width += insets.left + insets.right + getHgap() * 2;
dim.height += insets.top + insets.bottom + getVgap() * 2;
return dim;
}
}
}
}