From da6439a7bb4dd82b3556eae2057c747a49d5629b Mon Sep 17 00:00:00 2001 From: mdb Date: Sun, 10 Mar 2002 05:10:37 +0000 Subject: [PATCH] Implemented a class for selecting among a mutually exclusive set of buttons. git-svn-id: https://samskivert.googlecode.com/svn/trunk@644 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../com/samskivert/swing/ComboButtonBox.java | 383 ++++++++++++++++++ .../samskivert/swing/TestComboButtonBox.java | 47 +++ 2 files changed, 430 insertions(+) create mode 100644 projects/samskivert/src/java/com/samskivert/swing/ComboButtonBox.java create mode 100644 projects/samskivert/tests/src/java/com/samskivert/swing/TestComboButtonBox.java diff --git a/projects/samskivert/src/java/com/samskivert/swing/ComboButtonBox.java b/projects/samskivert/src/java/com/samskivert/swing/ComboButtonBox.java new file mode 100644 index 00000000..0661ccde --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/swing/ComboButtonBox.java @@ -0,0 +1,383 @@ +// +// $Id: ComboButtonBox.java,v 1.1 2002/03/10 05:10:37 mdb Exp $ + +package com.samskivert.swing; + +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.border.Border; +import javax.swing.BorderFactory; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; + +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import com.samskivert.Log; + +/** + * Used to display a horizontal or vertical array of buttons, out of which + * only one is selectable at a time (which will be represented by + * rendering it with an indented border, whereas the other buttons will + * render with an extruded border. + */ +public class ComboButtonBox extends JPanel + implements SwingConstants, ListDataListener, MouseListener +{ + /** + * Constructs a button box with the specified orientation (either + * {@link #HORIZONTAL} or {@link #VERTICAL}. + */ + public ComboButtonBox (int orientation) + { + this(orientation, new DefaultComboBoxModel()); + } + + /** + * Constructs a button box with the specified orientation (either + * {@link #HORIZONTAL} or {@link #VERTICAL}. The supplied model will + * be used to populate the buttons (see {@link #setModel} for more + * details). + */ + public ComboButtonBox (int orientation, ComboBoxModel model) + { + // set up our layout + setOrientation(orientation); + + // set up our contents + setModel(model); + } + + /** + * Sets the orientation of the box (either {@link #HORIZONTAL} or + * {@link #VERTICAL}. + */ + public void setOrientation (int orientation) + { + GroupLayout gl = (orientation == HORIZONTAL) ? + (GroupLayout)new HGroupLayout() : new VGroupLayout(); + gl.setPolicy(GroupLayout.EQUALIZE); + setLayout(gl); + } + + /** + * Provides the button box with a data model which it will display. If + * the model contains {@link Image} objects, they will be used to make + * icons for the buttons. Otherwise the button text will contain the + * string representation of the elements in the model. + */ + public void setModel (ComboBoxModel model) + { + // if we had a previous model, unregister ourselves from it + if (_model != null) { + _model.removeListDataListener(this); + } + + // subscribe to our new model + _model = model; + _model.addListDataListener(this); + + // rebuild the list + removeAll(); + addButtons(0, _model.getSize()); + } + + /** + * Returns the model in use by the button box. + */ + public ComboBoxModel getModel () + { + return _model; + } + + /** + * Sets the index of the selected component. A value of -1 will clear + * the selection. + */ + public void setSelectedIndex (int selidx) + { + // update the display + updateSelection(selidx); + + // let the model know what's up + Object item = (selidx == -1) ? null : _model.getElementAt(selidx); + _model.setSelectedItem(item); + } + + /** + * Returns the index of the selected item. + */ + public int getSelectedIndex () + { + return _selectedIndex; + } + + /** + * Specifies the command that will be used when generating action + * events (which is done when the selection changes). + */ + public void setActionCommand (String actionCommand) + { + _actionCommand = actionCommand; + } + + /** + * Adds a listener to our list of entities to be notified when the + * selection changes. + */ + public void addActionListener (ActionListener l) + { + listenerList.add(ActionListener.class, l); + } + + /** + * Removes a listener from the list. + */ + public void removeActionListener (ActionListener l) + { + listenerList.remove(ActionListener.class, l); + } + + /** + * Notifies our listeners when the selection changed. + */ + protected void fireActionPerformed () + { + // guaranteed to return a non-null array + Object[] listeners = listenerList.getListenerList(); + // process the listeners last to first, notifying those that are + // interested in this event + for (int i = listeners.length-2; i >= 0; i -= 2) { + if (listeners[i] == ActionListener.class) { + // lazily create the event: + if (_actionEvent == null) { + _actionEvent = new ActionEvent( + this, ActionEvent.ACTION_PERFORMED, _actionCommand); + } + ((ActionListener)listeners[i+1]).actionPerformed(_actionEvent); + } + } + } + + // documentation inherited from interface + public void contentsChanged (ListDataEvent e) + { + // if this update is informing us of a new selection, reflect that + // in the UI + int start = e.getIndex0(), count = start - e.getIndex1() + 1; + if (start == -1) { + // figure out the selected index + int selidx = -1; + Object eitem = _model.getSelectedItem(); + if (eitem != null) { + int ecount = _model.getSize(); + for (int i = 0; i < ecount; i++) { + if (eitem == _model.getElementAt(i)) { + selidx = i; + break; + } + } + } + + // and update it + updateSelection(selidx); + + } else { + // replace the buttons in this range + removeButtons(start, count); + addButtons(start, count); + } + } + + // documentation inherited from interface + public void intervalAdded (ListDataEvent e) + { + int start = e.getIndex0(), count = e.getIndex1() - start + 1; + + // adjust the selected index + if (_selectedIndex >= start) { + _selectedIndex += count; + } + + // add buttons for the new interval + addButtons(start, count); + } + + // documentation inherited from interface + public void intervalRemoved (ListDataEvent e) + { + // remove the buttons in the specified interval + int start = e.getIndex0(), count = e.getIndex1() - start + 1; + removeButtons(start, count); + revalidate(); + repaint(); + } + + // documentation inherited from interface + public void mouseClicked (MouseEvent e) + { + } + + // documentation inherited from interface + public void mousePressed (MouseEvent e) + { + // keep track of the selected button + _selectedButton = (JLabel)e.getSource(); + _selectedButton.setBorder(SELECTED_BORDER); + _selectedButton.repaint(); + } + + // documentation inherited from interface + public void mouseReleased (MouseEvent e) + { + // if the mouse was released within the bounds of the button, go + // ahead and select it properly + if (_selectedButton != null) { + if (_selectedButton.contains(e.getX(), e.getY())) { + // figure out which button this is + int selidx = -1; + int ccount = getComponentCount(); + for (int i = 0; i < ccount; i++) { + if (_selectedButton == getComponent(i)) { + selidx = i; + break; + } + } + + // sanity check + if (selidx == -1) { + Log.warning("Got action from non-button component!? " + + "[event=" + e + "]."); + + } else { + // if the selection changed, update our display and + // the model + setSelectedIndex(selidx); + } + + } else { + _selectedButton.setBorder(DESELECTED_BORDER); + _selectedButton.repaint(); + } + + // clear out the selected button indicator + _selectedButton = null; + } + } + + // documentation inherited from interface + public void mouseEntered (MouseEvent e) + { + } + + // documentation inherited from interface + public void mouseExited (MouseEvent e) + { + } + + /** + * Adds buttons for the specified range of model elements. + */ + protected void addButtons (int start, int count) + { + Object selobj = _model.getSelectedItem(); + for (int i = start; i < count; i++) { + Object elem = _model.getElementAt(i); + if (selobj == elem) { + _selectedIndex = i; + } + JLabel ibut = null; + if (elem instanceof Image) { + ibut = new JLabel(new ImageIcon((Image)elem)); + } else { + ibut = new JLabel(elem.toString()); + } + ibut.addMouseListener(this); + ibut.setBorder((_selectedIndex == i) ? + SELECTED_BORDER : DESELECTED_BORDER); + add(ibut, i); + } + + revalidate(); + repaint(); + } + + /** + * Removes the buttons in the specified interval. + */ + protected void removeButtons (int start, int count) + { + while (count-- > 0) { + remove(start); + } + + // adjust the selected index + if (_selectedIndex >= start) { + if (start + count > _selectedIndex) { + _selectedIndex = -1; + } else { + _selectedIndex -= count; + } + } + } + + /** + * Sets the selection to the specified index and updates the buttons + * to reflect the change. This does not update the model. + */ + protected void updateSelection (int selidx) + { + // do nothing if this element is already selected + if (selidx == _selectedIndex) { + return; + } + + // unhighlight the old component + if (_selectedIndex != -1) { + JLabel but = (JLabel)getComponent(_selectedIndex); + but.setBorder(DESELECTED_BORDER); + } + + // save the new selection + _selectedIndex = selidx; + + // if the selection is valid, highlight the new component + if (_selectedIndex != -1) { + JLabel but = (JLabel)getComponent(_selectedIndex); + but.setBorder(SELECTED_BORDER); + } + + repaint(); + } + + /** The contents of the box. */ + protected ComboBoxModel _model; + + /** The index of the selected button. */ + protected int _selectedIndex = -1; + + /** The button over which the mouse was pressed. */ + protected JLabel _selectedButton; + + /** Used when notifying our listeners. */ + protected ActionEvent _actionEvent; + + /** The action command we generate when the selection changes. */ + protected String _actionCommand; + + /** The border used for selected components. */ + protected static final Border SELECTED_BORDER = + BorderFactory.createLoweredBevelBorder(); + + /** The border used for non-selected components. */ + protected static final Border DESELECTED_BORDER = + BorderFactory.createRaisedBevelBorder(); +} diff --git a/projects/samskivert/tests/src/java/com/samskivert/swing/TestComboButtonBox.java b/projects/samskivert/tests/src/java/com/samskivert/swing/TestComboButtonBox.java new file mode 100644 index 00000000..fadf99fc --- /dev/null +++ b/projects/samskivert/tests/src/java/com/samskivert/swing/TestComboButtonBox.java @@ -0,0 +1,47 @@ +// +// $Id: TestComboButtonBox.java,v 1.1 2002/03/10 05:10:37 mdb Exp $ + +package com.samskivert.swing; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JFrame; + +/** + * Tests the image button box. + */ +public class TestComboButtonBox +{ + protected static Image createImage (Color color) + { + BufferedImage img = + new BufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB); + Graphics g = img.getGraphics(); + g.setColor(color); + g.fillRect(0, 0, 24, 24); + g.dispose(); + return img; + } + + public static void main (String[] args) + { + JFrame frame = new JFrame("Test ComboButtonBox"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + DefaultComboBoxModel model = new DefaultComboBoxModel(); + model.addElement(createImage(Color.blue)); + model.addElement(createImage(Color.green)); + model.addElement(createImage(Color.red)); + model.addElement(createImage(Color.yellow)); + + ComboButtonBox box = + new ComboButtonBox(ComboButtonBox.HORIZONTAL, model); + frame.getContentPane().add(box); + frame.pack(); + frame.show(); + } +}