diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/javax/swing/JList.java | |
download | cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.bz2 cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/javax/swing/JList.java')
-rw-r--r-- | libjava/classpath/javax/swing/JList.java | 2499 |
1 files changed, 2499 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/JList.java b/libjava/classpath/javax/swing/JList.java new file mode 100644 index 000000000..b12fda365 --- /dev/null +++ b/libjava/classpath/javax/swing/JList.java @@ -0,0 +1,2499 @@ +/* JList.java -- + Copyright (C) 2002, 2003, 2004, 2005, 2006, Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath 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 2, or (at your option) +any later version. + +GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package javax.swing; + +import gnu.java.lang.CPStringBuilder; + +import java.awt.Color; +import java.awt.Component; +import java.awt.ComponentOrientation; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Locale; +import java.util.Vector; + +import javax.accessibility.Accessible; +import javax.accessibility.AccessibleComponent; +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; +import javax.accessibility.AccessibleSelection; +import javax.accessibility.AccessibleState; +import javax.accessibility.AccessibleStateSet; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.plaf.ListUI; +import javax.swing.text.Position; + +/** + * <p>This class is a facade over three separate objects: {@link + * javax.swing.ListModel}, {@link javax.swing.ListSelectionModel} and + * {@link javax.swing.plaf.ListUI}. The facade represents a unified "list" + * concept, with independently replacable (possibly client-provided) models + * for its contents and its current selection. In addition, each element in + * the list is rendered via a strategy class {@link + * javax.swing.ListCellRenderer}.</p> + * + * <p>Lists have many properties, some of which are stored in this class + * while others are delegated to the list's model or selection. The + * following properties are available:</p> + * + * <table> + * <tr><th>Property </th><th>Stored in</th><th>Bound?</th></tr> + * <tr><td>accessibleContext </td><td>list </td><td>no </td></tr> + * <tr><td>anchorSelectionIndex </td><td>selection</td><td>no </td></tr> + * <tr><td>cellRenderer </td><td>list </td><td>yes </td></tr> + * <tr><td>dragEnabled </td><td>list </td><td>no </td></tr> + * <tr><td>firstVisibleIndex </td><td>list </td><td>no </td></tr> + * <tr><td>fixedCellHeight </td><td>list </td><td>yes </td></tr> + * <tr><td>fixedCellWidth </td><td>list </td><td>yes </td></tr> + * <tr><td>lastVisibleIndex </td><td>list </td><td>no </td></tr> + * <tr><td>layoutOrientation </td><td>list </td><td>yes </td></tr> + * <tr><td>leadSelectionIndex </td><td>selection</td><td>no </td></tr> + * <tr><td>maxSelectionIndex </td><td>selection</td><td>no </td></tr> + * <tr><td>minSelectionIndex </td><td>selection</td><td>no </td></tr> + * <tr><td>model </td><td>list </td><td>yes </td></tr> + * <tr><td>opaque </td><td>list </td><td>no </td></tr> + * <tr><td>preferredScrollableViewportSize</td><td>list </td><td>no </td></tr> + * <tr><td>prototypeCellValue </td><td>list </td><td>yes </td></tr> + * <tr><td>scrollableTracksViewportHeight </td><td>list </td><td>no </td></tr> + * <tr><td>scrollableTracksViewportWidth </td><td>list </td><td>no </td></tr> + * <tr><td>selectedIndex </td><td>selection</td><td>no </td></tr> + * <tr><td>selectedIndices </td><td>selection</td><td>no </td></tr> + * <tr><td>selectedValue </td><td>model </td><td>no </td></tr> + * <tr><td>selectedValues </td><td>model </td><td>no </td></tr> + * <tr><td>selectionBackground </td><td>list </td><td>yes </td></tr> + * <tr><td>selectionEmpty </td><td>selection</td><td>no </td></tr> + * <tr><td>selectionForeground </td><td>list </td><td>yes </td></tr> + * <tr><td>selectionMode </td><td>selection</td><td>no </td></tr> + * <tr><td>selectionModel </td><td>list </td><td>yes </td></tr> + * <tr><td>UI </td><td>list </td><td>yes </td></tr> + * <tr><td>UIClassID </td><td>list </td><td>no </td></tr> + * <tr><td>valueIsAdjusting </td><td>list </td><td>no </td></tr> + * <tr><td>visibleRowCount </td><td>list </td><td>no </td></tr> + * </table> + * + * @author Graydon Hoare (graydon@redhat.com) + */ + +public class JList extends JComponent implements Accessible, Scrollable +{ + + /** + * Provides accessibility support for <code>JList</code>. + */ + protected class AccessibleJList extends AccessibleJComponent + implements AccessibleSelection, PropertyChangeListener, + ListSelectionListener, ListDataListener + { + + /** + * Provides accessibility support for list elements in <code>JList</code>s. + */ + protected class AccessibleJListChild extends AccessibleContext + implements Accessible, AccessibleComponent + { + + /** + * The parent list. + */ + JList parent; + + /** + * The index in the list for that child. + */ + int listIndex; + + /** + * The cursor for this list child. + */ + // TODO: Testcases show that this class somehow stores state about the + // cursor. I cannot make up though how that could affect + // the actual list. + Cursor cursor = Cursor.getDefaultCursor(); + + /** + * Creates a new instance of <code>AccessibleJListChild</code>. + * + * @param list the list of which this is an accessible child + * @param index the list index for this child + */ + public AccessibleJListChild(JList list, int index) + { + parent = list; + listIndex = index; + } + + /** + * Returns the accessible context of this object. Returns + * <code>this</code> since <code>AccessibleJListChild</code>s are their + * own accessible contexts. + * + * @return the accessible context of this object, <code>this</code> + */ + public AccessibleContext getAccessibleContext() + { + return this; + } + + /** + * Returns the background color for this list child. This returns the + * background of the <code>JList</code> itself since the background + * cannot be set on list children individually + * + * @return the background color for this list child + */ + public Color getBackground() + { + return parent.getBackground(); + } + + /** + * Calling this method has no effect, since the background color cannot be + * set on list children individually. + * + * @param color not used here. + */ + public void setBackground(Color color) + { + // Calling this method has no effect, since the background color cannot + // be set on list children individually. + } + + /** + * Returns the foreground color for this list child. This returns the + * background of the <code>JList</code> itself since the foreground + * cannot be set on list children individually. + * + * @return the background color for this list child + */ + public Color getForeground() + { + return parent.getForeground(); + } + + /** + * Calling this method has no effect, since the foreground color cannot be + * set on list children individually. + * + * @param color not used here. + */ + public void setForeground(Color color) + { + // Calling this method has no effect, since the foreground color cannot + // be set on list children individually. + } + + /** + * Returns the cursor for this list child. + * + * @return the cursor for this list child + */ + public Cursor getCursor() + { + // TODO: Testcases show that this method returns the cursor that has + // been set by setCursor. I cannot make up though how that could affect + // the actual list. + return cursor; + } + + /** + * Sets the cursor for this list child. + */ + public void setCursor(Cursor cursor) + { + this.cursor = cursor; + // TODO: Testcases show that this method returns the cursor that has + // been set by setCursor. I cannot make up though how that could affect + // the actual list. + } + + /** + * Returns the font of the <code>JList</code> since it is not possible to + * set fonts for list children individually. + * + * @return the font of the <code>JList</code> + */ + public Font getFont() + { + return parent.getFont(); + } + + /** + * Does nothing since it is not possible to set the font on list children + * individually. + * + * @param font not used here + */ + public void setFont(Font font) + { + // Does nothing since it is not possible to set the font on list + // children individually. + } + + /** + * Returns the font metrics for the specified font. This method forwards + * to the parent <code>JList</code>. + * + * @param font the font for which the font metrics is queried + * + * @return the font metrics for the specified font + */ + public FontMetrics getFontMetrics(Font font) + { + return parent.getFontMetrics(font); + } + + /** + * Returns <code>true</code> if the parent <code>JList</code> is enabled, + * <code>false</code> otherwise. The list children cannot have an enabled + * flag set individually. + * + * @return <code>true</code> if the parent <code>JList</code> is enabled, + * <code>false</code> otherwise + */ + public boolean isEnabled() + { + return parent.isEnabled(); + } + + /** + * Does nothing since the enabled flag cannot be set for list children + * individually. + * + * @param b not used here + */ + public void setEnabled(boolean b) + { + // Does nothing since the enabled flag cannot be set for list children + // individually. + } + + /** + * Returns <code>true</code> if this list child is visible, + * <code>false</code> otherwise. The value of this property depends + * on {@link JList#getFirstVisibleIndex()} and + * {@link JList#getLastVisibleIndex()}. + * + * @return <code>true</code> if this list child is visible, + * <code>false</code> otherwise + */ + public boolean isVisible() + { + return listIndex >= parent.getFirstVisibleIndex() + && listIndex <= parent.getLastVisibleIndex(); + } + + /** + * The value of the visible property cannot be modified, so this method + * does nothing. + * + * @param b not used here + */ + public void setVisible(boolean b) + { + // The value of the visible property cannot be modified, so this method + // does nothing. + } + + /** + * Returns <code>true</code> if this list child is currently showing on + * screen and <code>false</code> otherwise. The list child is showing if + * it is visible and if it's parent JList is currently showing. + * + * @return <code>true</code> if this list child is currently showing on + * screen and <code>false</code> otherwise + */ + public boolean isShowing() + { + return isVisible() && parent.isShowing(); + } + + /** + * Returns <code>true</code> if this list child covers the screen location + * <code>point</code> (relative to the <code>JList</code> coordinate + * system, <code>false</code> otherwise. + * + * @return <code>true</code> if this list child covers the screen location + * <code>point</code> , <code>false</code> otherwise + */ + public boolean contains(Point point) + { + return getBounds().contains(point); + } + + /** + * Returns the absolute screen location of this list child. + * + * @return the absolute screen location of this list child + */ + public Point getLocationOnScreen() + { + Point loc = getLocation(); + SwingUtilities.convertPointToScreen(loc, parent); + return loc; + } + + /** + * Returns the screen location of this list child relative to it's parent. + * + * @return the location of this list child relative to it's parent + * + * @see JList#indexToLocation(int) + */ + public Point getLocation() + { + return parent.indexToLocation(listIndex); + } + + /** + * Does nothing since the screen location cannot be set on list children + * explictitly. + * + * @param point not used here + */ + public void setLocation(Point point) + { + // Does nothing since the screen location cannot be set on list children + // explictitly. + } + + /** + * Returns the bounds of this list child. + * + * @return the bounds of this list child + * + * @see JList#getCellBounds(int, int) + */ + public Rectangle getBounds() + { + return parent.getCellBounds(listIndex, listIndex); + } + + /** + * Does nothing since the bounds cannot be set on list children + * individually. + * + * @param rectangle not used here + */ + public void setBounds(Rectangle rectangle) + { + // Does nothing since the bounds cannot be set on list children + // individually. + } + + /** + * Returns the size of this list child. + * + * @return the size of this list child + */ + public Dimension getSize() + { + Rectangle b = getBounds(); + return b.getSize(); + } + + /** + * Does nothing since the size cannot be set on list children + * individually. + * + * @param dimension not used here + */ + public void setSize(Dimension dimension) + { + // Does nothing since the size cannot be set on list children + // individually. + } + + /** + * Returns <code>null</code> because list children do not have children + * themselves + * + * @return <code>null</code> + */ + public Accessible getAccessibleAt(Point point) + { + return null; + } + + /** + * Returns <code>true</code> since list children are focus traversable. + * + * @return true + */ + public boolean isFocusTraversable() + { + // TODO: Is this 100% ok? + return true; + } + + /** + * Requests focus on the parent list. List children cannot request focus + * individually. + */ + public void requestFocus() + { + // TODO: Is this 100% ok? + parent.requestFocus(); + } + + /** + * Adds a focus listener to the parent list. List children do not have + * their own focus management. + * + * @param listener the focus listener to add + */ + public void addFocusListener(FocusListener listener) + { + // TODO: Is this 100% ok? + parent.addFocusListener(listener); + } + + /** + * Removes a focus listener from the parent list. List children do not + * have their own focus management. + * + * @param listener the focus listener to remove + */ + public void removeFocusListener(FocusListener listener) + { + // TODO: Is this 100% + parent.removeFocusListener(listener); + } + + /** + * Returns the accessible role of this list item, which is + * {@link AccessibleRole#LABEL}. + * + * @return {@link AccessibleRole#LABEL} + */ + public AccessibleRole getAccessibleRole() + { + return AccessibleRole.LABEL; + } + + /** + * Returns the accessible state set of this list item. + * + * @return the accessible state set of this list item + */ + public AccessibleStateSet getAccessibleStateSet() + { + AccessibleStateSet states = new AccessibleStateSet(); + if (isVisible()) + states.add(AccessibleState.VISIBLE); + if (isShowing()) + states.add(AccessibleState.SHOWING); + if (isFocusTraversable()) + states.add(AccessibleState.FOCUSABLE); + // TODO: How should the active state be handled? The API docs + // suggest that this state is set on the activated list child, + // that is the one that is drawn with a box. However, I don't know how + // to implement this. + + // TODO: We set the selectable state here because list children are + // selectable. Is there a way to disable single children? + if (parent.isEnabled()) + states.add(AccessibleState.SELECTABLE); + + if (parent.isSelectedIndex(listIndex)) + states.add(AccessibleState.SELECTED); + + // TODO: Handle more states here? + return states; + } + + /** + * Returns the index of this list child within it's parent list. + * + * @return the index of this list child within it's parent list + */ + public int getAccessibleIndexInParent() + { + return listIndex; + } + + /** + * Returns <code>0</code> since list children don't have children + * themselves. + * + * @return <code>0</code> + */ + public int getAccessibleChildrenCount() + { + return 0; + } + + /** + * Returns <code>null</code> since list children don't have children + * themselves. + * + * @return <code>null</code> + */ + public Accessible getAccessibleChild(int i) + { + return null; + } + + /** + * Returns the locale of this component. This call is forwarded to the + * parent list since list children don't have a separate locale setting. + * + * @return the locale of this component + */ + public Locale getLocale() + { + return parent.getLocale(); + } + + /** + * This method does + * nothing, list children are transient accessible objects which means + * that they don't fire property change events. + * + * @param l not used here + */ + public void addPropertyChangeListener(PropertyChangeListener l) + { + // Do nothing here. + } + + /** + * This method does + * nothing, list children are transient accessible objects which means + * that they don't fire property change events. + * + * @param l not used here + */ + public void removePropertyChangeListener(PropertyChangeListener l) + { + // Do nothing here. + } + + // TODO: Implement the remaining methods of this class. + } + + /** + * Create a new AccessibleJList. + */ + public AccessibleJList() + { + // Nothing to do here. + } + + /** + * Returns the number of selected accessible children. + * + * @return the number of selected accessible children + */ + public int getAccessibleSelectionCount() + { + return getSelectedIndices().length; + } + + /** + * Returns the n-th selected accessible child. + * + * @param n the index of the selected child to return + * + * @return the n-th selected accessible child + */ + public Accessible getAccessibleSelection(int n) + { + return new AccessibleJListChild(JList.this, getSelectedIndices()[n]); + } + + /** + * Returns <code>true</code> if the n-th child is selected, + * <code>false</code> otherwise. + * + * @param n the index of the child of which the selected state is queried + * + * @return <code>true</code> if the n-th child is selected, + * <code>false</code> otherwise + */ + public boolean isAccessibleChildSelected(int n) + { + return isSelectedIndex(n); + } + + /** + * Adds the accessible item with the specified index to the selected items. + * If multiple selections are supported, the item is added to the selection, + * otherwise the item replaces the current selection. + * + * @param i the index of the item to add to the selection + */ + public void addAccessibleSelection(int i) + { + addSelectionInterval(i, i); + } + + /** + * Removes the accessible item with the specified index to the selection. + * + * @param i the index of the item to be removed from the selection + */ + public void removeAccessibleSelection(int i) + { + removeSelectionInterval(i, i); + } + + /** + * Remove all selection items from the selection. + */ + public void clearAccessibleSelection() + { + clearSelection(); + } + + /** + * Selects all items if multiple selections are supported. + * Otherwise do nothing. + */ + public void selectAllAccessibleSelection() + { + addSelectionInterval(0, getModel().getSize()); + } + + /** + * Receices notification when the list selection is changed. This method + * fires two property change events, the first with + * {@link AccessibleContext#ACCESSIBLE_VISIBLE_DATA_PROPERTY} and the second + * with {@link AccessibleContext#ACCESSIBLE_SELECTION_PROPERTY}. + * + * @param event the list selection event + */ + public void valueChanged(ListSelectionEvent event) + { + firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY, Boolean.FALSE, + Boolean.TRUE); + firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, Boolean.FALSE, + Boolean.TRUE); + } + + /** + * Receives notification when items have changed in the + * <code>JList</code>. This method fires a property change event with + * {@link AccessibleContext#ACCESSIBLE_VISIBLE_DATA_PROPERTY}. + * + * @param event the list data event + */ + public void contentsChanged(ListDataEvent event) + { + firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY, Boolean.FALSE, + Boolean.TRUE); + } + + /** + * Receives notification when items are inserted into the + * <code>JList</code>. This method fires a property change event with + * {@link AccessibleContext#ACCESSIBLE_VISIBLE_DATA_PROPERTY}. + * + * @param event the list data event + */ + public void intervalAdded(ListDataEvent event) + { + firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY, Boolean.FALSE, + Boolean.TRUE); + } + + /** + * Receives notification when items are removed from the + * <code>JList</code>. This method fires a property change event with + * {@link AccessibleContext#ACCESSIBLE_VISIBLE_DATA_PROPERTY}. + * + * @param event the list data event + */ + public void intervalRemoved(ListDataEvent event) + { + firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY, Boolean.FALSE, + Boolean.TRUE); + } + + + /** + * Receives notification about changes of the <code>JList</code>'s + * properties. This is used to re-register this object as listener to + * the data model and selection model when the data model or selection model + * changes. + * + * @param e the property change event + */ + public void propertyChange(PropertyChangeEvent e) + { + String propertyName = e.getPropertyName(); + if (propertyName.equals("model")) + { + ListModel oldModel = (ListModel) e.getOldValue(); + oldModel.removeListDataListener(this); + ListModel newModel = (ListModel) e.getNewValue(); + newModel.addListDataListener(this); + } + else if (propertyName.equals("selectionModel")) + { + ListSelectionModel oldModel = (ListSelectionModel) e.getOldValue(); + oldModel.removeListSelectionListener(this); + ListSelectionModel newModel = (ListSelectionModel) e.getNewValue(); + oldModel.addListSelectionListener(this); + } + } + + /** + * Return the state set of the <code>JList</code>. + * + * @return the state set of the <code>JList</code> + */ + public AccessibleStateSet getAccessibleStateSet() + { + // TODO: Figure out if there is possibly more state that must be + // handled here. + AccessibleStateSet s = super.getAccessibleStateSet(); + if (getSelectionMode() != ListSelectionModel.SINGLE_SELECTION) + s.add(AccessibleState.MULTISELECTABLE); + return s; + } + + /** + * Returns the accessible role for <code>JList</code>, + * {@link AccessibleRole#LIST}. + * + * @return the accessible role for <code>JList</code> + */ + public AccessibleRole getAccessibleRole() + { + return AccessibleRole.LIST; + } + + /** + * Returns the accessible child at the visual location <code>p</code> + * (relative to the upper left corner of the <code>JList</code>). If there + * is no child at that location, this returns <code>null</code>. + * + * @param p the screen location for which to return the accessible child + * + * @return the accessible child at the specified location, or + * <code>null</code> if there is no child at that location + */ + public Accessible getAccessibleAt(Point p) + { + int childIndex = locationToIndex(p); + return getAccessibleChild(childIndex); + } + + /** + * Returns the number of accessible children in the <code>JList</code>. + * + * @return the number of accessible children in the <code>JList</code> + */ + public int getAccessibleChildrenCount() + { + return getModel().getSize(); + } + + /** + * Returns the n-th accessible child of this <code>JList</code>. This will + * be an instance of {@link AccessibleJListChild}. If there is no child + * at that index, <code>null</code> is returned. + * + * @param n the index of the child to return + * + * @return the n-th accessible child of this <code>JList</code> + */ + public Accessible getAccessibleChild(int n) + { + if (getModel().getSize() <= n) + return null; + return new AccessibleJListChild(JList.this, n); + } + } + + private static final long serialVersionUID = 4406629526391098046L; + + /** + * Constant value used in "layoutOrientation" property. This value means + * that cells are laid out in a single vertical column. This is the default. + */ + public static final int VERTICAL = 0; + + /** + * Constant value used in "layoutOrientation" property. This value means + * that cells are laid out in multiple columns "newspaper style", filling + * vertically first, then horizontally. + */ + public static final int VERTICAL_WRAP = 1; + + /** + * Constant value used in "layoutOrientation" property. This value means + * that cells are laid out in multiple columns "newspaper style", + * filling horizontally first, then vertically. + */ + public static final int HORIZONTAL_WRAP = 2; + + /** + * This property indicates whether "drag and drop" functions are enabled + * on the list. + */ + boolean dragEnabled; + + /** This property provides a strategy for rendering cells in the list. */ + ListCellRenderer cellRenderer; + + /** + * This property indicates an fixed width to assign to all cells in the + * list. If its value is <code>-1</code>, no width has been + * assigned. This value can be set explicitly, or implicitly by setting + * the {@link #prototypeCellValue} property. + */ + int fixedCellWidth; + + /** + * This property indicates an fixed height to assign to all cells in the + * list. If its value is <code>-1</code>, no height has been + * assigned. This value can be set explicitly, or implicitly by setting + * the {@link #prototypeCellValue} property. + */ + int fixedCellHeight; + + /** + * This property holds the current layout orientation of the list, which + * is one of the integer constants {@link #VERTICAL}, {@link + * #VERTICAL_WRAP}, or {@link #HORIZONTAL_WRAP}. + */ + int layoutOrientation; + + /** This property holds the data elements displayed by the list. */ + ListModel model; + + /** + * <p>This property holds a reference to a "prototype" data value -- + * typically a String -- which is used to calculate the {@link + * #fixedCellWidth} and {@link #fixedCellHeight} properties, using the + * {@link #cellRenderer} property to acquire a component to render the + * prototype.</p> + * + * <p>It is important that you <em>not</em> set this value to a + * component. It has to be a <em>data value</em> such as the objects you + * would find in the list's model. Setting it to a component will have + * undefined (and undesirable) affects. </p> + */ + Object prototypeCellValue; + + /** + * This property specifies a foreground color for the selected cells in + * the list. When {@link ListCellRenderer#getListCellRendererComponent} + * is called with a selected cell object, the component returned will + * have its "foreground" set to this color. + */ + Color selectionBackground; + + /** + * This property specifies a background color for the selected cells in + * the list. When {@link ListCellRenderer#getListCellRendererComponent} + * is called with a selected cell object, the component returned will + * have its "background" property set to this color. + */ + Color selectionForeground; + + /** + * This property holds a description of which data elements in the {@link + * #model} property should be considered "selected", when displaying and + * interacting with the list. + */ + ListSelectionModel selectionModel; + + /** + * This property indicates a <em>preference</em> for the number of rows + * displayed in the list, and will scale the + * {@link #getPreferredScrollableViewportSize} property accordingly. The actual + * number of displayed rows, when the list is placed in a real {@link + * JViewport} or other component, may be greater or less than this number. + */ + int visibleRowCount; + + /** + * Fire a {@link ListSelectionEvent} to all the registered + * ListSelectionListeners. + * + * @param firstIndex the lowest index covering the selection change. + * @param lastIndex the highest index covering the selection change. + * @param isAdjusting a flag indicating if this event is one in a series + * of events updating the selection. + */ + protected void fireSelectionValueChanged(int firstIndex, int lastIndex, + boolean isAdjusting) + { + ListSelectionEvent evt = new ListSelectionEvent(this, firstIndex, + lastIndex, isAdjusting); + ListSelectionListener listeners[] = getListSelectionListeners(); + for (int i = 0; i < listeners.length; ++i) + { + listeners[i].valueChanged(evt); + } + } + + /** + * This private listener propagates {@link ListSelectionEvent} events + * from the list's "selectionModel" property to the list's {@link + * ListSelectionListener} listeners. It also listens to {@link + * ListDataEvent} events from the list's {@link #model} property. If this + * class receives either type of event, it triggers repainting of the + * list. + */ + private class ListListener + implements ListSelectionListener, ListDataListener + { + // ListDataListener events + public void contentsChanged(ListDataEvent event) + { + JList.this.revalidate(); + JList.this.repaint(); + } + public void intervalAdded(ListDataEvent event) + { + JList.this.revalidate(); + JList.this.repaint(); + } + public void intervalRemoved(ListDataEvent event) + { + JList.this.revalidate(); + JList.this.repaint(); + } + // ListSelectionListener events + public void valueChanged(ListSelectionEvent event) + { + JList.this.fireSelectionValueChanged(event.getFirstIndex(), + event.getLastIndex(), + event.getValueIsAdjusting()); + JList.this.repaint(); + } + } + + /** + * Shared ListListener instance, subscribed to both the current {@link + * #model} and {@link #selectionModel} properties of the list. + */ + ListListener listListener; + + + /** + * Creates a new <code>JList</code> object. + */ + public JList() + { + init(new DefaultListModel()); + } + + /** + * Creates a new <code>JList</code> object. + * + * @param items the initial list items. + */ + public JList(Object[] items) + { + init(createListModel(items)); + } + + /** + * Creates a new <code>JList</code> object. + * + * @param items the initial list items. + */ + public JList(Vector<?> items) + { + init(createListModel(items)); + } + + /** + * Creates a new <code>JList</code> object. + * + * @param model a model containing the list items (<code>null</code> not + * permitted). + * + * @throws IllegalArgumentException if <code>model</code> is + * <code>null</code>. + */ + public JList(ListModel model) + { + init(model); + } + + /** + * Initializes the list. + * + * @param m the list model (<code>null</code> not permitted). + */ + private void init(ListModel m) + { + if (m == null) + throw new IllegalArgumentException("Null model not permitted."); + dragEnabled = false; + fixedCellHeight = -1; + fixedCellWidth = -1; + layoutOrientation = VERTICAL; + opaque = true; + visibleRowCount = 8; + + cellRenderer = new DefaultListCellRenderer(); + listListener = new ListListener(); + + model = m; + if (model != null) + model.addListDataListener(listListener); + + selectionModel = createSelectionModel(); + if (selectionModel != null) + { + selectionModel.addListSelectionListener(listListener); + selectionModel.setSelectionMode + (ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + } + setLayout(null); + + updateUI(); + } + + /** + * Creates the default <code>ListSelectionModel</code>. + * + * @return the <code>ListSelectionModel</code> + */ + protected ListSelectionModel createSelectionModel() + { + return new DefaultListSelectionModel(); + } + + /** + * Gets the value of the {@link #fixedCellHeight} property. This property + * may be <code>-1</code> to indicate that no cell height has been + * set. This property is also set implicitly when the + * {@link #prototypeCellValue} property is set. + * + * @return The current value of the property + * + * @see #fixedCellHeight + * @see #setFixedCellHeight + * @see #setPrototypeCellValue + */ + public int getFixedCellHeight() + { + return fixedCellHeight; + } + + /** + * Sets the value of the {@link #fixedCellHeight} property. This property + * may be <code>-1</code> to indicate that no cell height has been + * set. This property is also set implicitly when the {@link + * #prototypeCellValue} property is set, but setting it explicitly + * overrides the height computed from {@link #prototypeCellValue}. + * + * @param h the height. + * + * @see #getFixedCellHeight + * @see #getPrototypeCellValue + */ + public void setFixedCellHeight(int h) + { + if (fixedCellHeight == h) + return; + + int old = fixedCellHeight; + fixedCellHeight = h; + firePropertyChange("fixedCellHeight", old, h); + } + + + /** + * Gets the value of the {@link #fixedCellWidth} property. This property + * may be <code>-1</code> to indicate that no cell width has been + * set. This property is also set implicitly when the {@link + * #prototypeCellValue} property is set. + * + * @return The current value of the property + * + * @see #setFixedCellWidth + * @see #setPrototypeCellValue + */ + public int getFixedCellWidth() + { + return fixedCellWidth; + } + + /** + * Sets the value of the {@link #fixedCellWidth} property. This property + * may be <code>-1</code> to indicate that no cell width has been + * set. This property is also set implicitly when the {@link + * #prototypeCellValue} property is set, but setting it explicitly + * overrides the width computed from {@link #prototypeCellValue}. + * + * @param w the width. + * + * @see #getFixedCellHeight + * @see #getPrototypeCellValue + */ + public void setFixedCellWidth(int w) + { + if (fixedCellWidth == w) + return; + + int old = fixedCellWidth; + fixedCellWidth = w; + firePropertyChange("fixedCellWidth", old, w); + } + + /** + * Gets the value of the {@link #visibleRowCount} property. The default + * value is 8. + * + * @return the current value of the property. + * + * @see #setVisibleRowCount(int) + */ + public int getVisibleRowCount() + { + return visibleRowCount; + } + + /** + * Sets the value of the {@link #visibleRowCount} property. + * + * @param vc The new property value + * + * @see #getVisibleRowCount() + */ + public void setVisibleRowCount(int vc) + { + if (visibleRowCount != vc) + { + int oldValue = visibleRowCount; + visibleRowCount = Math.max(vc, 0); + firePropertyChange("visibleRowCount", oldValue, vc); + revalidate(); + repaint(); + } + } + + /** + * Adds a {@link ListSelectionListener} to the listener list for this + * list. The listener will be called back with a {@link + * ListSelectionEvent} any time the list's {@link #selectionModel} + * property changes. The source of such events will be the JList, + * not the selection model. + * + * @param listener The new listener to add + */ + public void addListSelectionListener(ListSelectionListener listener) + { + listenerList.add (ListSelectionListener.class, listener); + } + + /** + * Removes a {@link ListSelectionListener} from the listener list for + * this list. The listener will no longer be called when the list's + * {@link #selectionModel} changes. + * + * @param listener The listener to remove + */ + public void removeListSelectionListener(ListSelectionListener listener) + { + listenerList.remove(ListSelectionListener.class, listener); + } + + /** + * Returns an array of all ListSelectionListeners subscribed to this + * list. + * + * @return The current subscribed listeners + * + * @since 1.4 + */ + public ListSelectionListener[] getListSelectionListeners() + { + return (ListSelectionListener[]) getListeners(ListSelectionListener.class); + } + + /** + * Returns the selection mode for the list (one of: + * {@link ListSelectionModel#SINGLE_SELECTION}, + * {@link ListSelectionModel#SINGLE_INTERVAL_SELECTION} and + * {@link ListSelectionModel#MULTIPLE_INTERVAL_SELECTION}). + * + * @return The selection mode. + * + * @see #setSelectionMode(int) + */ + public int getSelectionMode() + { + return selectionModel.getSelectionMode(); + } + + /** + * Sets the list's "selectionMode" property, which simply mirrors the + * same property on the list's {@link #selectionModel} property. This + * property should be one of the integer constants + * <code>SINGLE_SELECTION</code>, <code>SINGLE_INTERVAL_SELECTION</code>, + * or <code>MULTIPLE_INTERVAL_SELECTION</code> from the {@link + * ListSelectionModel} interface. + * + * @param a The new selection mode + */ + public void setSelectionMode(int a) + { + selectionModel.setSelectionMode(a); + } + + /** + * Adds the interval <code>[a,a]</code> to the set of selections managed + * by this list's {@link #selectionModel} property. Depending on the + * selection mode, this may cause existing selections to become invalid, + * or may simply expand the set of selections. + * + * @param a A number in the half-open range <code>[0, x)</code> where + * <code>x = getModel.getSize()</code>, indicating the index of an + * element in the list to select. When < 0 the selection is cleared. + * + * @see #setSelectionMode + * @see #selectionModel + */ + public void setSelectedIndex(int a) + { + if (a < 0) + selectionModel.clearSelection(); + else + selectionModel.setSelectionInterval(a, a); + } + + /** + * For each element <code>a[i]</code> of the provided array + * <code>a</code>, calls {@link #setSelectedIndex} on <code>a[i]</code>. + * + * @param a an array of selected indices (<code>null</code> not permitted). + * + * @throws NullPointerException if <code>a</code> is <code>null</code>. + * @see #setSelectionMode + * @see #selectionModel + */ + public void setSelectedIndices(int [] a) + { + for (int i = 0; i < a.length; ++i) + setSelectedIndex(a[i]); + } + + /** + * Returns the minimum index of an element in the list which is currently + * selected. + * + * @return A number in the half-open range <code>[0, x)</code> where + * <code>x = getModel.getSize()</code>, indicating the minimum index of + * an element in the list for which the element is selected, or + * <code>-1</code> if no elements are selected + */ + public int getSelectedIndex() + { + return selectionModel.getMinSelectionIndex(); + } + + /** + * Returns <code>true</code> if the model's selection is empty, otherwise + * <code>false</code>. + * + * @return The return value of {@link ListSelectionModel#isSelectionEmpty} + */ + public boolean isSelectionEmpty() + { + return selectionModel.isSelectionEmpty(); + } + + /** + * Returns the list index of the upper left or upper right corner of the + * visible rectangle of this list, depending on the {@link + * Component#getComponentOrientation} property. + * + * @return The index of the first visible list cell, or <code>-1</code> + * if none is visible. + */ + public int getFirstVisibleIndex() + { + ComponentOrientation or = getComponentOrientation(); + Rectangle r = getVisibleRect(); + if (or == ComponentOrientation.RIGHT_TO_LEFT) + r.translate((int) r.getWidth() - 1, 0); + return getUI().locationToIndex(this, r.getLocation()); + } + + + /** + * Returns index of the cell to which specified location is closest to. If + * the location is outside the bounds of the list, then the greatest index + * in the list model is returned. If the list model is empty, then + * <code>-1</code> is returned. + * + * @param location for which to look for in the list + * + * @return index of the cell to which specified location is closest to. + */ + public int locationToIndex(Point location) + { + return getUI().locationToIndex(this, location); + } + + /** + * Returns location of the cell located at the specified index in the list. + * @param index of the cell for which location will be determined + * + * @return location of the cell located at the specified index in the list. + */ + public Point indexToLocation(int index) + { + return getUI().indexToLocation(this, index); + } + + /** + * Returns the list index of the lower right or lower left corner of the + * visible rectangle of this list, depending on the {@link + * Component#getComponentOrientation} property. + * + * @return The index of the last visible list cell, or <code>-1</code> + * if none is visible. + */ + public int getLastVisibleIndex() + { + ComponentOrientation or = getComponentOrientation(); + Rectangle r = getVisibleRect(); + r.translate(0, (int) r.getHeight() - 1); + if (or == ComponentOrientation.LEFT_TO_RIGHT) + r.translate((int) r.getWidth() - 1, 0); + if (getUI().locationToIndex(this, r.getLocation()) == -1 + && indexToLocation(getModel().getSize() - 1).y < r.y) + return getModel().getSize() - 1; + return getUI().locationToIndex(this, r.getLocation()); + } + + /** + * Returns the indices of values in the {@link #model} property which are + * selected. + * + * @return An array of model indices, each of which is selected according + * to the {@link #getSelectedValues} property + */ + public int[] getSelectedIndices() + { + int lo, hi, n, i, j; + if (selectionModel.isSelectionEmpty()) + return new int[0]; + lo = selectionModel.getMinSelectionIndex(); + hi = selectionModel.getMaxSelectionIndex(); + n = 0; + for (i = lo; i <= hi; ++i) + if (selectionModel.isSelectedIndex(i)) + n++; + int [] v = new int[n]; + j = 0; + for (i = lo; i <= hi; ++i) + if (selectionModel.isSelectedIndex(i)) + v[j++] = i; + return v; + } + + /** + * Indicates whether the list element at a given index value is + * currently selected. + * + * @param a The index to check + * @return <code>true</code> if <code>a</code> is the index of a selected + * list element + */ + public boolean isSelectedIndex(int a) + { + return selectionModel.isSelectedIndex(a); + } + + /** + * Returns the first value in the list's {@link #model} property which is + * selected, according to the list's {@link #selectionModel} property. + * This is equivalent to calling + * <code>getModel()getElementAt(getSelectedIndex())</code>, with a check + * for the special index value of <code>-1</code> which returns null + * <code>null</code>. + * + * @return The first selected element, or <code>null</code> if no element + * is selected. + * + * @see #getSelectedValues + */ + public Object getSelectedValue() + { + int index = getSelectedIndex(); + if (index == -1) + return null; + return getModel().getElementAt(index); + } + + /** + * Returns all the values in the list's {@link #model} property which are + * selected, according to the list's {@link #selectionModel} property. + * + * @return An array containing all the selected values + * @see #setSelectedValue + */ + public Object[] getSelectedValues() + { + int[] idx = getSelectedIndices(); + Object[] v = new Object[idx.length]; + for (int i = 0; i < idx.length; ++i) + v[i] = getModel().getElementAt(idx[i]); + return v; + } + + /** + * Gets the value of the {@link #selectionBackground} property. + * + * @return The current value of the property + */ + public Color getSelectionBackground() + { + return selectionBackground; + } + + /** + * Sets the value of the {@link #selectionBackground} property. + * + * @param c The new value of the property + */ + public void setSelectionBackground(Color c) + { + if (selectionBackground == c) + return; + + Color old = selectionBackground; + selectionBackground = c; + firePropertyChange("selectionBackground", old, c); + repaint(); + } + + /** + * Gets the value of the {@link #selectionForeground} property. + * + * @return The current value of the property + */ + public Color getSelectionForeground() + { + return selectionForeground; + } + + /** + * Sets the value of the {@link #selectionForeground} property. + * + * @param c The new value of the property + */ + public void setSelectionForeground(Color c) + { + if (selectionForeground == c) + return; + + Color old = selectionForeground; + selectionForeground = c; + firePropertyChange("selectionForeground", old, c); + } + + /** + * Sets the selection to cover only the specified value, if it + * exists in the model. + * + * @param obj The object to select + * @param scroll Whether to scroll the list to make the newly selected + * value visible + * + * @see #ensureIndexIsVisible + */ + + public void setSelectedValue(Object obj, boolean scroll) + { + for (int i = 0; i < model.getSize(); ++i) + { + if (model.getElementAt(i).equals(obj)) + { + setSelectedIndex(i); + if (scroll) + ensureIndexIsVisible(i); + break; + } + } + } + + /** + * Scrolls this list to make the specified cell visible. This + * only works if the list is contained within a viewport. + * + * @param i The list index to make visible + * + * @see JComponent#scrollRectToVisible + */ + public void ensureIndexIsVisible(int i) + { + Rectangle r = getUI().getCellBounds(this, i, i); + if (r != null) + scrollRectToVisible(r); + } + + /** + * Sets the {@link #model} property of the list to a new anonymous + * {@link AbstractListModel} subclass which accesses the provided Object + * array directly. + * + * @param listData The object array to build a new list model on + * @see #setModel + */ + public void setListData(Object[] listData) + { + setModel(createListModel(listData)); + } + + /** + * Returns a {@link ListModel} backed by the specified array. + * + * @param items the list items (don't use <code>null</code>). + * + * @return A list model containing the specified items. + */ + private ListModel createListModel(final Object[] items) + { + return new AbstractListModel() + { + public int getSize() + { + return items.length; + } + public Object getElementAt(int i) + { + return items[i]; + } + }; + } + + /** + * Returns a {@link ListModel} backed by the specified vector. + * + * @param items the list items (don't use <code>null</code>). + * + * @return A list model containing the specified items. + */ + private ListModel createListModel(final Vector items) + { + return new AbstractListModel() + { + public int getSize() + { + return items.size(); + } + public Object getElementAt(int i) + { + return items.get(i); + } + }; + } + + /** + * Sets the {@link #model} property of the list to a new anonymous {@link + * AbstractListModel} subclass which accesses the provided vector + * directly. + * + * @param listData The object array to build a new list model on + * @see #setModel + */ + public void setListData(final Vector<?> listData) + { + setModel(new AbstractListModel() + { + public int getSize() + { + return listData.size(); + } + + public Object getElementAt(int i) + { + return listData.elementAt(i); + } + }); + } + + /** + * Gets the value of the {@link #cellRenderer} property. + * + * @return The current value of the property + */ + public ListCellRenderer getCellRenderer() + { + return cellRenderer; + } + + /** + * Sets the value of the {@link #getCellRenderer} property. + * + * @param renderer The new property value + */ + public void setCellRenderer(ListCellRenderer renderer) + { + if (cellRenderer == renderer) + return; + + ListCellRenderer old = cellRenderer; + cellRenderer = renderer; + firePropertyChange("cellRenderer", old, renderer); + revalidate(); + repaint(); + } + + /** + * Gets the value of the {@link #model} property. + * + * @return The current value of the property + */ + public ListModel getModel() + { + return model; + } + + /** + * Sets the value of the {@link #model} property. The list's {@link + * #listListener} is unsubscribed from the existing model, if it exists, + * and re-subscribed to the new model. + * + * @param model the new model (<code>null</code> not permitted). + * + * @throws IllegalArgumentException if <code>model</code> is + * <code>null</code>. + */ + public void setModel(ListModel model) + { + if (model == null) + throw new IllegalArgumentException("Null 'model' argument."); + if (this.model == model) + return; + + if (this.model != null) + this.model.removeListDataListener(listListener); + + ListModel old = this.model; + this.model = model; + + if (this.model != null) + this.model.addListDataListener(listListener); + + firePropertyChange("model", old, model); + revalidate(); + repaint(); + } + + /** + * Returns the selection model for the {@link JList} component. Note that + * this class contains a range of convenience methods for configuring the + * selection model:<br> + * <ul> + * <li>{@link #clearSelection()};</li> + * <li>{@link #setSelectionMode(int)};</li> + * <li>{@link #addSelectionInterval(int, int)};</li> + * <li>{@link #setSelectedIndex(int)};</li> + * <li>{@link #setSelectedIndices(int[])};</li> + * <li>{@link #setSelectionInterval(int, int)}.</li> + * </ul> + * + * @return The selection model. + */ + public ListSelectionModel getSelectionModel() + { + return selectionModel; + } + + /** + * Sets the value of the {@link #selectionModel} property. The list's + * {@link #listListener} is unsubscribed from the existing selection + * model, if it exists, and re-subscribed to the new selection model. + * + * @param model The new property value + */ + public void setSelectionModel(ListSelectionModel model) + { + if (selectionModel == model) + return; + + if (selectionModel != null) + selectionModel.removeListSelectionListener(listListener); + + ListSelectionModel old = selectionModel; + selectionModel = model; + + if (selectionModel != null) + selectionModel.addListSelectionListener(listListener); + + firePropertyChange("selectionModel", old, model); + revalidate(); + repaint(); + } + + /** + * Gets the value of the UI property. + * + * @return The current property value + */ + public ListUI getUI() + { + return (ListUI) ui; + } + + /** + * Sets the value of the UI property. + * + * @param ui The new property value + */ + public void setUI(ListUI ui) + { + super.setUI(ui); + } + + /** + * Calls {@link #setUI} with the {@link ListUI} subclass + * returned from calling {@link UIManager#getUI}. + */ + public void updateUI() + { + setUI((ListUI) UIManager.getUI(this)); + } + + /** + * Return the class identifier for the list's UI property. This should + * be the constant string <code>"ListUI"</code>, and map to an + * appropriate UI class in the {@link UIManager}. + * + * @return The class identifier + */ + public String getUIClassID() + { + return "ListUI"; + } + + + /** + * Returns the current value of the {@link #prototypeCellValue} + * property. This property holds a reference to a "prototype" data value + * -- typically a String -- which is used to calculate the {@link + * #fixedCellWidth} and {@link #fixedCellHeight} properties, using the + * {@link #cellRenderer} property to acquire a component to render the + * prototype. + * + * @return The current prototype cell value + * @see #setPrototypeCellValue + */ + public Object getPrototypeCellValue() + { + return prototypeCellValue; + } + + /** + * <p>Set the {@link #prototypeCellValue} property. This property holds a + * reference to a "prototype" data value -- typically a String -- which + * is used to calculate the {@link #fixedCellWidth} and {@link + * #fixedCellHeight} properties, using the {@link #cellRenderer} property + * to acquire a component to render the prototype.</p> + * + * <p>It is important that you <em>not</em> set this value to a + * component. It has to be a <em>data value</em> such as the objects you + * would find in the list's model. Setting it to a component will have + * undefined (and undesirable) affects. </p> + * + * @param obj The new prototype cell value + * @see #getPrototypeCellValue + */ + public void setPrototypeCellValue(Object obj) + { + if (prototypeCellValue == obj) + return; + + Object old = prototypeCellValue; + Component comp = getCellRenderer() + .getListCellRendererComponent(this, obj, 0, false, false); + Dimension d = comp.getPreferredSize(); + fixedCellWidth = d.width; + fixedCellHeight = d.height; + prototypeCellValue = obj; + firePropertyChange("prototypeCellValue", old, obj); + } + + public AccessibleContext getAccessibleContext() + { + return new AccessibleJList(); + } + + /** + * Returns a size indicating how much space this list would like to + * consume, when contained in a scrollable viewport. This is part of the + * {@link Scrollable} interface, which interacts with {@link + * ScrollPaneLayout} and {@link JViewport} to define scrollable objects. + * + * @return The preferred size + */ + public Dimension getPreferredScrollableViewportSize() + { + //If the layout orientation is not VERTICAL, then this will + //return the value from getPreferredSize. The current ListUI is + //expected to override getPreferredSize to return an appropriate value. + if (getLayoutOrientation() != VERTICAL) + return getPreferredSize(); + + int size = getModel().getSize(); + + // Trivial case: if fixedCellWidth and fixedCellHeight were set + // just use them + if (fixedCellHeight != -1 && fixedCellWidth != -1) + return new Dimension(fixedCellWidth, size * fixedCellHeight); + + // If the model is empty we use 16 * the number of visible rows + // for the height and either fixedCellWidth (if set) or 256 + // for the width + if (size == 0) + { + if (fixedCellWidth == -1) + return new Dimension(256, 16 * getVisibleRowCount()); + else + return new Dimension(fixedCellWidth, 16 * getVisibleRowCount()); + } + + // Calculate the width: if fixedCellWidth was set use that, otherwise + // use the preferredWidth + int prefWidth; + if (fixedCellWidth != -1) + prefWidth = fixedCellWidth; + else + prefWidth = getPreferredSize().width; + + // Calculate the height: if fixedCellHeight was set use that, otherwise + // use the height of the first row multiplied by the number of visible + // rows + int prefHeight; + if (fixedCellHeight != -1) + prefHeight = fixedCellHeight; + else + prefHeight = getVisibleRowCount() * getCellBounds(0, 0).height; + + return new Dimension (prefWidth, prefHeight); + } + + /** + * <p>Return the number of pixels the list must scroll in order to move a + * "unit" of the list into the provided visible rectangle. When the + * provided direction is positive, the call describes a "downwards" + * scroll, which will be exposing a cell at a <em>greater</em> index in + * the list than those elements currently showing. Then the provided + * direction is negative, the call describes an "upwards" scroll, which + * will be exposing a cell at a <em>lesser</em> index in the list than + * those elements currently showing.</p> + * + * <p>If the provided orientation is <code>HORIZONTAL</code>, the above + * comments refer to "rightwards" for positive direction, and "leftwards" + * for negative.</p> + * + * + * @param visibleRect The rectangle to scroll an element into + * @param orientation One of the numeric consants <code>VERTICAL</code> + * or <code>HORIZONTAL</code> + * @param direction An integer indicating the scroll direction: positive means + * forwards (down, right), negative means backwards (up, left) + * + * @return The scrollable unit increment, in pixels + */ + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, int direction) + { + int unit = -1; + if (orientation == SwingConstants.VERTICAL) + { + int row = getFirstVisibleIndex(); + if (row == -1) + unit = 0; + else if (direction > 0) + { + // Scrolling down. + Rectangle bounds = getCellBounds(row, row); + if (bounds != null) + unit = bounds.height - (visibleRect.y - bounds.y); + else + unit = 0; + } + else + { + // Scrolling up. + Rectangle bounds = getCellBounds(row, row); + // First row. + if (row == 0 && bounds.y == visibleRect.y) + unit = 0; // No need to scroll. + else if (bounds.y == visibleRect.y) + { + // Scroll to previous row. + Point loc = bounds.getLocation(); + loc.y--; + int prev = locationToIndex(loc); + Rectangle prevR = getCellBounds(prev, prev); + if (prevR == null || prevR.y >= bounds.y) + unit = 0; // For multicolumn lists. + else + unit = prevR.height; + } + else + unit = visibleRect.y - bounds.y; + } + } + else if (orientation == SwingConstants.HORIZONTAL && getLayoutOrientation() != VERTICAL) + { + // Horizontal scrolling. + int i = locationToIndex(visibleRect.getLocation()); + if (i != -1) + { + Rectangle b = getCellBounds(i, i); + if (b != null) + { + if (b.x != visibleRect.x) + { + if (direction < 0) + unit = Math.abs(b.x - visibleRect.x); + else + unit = b.width + b.x - visibleRect.x; + } + else + unit = b.width; + } + } + } + + if (unit == -1) + { + // This fallback seems to be used by the RI for the degenerate cases + // not covered above. + Font f = getFont(); + unit = f != null ? f.getSize() : 1; + } + return unit; + } + + /** + * <p>Return the number of pixels the list must scroll in order to move a + * "block" of the list into the provided visible rectangle. When the + * provided direction is positive, the call describes a "downwards" + * scroll, which will be exposing a cell at a <em>greater</em> index in + * the list than those elements currently showing. Then the provided + * direction is negative, the call describes an "upwards" scroll, which + * will be exposing a cell at a <em>lesser</em> index in the list than + * those elements currently showing.</p> + * + * <p>If the provided orientation is <code>HORIZONTAL</code>, the above + * comments refer to "rightwards" for positive direction, and "leftwards" + * for negative.</p> + * + * + * @param visibleRect The rectangle to scroll an element into + * @param orientation One of the numeric consants <code>VERTICAL</code> + * or <code>HORIZONTAL</code> + * @param direction An integer indicating the scroll direction: positive means + * forwards (down, right), negative means backwards (up, left) + * + * @return The scrollable unit increment, in pixels + */ + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) + { + int block = -1; + if (orientation == SwingConstants.VERTICAL) + { + // Default block scroll. Special cases are handled below for + // better usability. + block = visibleRect.height; + if (direction > 0) + { + // Scroll down. + // Scroll so that after scrolling the last line aligns with + // the lower boundary of the visible area. + Point p = new Point(visibleRect.x, + visibleRect.y + visibleRect.height - 1); + int last = locationToIndex(p); + if (last != -1) + { + Rectangle lastR = getCellBounds(last, last); + if (lastR != null) + { + block = lastR.y - visibleRect.y; + if (block == 0&& last < getModel().getSize() - 1) + block = lastR.height; + } + } + } + else + { + // Scroll up. + // Scroll so that after scrolling the first line aligns with + // the upper boundary of the visible area. + Point p = new Point(visibleRect.x, + visibleRect.y - visibleRect.height); + int newFirst = locationToIndex(p); + if (newFirst != -1) + { + int first = getFirstVisibleIndex(); + if (first == -1) + first = locationToIndex(visibleRect.getLocation()); + Rectangle newFirstR = getCellBounds(newFirst, newFirst); + Rectangle firstR = getCellBounds(first, first); + if (newFirstR != null && firstR != null) + { + // Search first item that would left the current first + // item visible when scrolled to. + while (newFirstR.y + visibleRect.height + < firstR.y + firstR.height + && newFirstR.y < firstR.y) + { + newFirst++; + newFirstR = getCellBounds(newFirst, newFirst); + } + block = visibleRect.y - newFirstR.y; + if (block <= 0 && newFirstR.y > 0) + { + newFirst--; + newFirstR = getCellBounds(newFirst, newFirst); + if (newFirstR != null) + block = visibleRect.y - newFirstR.y; + } + } + } + } + } + else if (orientation == SwingConstants.HORIZONTAL + && getLayoutOrientation() != VERTICAL) + { + // Default block increment. Special cases are handled below for + // better usability. + block = visibleRect.width; + if (direction > 0) + { + // Scroll right. + Point p = new Point(visibleRect.x + visibleRect.width + 1, + visibleRect.y); + int last = locationToIndex(p); + if (last != -1) + { + Rectangle lastR = getCellBounds(last, last); + if (lastR != null) + { + block = lastR.x - visibleRect.x; + if (block < 0) + block += lastR.width; + else if (block == 0 && last < getModel().getSize() - 1) + block = lastR.width; + } + } + } + else + { + // Scroll left. + Point p = new Point(visibleRect.x - visibleRect.width, + visibleRect.y); + int first = locationToIndex(p); + if (first != -1) + { + Rectangle firstR = getCellBounds(first, first); + if (firstR != null) + { + if (firstR.x < visibleRect.x - visibleRect.width) + { + if (firstR.x + firstR.width > visibleRect.x) + block = visibleRect.x - firstR.x; + else + block = visibleRect.x - firstR.x - firstR.width; + } + else + block = visibleRect.x - firstR.x; + } + } + } + } + + return block; + } + + /** + * Gets the value of the <code>scrollableTracksViewportWidth</code> property. + * + * @return <code>true</code> if the viewport is larger (horizontally) + * than the list and the list should be expanded to fit the viewport; + * <code>false</code> if the viewport is smaller than the list and the + * list should scroll (horizontally) within the viewport + */ + public boolean getScrollableTracksViewportWidth() + { + Component parent = getParent(); + boolean retVal = false; + if (parent instanceof JViewport) + { + JViewport viewport = (JViewport) parent; + Dimension pref = getPreferredSize(); + if (viewport.getSize().width > pref.width) + retVal = true; + if ((getLayoutOrientation() == HORIZONTAL_WRAP) + && (getVisibleRowCount() <= 0)) + retVal = true; + } + return retVal; + } + + /** + * Gets the value of the </code>scrollableTracksViewportWidth</code> property. + * + * @return <code>true</code> if the viewport is larger (vertically) + * than the list and the list should be expanded to fit the viewport; + * <code>false</code> if the viewport is smaller than the list and the + * list should scroll (vertically) within the viewport + */ + public boolean getScrollableTracksViewportHeight() + { + Component parent = getParent(); + boolean retVal = false; + if (parent instanceof JViewport) + { + JViewport viewport = (JViewport) parent; + Dimension pref = getPreferredSize(); + if (viewport.getSize().height > pref.height) + retVal = true; + if ((getLayoutOrientation() == VERTICAL_WRAP) + && (getVisibleRowCount() <= 0)) + retVal = true; + } + return retVal; + } + + /** + * Returns the index of the anchor item in the current selection, or + * <code>-1</code> if there is no anchor item. + * + * @return The item index. + */ + public int getAnchorSelectionIndex() + { + return selectionModel.getAnchorSelectionIndex(); + } + + /** + * Returns the index of the lead item in the current selection, or + * <code>-1</code> if there is no lead item. + * + * @return The item index. + */ + public int getLeadSelectionIndex() + { + return selectionModel.getLeadSelectionIndex(); + } + + /** + * Returns the lowest item index in the current selection, or <code>-1</code> + * if there is no selection. + * + * @return The index. + * + * @see #getMaxSelectionIndex() + */ + public int getMinSelectionIndex() + { + return selectionModel.getMinSelectionIndex(); + } + + /** + * Returns the highest item index in the current selection, or + * <code>-1</code> if there is no selection. + * + * @return The index. + * + * @see #getMinSelectionIndex() + */ + public int getMaxSelectionIndex() + { + return selectionModel.getMaxSelectionIndex(); + } + + /** + * Clears the current selection. + */ + public void clearSelection() + { + selectionModel.clearSelection(); + } + + /** + * Sets the current selection to the items in the specified range (inclusive). + * Note that <code>anchor</code> can be less than, equal to, or greater than + * <code>lead</code>. + * + * @param anchor the index of the anchor item. + * @param lead the index of the anchor item. + */ + public void setSelectionInterval(int anchor, int lead) + { + selectionModel.setSelectionInterval(anchor, lead); + } + + /** + * Adds the specified interval to the current selection. Note that + * <code>anchor</code> can be less than, equal to, or greater than + * <code>lead</code>. + * + * @param anchor the index of the anchor item. + * @param lead the index of the lead item. + */ + public void addSelectionInterval(int anchor, int lead) + { + selectionModel.addSelectionInterval(anchor, lead); + } + + /** + * Removes the specified interval from the current selection. Note that + * <code>index0</code> can be less than, equal to, or greater than + * <code>index1</code>. + * + * @param index0 an index for one end of the range. + * @param index1 an index for the other end of the range. + */ + public void removeSelectionInterval(int index0, int index1) + { + selectionModel.removeSelectionInterval(index0, index1); + } + + /** + * Returns the <code>valueIsAdjusting</code> flag from the list's selection + * model. + * + * @return the value + */ + public boolean getValueIsAdjusting() + { + return selectionModel.getValueIsAdjusting(); + } + + /** + * Sets the <code>valueIsAdjusting</code> flag in the list's selection + * model. + * + * @param isAdjusting the new value + */ + public void setValueIsAdjusting(boolean isAdjusting) + { + selectionModel.setValueIsAdjusting(isAdjusting); + } + + /** + * Return the value of the <code>dragEnabled</code> property. + * + * @return the value + * + * @since 1.4 + */ + public boolean getDragEnabled() + { + return dragEnabled; + } + + /** + * Set the <code>dragEnabled</code> property. + * + * @param enabled new value + * + * @since 1.4 + */ + public void setDragEnabled(boolean enabled) + { + dragEnabled = enabled; + } + + /** + * Returns the layout orientation, which will be one of {@link #VERTICAL}, + * {@link #VERTICAL_WRAP} and {@link #HORIZONTAL_WRAP}. The default value + * is {@link #VERTICAL}. + * + * @return the orientation. + * + * @see #setLayoutOrientation(int) + * @since 1.4 + */ + public int getLayoutOrientation() + { + return layoutOrientation; + } + + /** + * Sets the layout orientation (this is a bound property with the name + * 'layoutOrientation'). Valid orientations are {@link #VERTICAL}, + * {@link #VERTICAL_WRAP} and {@link #HORIZONTAL_WRAP}. + * + * @param orientation the orientation. + * + * @throws IllegalArgumentException if <code>orientation</code> is not one + * of the specified values. + * @since 1.4 + * @see #getLayoutOrientation() + */ + public void setLayoutOrientation(int orientation) + { + if (orientation < JList.VERTICAL || orientation > JList.HORIZONTAL_WRAP) + throw new IllegalArgumentException(); + if (layoutOrientation == orientation) + return; + + int old = layoutOrientation; + layoutOrientation = orientation; + firePropertyChange("layoutOrientation", old, orientation); + } + + /** + * Returns the bounds of the rectangle that encloses both list cells + * with index0 and index1. + * + * @param index0 the index of the first cell + * @param index1 the index of the second cell + * + * @return the bounds of the rectangle that encloses both list cells + * with index0 and index1, <code>null</code> if one of the indices is + * not valid + */ + public Rectangle getCellBounds(int index0, int index1) + { + ListUI ui = getUI(); + Rectangle bounds = null; + if (ui != null) + { + bounds = ui.getCellBounds(this, index0, index1); + } + // When the UI is null, this method also returns null in the RI. + return bounds; + } + + /** + * Returns the index of the next list element (beginning at + * <code>startIndex</code> and moving in the specified direction through the + * list, looping around if necessary) that starts with <code>prefix</code> + * (ignoring case). + * + * @param prefix the prefix to search for in the cell values + * @param startIndex the index where to start searching from + * @param direction the search direction, either {@link Position.Bias#Forward} + * or {@link Position.Bias#Backward} (<code>null</code> is interpreted + * as {@link Position.Bias#Backward}. + * + * @return the index of the found element or -1 if no such element has + * been found + * + * @throws IllegalArgumentException if prefix is <code>null</code> or + * startIndex is not valid + * + * @since 1.4 + */ + public int getNextMatch(String prefix, int startIndex, + Position.Bias direction) + { + if (prefix == null) + throw new IllegalArgumentException("The argument 'prefix' must not be" + + " null."); + if (startIndex < 0) + throw new IllegalArgumentException("The argument 'startIndex' must not" + + " be less than zero."); + + int size = model.getSize(); + if (startIndex >= model.getSize()) + throw new IllegalArgumentException("The argument 'startIndex' must not" + + " be greater than the number of" + + " elements in the ListModel."); + + int result = -1; + int current = startIndex; + int delta = -1; + int itemCount = model.getSize(); + boolean finished = false; + prefix = prefix.toUpperCase(); + + if (direction == Position.Bias.Forward) + delta = 1; + while (!finished) + { + String itemStr = model.getElementAt(current).toString().toUpperCase(); + if (itemStr.startsWith(prefix)) + return current; + current = (current + delta); + if (current == -1) + current += itemCount; + else + current = current % itemCount; + finished = current == startIndex; + } + return result; + } + + /** + * Returns a string describing the attributes for the <code>JList</code> + * component, for use in debugging. The return value is guaranteed to be + * non-<code>null</code>, but the format of the string may vary between + * implementations. + * + * @return A string describing the attributes of the <code>JList</code>. + */ + protected String paramString() + { + CPStringBuilder sb = new CPStringBuilder(super.paramString()); + sb.append(",fixedCellHeight=").append(getFixedCellHeight()); + sb.append(",fixedCellWidth=").append(getFixedCellWidth()); + sb.append(",selectionBackground="); + if (getSelectionBackground() != null) + sb.append(getSelectionBackground()); + sb.append(",selectionForeground="); + if (getSelectionForeground() != null) + sb.append(getSelectionForeground()); + sb.append(",visibleRowCount=").append(getVisibleRowCount()); + sb.append(",layoutOrientation=").append(getLayoutOrientation()); + return sb.toString(); + } +} |