package prefuse.util.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JToggleButton;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* Swing component representing a group of toggle buttons -- either checkboxes
* or radio buttons. This class uses a ListModel and ListSelectionModel to
* represent the selection state of the buttons.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class JToggleGroup extends JPanel {
public static final int CHECKBOX = 0;
public static final int RADIO = 1;
protected final int m_type;
protected int m_margin = 0;
protected int m_spacing = 0;
protected int m_axis = BoxLayout.X_AXIS;
protected ListModel m_data;
protected ListSelectionModel m_sel;
protected String[] m_labels;
protected ButtonGroup m_group;
private Listener m_lstnr;
/**
* Create a new JToggleGroup.
* @param type the toggle button type to use, one of {@link #CHECKBOX}
* or {@link #RADIO}
* @param data the list data that should populate the toggle group
*/
public JToggleGroup(int type, Object[] data) {
this(type, new DefaultListModel(),
new DefaultListSelectionModel());
DefaultListModel model = (DefaultListModel)m_data;
for ( int i=0; i<data.length; ++i ) {
model.addElement(data[i]);
}
initUI();
}
/**
* Create a new JToggleGroup.
* @param type the toggle button type to use, one of {@link #CHECKBOX}
* or {@link #RADIO}
* @param data the list model data backing the toggle group
*/
public JToggleGroup(int type, ListModel data) {
this(type, data, new DefaultListSelectionModel());
}
/**
* Create a new JToggleGroup.
* @param type the toggle button type to use, one of {@link #CHECKBOX}
* or {@link #RADIO}
* @param data the list model data backing the toggle group
* @param selection the list selection model to use to monitor selection
* changes to the various toggle buttons.
*/
public JToggleGroup(int type, ListModel data, ListSelectionModel selection)
{
setLayout(new BoxLayout(this, m_axis));
m_type = type;
m_data = data;
m_sel = selection;
if ( m_type == RADIO ) {
m_group = new ButtonGroup();
}
m_lstnr = new Listener();
m_sel.addListSelectionListener(m_lstnr);
if ( m_data.getSize() > 0 )
initUI();
setFocusable(false);
}
// ------------------------------------------------------------------------
/**
* Initialize the UI.
*/
protected void initUI() {
// unregister all active components
for ( int i=0; i<getComponentCount(); ++i ) {
Component c = getComponent(i);
if ( !(c instanceof JToggleButton) ) continue;
JToggleButton tb = (JToggleButton)c;
tb.removeActionListener(m_lstnr);
if ( m_group != null )
m_group.remove(tb);
}
// clear this container and add new components
removeAll();
UILib.addStrut(this, m_axis, m_margin);
for ( int i=0; i<m_data.getSize(); ++i ) {
if ( i>0 ) UILib.addStrut(this, m_axis, m_spacing);
Object data = m_data.getElementAt(i);
String label = m_labels==null ? data.toString() : m_labels[i];
JToggleButton tb = null;
if ( m_type == CHECKBOX ) {
tb = new JCheckBox(label);
} else {
tb = new JRadioButton(label);
m_group.add(tb);
}
tb.putClientProperty("idx", new Integer(i));
tb.addActionListener(m_lstnr);
add(tb);
}
UILib.addStrut(this, m_axis, m_margin);
// make sure the selection status shows up
m_lstnr.valueChanged(null);
}
// ------------------------------------------------------------------------
/**
* Set the Box axis type used to orient the toggle group component.
* @param axis the axis type, one of
* {@link javax.swing.BoxLayout#X_AXIS},
* {@link javax.swing.BoxLayout#Y_AXIS},
* {@link javax.swing.BoxLayout#LINE_AXIS}, or
* {@link javax.swing.BoxLayout#PAGE_AXIS}.
*/
public void setAxisType(int axis) {
this.setLayout(new BoxLayout(this, axis));
m_axis = axis;
initUI();
}
/**
* Get the Box axis type used to orient the toggle group component.
* @return the axis type, one of
* {@link javax.swing.BoxLayout#X_AXIS},
* {@link javax.swing.BoxLayout#Y_AXIS},
* {@link javax.swing.BoxLayout#LINE_AXIS}, or
* {@link javax.swing.BoxLayout#PAGE_AXIS}.
*/
public int getAxisType() {
return m_axis;
}
/**
* Set the margin, in pixels, to use at the ends of the JToggleGroup.
* @param margin the margin in pixels
*/
public void setMargin(int margin) {
if ( margin < 0 )
throw new IllegalArgumentException("Margin is less than zero.");
m_margin = margin;
initUI();
}
/**
* Get the margin, in pixels, used at the ends of the JToggleGroup.
* @return the margin in pixels
*/
public int getMargin() {
return m_margin;
}
/**
* Set the spacing between toggle group components.
* @param spacing the spacing, in pixels, to use between components
*/
public void setSpacing(int spacing) {
if ( spacing < 0 )
throw new IllegalArgumentException("Spacing is less than zero.");
m_spacing = spacing;
initUI();
}
/**
* Get the spacing between toggle group components.
* @return the spacing, in pixels, to use between components
*/
public int getSpacing() {
return m_spacing;
}
/**
* Set the ListModel backing this component.
* @return the list model to use
*/
public void setModel(ListModel model) {
m_data = model;
initUI();
}
/**
* Get the ListModel backing this component.
* @return the list model
*/
public ListModel getModel() {
return m_data;
}
/**
* Set the ListSelectionModel used by this component.
* @param sel the list selection model to use
*/
public void setSelectionModel(ListSelectionModel sel) {
m_sel.removeListSelectionListener(m_lstnr);
m_sel = sel;
m_sel.addListSelectionListener(m_lstnr);
m_lstnr.valueChanged(null);
}
/**
* Get the ListSelectionModel used by this component.
* @return the list selection model to use
*/
public ListSelectionModel getSelectionModel() {
return m_sel;
}
/**
* Set the labels to use for the Objects contained in the list model.
* @param labels the display labels to use in the interface component
*/
public void setLabels(String[] labels) {
if ( labels.length < m_data.getSize() ) {
throw new IllegalArgumentException("Alias array is too short");
}
m_labels = labels;
initUI();
}
/**
* Set the background color of this toggle group.
* @see java.awt.Component#setBackground(java.awt.Color)
*/
public void setBackground(Color background) {
for ( int i=0; i<getComponentCount(); ++i ) {
getComponent(i).setBackground(background);
}
}
/**
* Set the foreground color of this toggle group.
* @see java.awt.Component#setBackground(java.awt.Color)
*/
public void setForeground(Color foreground) {
for ( int i=0; i<getComponentCount(); ++i ) {
getComponent(i).setForeground(foreground);
}
}
/**
* Set the font used by this toggle group.
* @see java.awt.Component#setFont(java.awt.Font)
*/
public void setFont(Font font) {
for ( int i=0; i<getComponentCount(); ++i ) {
getComponent(i).setFont(font);
}
}
/**
* Sets if the various toggle buttons can receive the keyboard focus.
* @param b true to set toggle buttons keyboard accessible, false to
* set them unaccessible.
*/
public void setGroupFocusable(boolean b) {
for ( int i=0; i<getComponentCount(); ++i ) {
Component c = getComponent(i);
if ( c instanceof JToggleButton )
c.setFocusable(b);
}
}
// ------------------------------------------------------------------------
private class Listener implements ListSelectionListener, ActionListener {
private boolean m_ignore = false;
public void valueChanged(ListSelectionEvent neverUsed) {
if ( m_ignore ) { return; } else { m_ignore = true; }
if ( m_type == RADIO ) {
int idx = m_sel.getMinSelectionIndex();
boolean sel = (idx >= 0);
JToggleButton tb = null;
for ( int i=0, j=0; i<getComponentCount(); ++i ) {
Component c = getComponent(i);
if ( c instanceof JToggleButton ) {
tb = (JToggleButton)c;
if ( (!sel && tb.isSelected()) || (sel && idx==j) )
break;
++j;
}
}
tb.setSelected(sel);
} else {
for ( int i=0, j=0; i<getComponentCount(); ++i ) {
Component c = getComponent(i);
if ( c instanceof JCheckBox ) {
((JCheckBox)c).setSelected(m_sel.isSelectedIndex(j++));
}
}
}
m_ignore = false;
}
public void actionPerformed(ActionEvent e) {
if ( m_ignore ) { return; } else { m_ignore = true; }
JToggleButton tb = (JToggleButton)e.getSource();
boolean sel = tb.isSelected();
int idx = ((Integer)tb.getClientProperty("idx")).intValue();
if ( m_type == RADIO ) {
m_sel.setSelectionInterval(idx,idx);
} else if ( sel ) {
m_sel.addSelectionInterval(idx,idx);
} else {
m_sel.removeSelectionInterval(idx,idx);
}
m_ignore = false;
}
}
} // end of class JToggleGroup