| /* BasicListUI.java -- |
| Copyright (C) 2002, 2004 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., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 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.plaf.basic; |
| |
| import java.awt.Color; |
| import java.awt.Component; |
| import java.awt.Dimension; |
| import java.awt.Graphics; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.event.FocusEvent; |
| import java.awt.event.FocusListener; |
| import java.awt.event.MouseEvent; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| |
| import javax.swing.JComponent; |
| import javax.swing.JList; |
| import javax.swing.ListCellRenderer; |
| import javax.swing.ListModel; |
| import javax.swing.ListSelectionModel; |
| import javax.swing.UIDefaults; |
| import javax.swing.UIManager; |
| import javax.swing.event.ListDataEvent; |
| import javax.swing.event.ListDataListener; |
| import javax.swing.event.ListSelectionEvent; |
| import javax.swing.event.ListSelectionListener; |
| import javax.swing.event.MouseInputListener; |
| import javax.swing.plaf.ComponentUI; |
| import javax.swing.plaf.ListUI; |
| |
| /** |
| * The Basic Look and Feel UI delegate for the |
| * JList. |
| */ |
| public class BasicListUI extends ListUI |
| { |
| /** |
| * A helper class which listens for {@link FocusEvents} |
| * from the JList. |
| */ |
| class FocusHandler implements FocusListener |
| { |
| /** |
| * Called when the JList acquires focus. |
| * |
| * @param e The FocusEvent representing focus acquisition |
| */ |
| public void focusGained(FocusEvent e) |
| { |
| repaintCellFocus(); |
| } |
| |
| /** |
| * Called when the JList loses focus. |
| * |
| * @param e The FocusEvent representing focus loss |
| */ |
| public void focusLost(FocusEvent e) |
| { |
| repaintCellFocus(); |
| } |
| |
| /** |
| * Helper method to repaint the focused cell's |
| * lost or acquired focus state. |
| */ |
| void repaintCellFocus() |
| { |
| } |
| } |
| |
| /** |
| * A helper class which listens for {@link ListDataEvent}s generated by |
| * the {@link JList}'s {@link ListModel}. |
| * |
| * @see javax.swing.JList#model |
| */ |
| class ListDataHandler implements ListDataListener |
| { |
| /** |
| * Called when a general change has happened in the model which cannot |
| * be represented in terms of a simple addition or deletion. |
| * |
| * @param e The event representing the change |
| */ |
| public void contentsChanged(ListDataEvent e) |
| { |
| BasicListUI.this.damageLayout(); |
| } |
| |
| /** |
| * Called when an interval of objects has been added to the model. |
| * |
| * @param e The event representing the addition |
| */ |
| public void intervalAdded(ListDataEvent e) |
| { |
| BasicListUI.this.damageLayout(); |
| } |
| |
| /** |
| * Called when an inteval of objects has been removed from the model. |
| * |
| * @param e The event representing the removal |
| */ |
| public void intervalRemoved(ListDataEvent e) |
| { |
| BasicListUI.this.damageLayout(); |
| } |
| } |
| |
| /** |
| * A helper class which listens for {@link ListSelectionEvent}s |
| * from the {@link JList}'s {@link ListSelectionModel}. |
| */ |
| class ListSelectionHandler implements ListSelectionListener |
| { |
| /** |
| * Called when the list selection changes. |
| * |
| * @param e The event representing the change |
| */ |
| public void valueChanged(ListSelectionEvent e) |
| { |
| } |
| } |
| |
| /** |
| * A helper class which listens for {@link MouseEvent}s |
| * from the {@link JList}. |
| */ |
| class MouseInputHandler implements MouseInputListener |
| { |
| /** |
| * Called when a mouse button press/release cycle completes |
| * on the {@link JList} |
| * |
| * @param event The event representing the mouse click |
| */ |
| public void mouseClicked(MouseEvent event) |
| { |
| } |
| |
| /** |
| * Called when a mouse button is pressed down on the |
| * {@link JList}. |
| * |
| * @param event The event representing the mouse press |
| */ |
| public void mousePressed(MouseEvent event) |
| { |
| int row = BasicListUI.this.convertYToRow(event.getY()); |
| if (row == -1) |
| return; |
| |
| BasicListUI.this.list.setSelectedIndex(row); |
| } |
| |
| /** |
| * Called when a mouse button is released on |
| * the {@link JList} |
| * |
| * @param event The event representing the mouse press |
| */ |
| public void mouseReleased(MouseEvent event) |
| { |
| } |
| |
| /** |
| * Called when the mouse pointer enters the area bounded |
| * by the {@link JList} |
| * |
| * @param event The event representing the mouse entry |
| */ |
| public void mouseEntered(MouseEvent event) |
| { |
| } |
| |
| /** |
| * Called when the mouse pointer leaves the area bounded |
| * by the {@link JList} |
| * |
| * @param event The event representing the mouse exit |
| */ |
| public void mouseExited(MouseEvent event) |
| { |
| } |
| |
| /** |
| * Called when the mouse pointer moves over the area bounded |
| * by the {@link JList} while a button is held down. |
| * |
| * @param event The event representing the mouse drag |
| */ |
| public void mouseDragged(MouseEvent event) |
| { |
| } |
| |
| /** |
| * Called when the mouse pointer moves over the area bounded |
| * by the {@link JList}. |
| * |
| * @param event The event representing the mouse move |
| */ |
| public void mouseMoved(MouseEvent event) |
| { |
| } |
| } |
| |
| /** |
| * Helper class which listens to {@link PropertyChangeEvent}s |
| * from the {@link JList}. |
| */ |
| class PropertyChangeHandler implements PropertyChangeListener |
| { |
| /** |
| * Called when the {@link JList} changes one of its bound properties. |
| * |
| * @param e The event representing the property change |
| */ |
| public void propertyChange(PropertyChangeEvent e) |
| { |
| if (e.getSource() == BasicListUI.this.list) |
| { |
| if (e.getOldValue() != null && e.getOldValue() instanceof ListModel) |
| ((ListModel) e.getOldValue()).removeListDataListener(BasicListUI.this.listDataListener); |
| |
| if (e.getNewValue() != null && e.getNewValue() instanceof ListModel) |
| ((ListModel) e.getNewValue()).addListDataListener(BasicListUI.this.listDataListener); |
| } |
| BasicListUI.this.damageLayout(); |
| } |
| } |
| |
| /** |
| * Creates a new BasicListUI for the component. |
| * |
| * @param c The component to create a UI for |
| * |
| * @return A new UI |
| */ |
| public static ComponentUI createUI(final JComponent c) |
| { |
| return new BasicListUI(); |
| } |
| |
| /** The current focus listener. */ |
| FocusHandler focusListener; |
| |
| /** The data listener listening to the model. */ |
| ListDataHandler listDataListener; |
| |
| /** The selection listener listening to the selection model. */ |
| ListSelectionHandler listSelectionListener; |
| |
| /** The mouse listener listening to the list. */ |
| MouseInputHandler mouseInputListener; |
| |
| /** The property change listener listening to the list. */ |
| PropertyChangeHandler propertyChangeListener; |
| |
| /** Saved reference to the list this UI was created for. */ |
| JList list; |
| |
| /** The height of a single cell in the list. */ |
| int cellHeight; |
| |
| /** The width of a single cell in the list. */ |
| int cellWidth; |
| |
| /** |
| * An array of varying heights of cells in the list, in cases where each |
| * cell might have a different height. |
| */ |
| int[] cellHeights; |
| |
| /** |
| * A simple counter. When nonzero, indicates that the UI class is out of |
| * date with respect to the underlying list, and must recalculate the |
| * list layout before painting or performing size calculations. |
| */ |
| int updateLayoutStateNeeded; |
| |
| /** |
| * Calculate the height of a particular row. If there is a fixed {@link |
| * #cellHeight}, return it; otherwise return the specific row height |
| * requested from the {@link #cellHeights} array. If the requested row |
| * is invalid, return <code>-1</code>. |
| * |
| * @param row The row to get the height of |
| * |
| * @return The height, in pixels, of the specified row |
| */ |
| int getRowHeight(int row) |
| { |
| if (row < 0 || row >= cellHeights.length) |
| return -1; |
| else if (cellHeight != -1) |
| return cellHeight; |
| else |
| return cellHeights[row]; |
| } |
| |
| /** |
| * Calculate the bounds of a particular cell, considering the upper left |
| * corner of the list as the origin position <code>(0,0)</code>. |
| * |
| * @param l Ignored; calculates over <code>this.list</code> |
| * @param index1 The first row to include in the bounds |
| * @param index2 The last row to incude in the bounds |
| * |
| * @return A rectangle encompassing the range of rows between |
| * <code>index1</code> and <code>index2</code> inclusive |
| */ |
| public Rectangle getCellBounds(JList l, int index1, int index2) |
| { |
| maybeUpdateLayoutState(); |
| |
| if (l != list || cellWidth == -1) |
| return null; |
| |
| int lo = Math.min(index1, index2); |
| int hi = Math.max(index1, index2); |
| Rectangle lobounds = new Rectangle(0, convertRowToY(lo), cellWidth, |
| getRowHeight(lo)); |
| Rectangle hibounds = new Rectangle(0, convertRowToY(hi), cellWidth, |
| getRowHeight(hi)); |
| |
| return lobounds.union(hibounds); |
| } |
| |
| /** |
| * Calculate the Y coordinate of the upper edge of a particular row, |
| * considering the Y coordinate <code>0</code> to occur at the top of the |
| * list. |
| * |
| * @param row The row to calculate the Y coordinate of |
| * |
| * @return The Y coordinate of the specified row, or <code>-1</code> if |
| * the specified row number is invalid |
| */ |
| int convertRowToY(int row) |
| { |
| int y = 0; |
| for (int i = 0; i < row; ++i) |
| { |
| int h = getRowHeight(i); |
| if (h == -1) |
| return -1; |
| y += h; |
| } |
| return y; |
| } |
| |
| /** |
| * Calculate the row number containing a particular Y coordinate, |
| * considering the Y coodrinate <code>0</code> to occur at the top of the |
| * list. |
| * |
| * @param y0 The Y coordinate to calculate the row number for |
| * |
| * @return The row number containing the specified Y value, or <code>-1</code> |
| * if the specified Y coordinate is invalid |
| */ |
| int convertYToRow(int y0) |
| { |
| for (int row = 0; row < cellHeights.length; ++row) |
| { |
| int h = getRowHeight(row); |
| |
| if (y0 < h) |
| return row; |
| y0 -= h; |
| } |
| return -1; |
| } |
| |
| /** |
| * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link |
| * #cellWidth} properties by examining the variouis properties of the |
| * {@link JList}. |
| */ |
| void updateLayoutState() |
| { |
| int nrows = list.getModel().getSize(); |
| cellHeight = -1; |
| cellWidth = -1; |
| if (cellHeights == null || cellHeights.length != nrows) |
| cellHeights = new int[nrows]; |
| if (list.getFixedCellHeight() == -1 || list.getFixedCellWidth() == -1) |
| { |
| ListCellRenderer rend = list.getCellRenderer(); |
| for (int i = 0; i < nrows; ++i) |
| { |
| Component flyweight = rend.getListCellRendererComponent(list, |
| list.getModel() |
| .getElementAt(i), |
| 0, false, |
| false); |
| Dimension dim = flyweight.getPreferredSize(); |
| cellHeights[i] = dim.height; |
| cellWidth = Math.max(cellWidth, dim.width); |
| } |
| } |
| else |
| { |
| cellHeight = list.getFixedCellHeight(); |
| cellWidth = list.getFixedCellWidth(); |
| } |
| } |
| |
| /** |
| * Marks the current layout as damaged and requests revalidation from the |
| * JList. |
| * |
| * @see #updateLayoutStateNeeded |
| */ |
| void damageLayout() |
| { |
| updateLayoutStateNeeded = 1; |
| list.revalidate(); |
| } |
| |
| /** |
| * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded} |
| * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero. |
| */ |
| void maybeUpdateLayoutState() |
| { |
| if (updateLayoutStateNeeded != 0) |
| { |
| updateLayoutState(); |
| updateLayoutStateNeeded = 0; |
| } |
| } |
| |
| /** |
| * Creates a new BasicListUI object. |
| */ |
| public BasicListUI() |
| { |
| focusListener = new FocusHandler(); |
| listDataListener = new ListDataHandler(); |
| listSelectionListener = new ListSelectionHandler(); |
| mouseInputListener = new MouseInputHandler(); |
| propertyChangeListener = new PropertyChangeHandler(); |
| updateLayoutStateNeeded = 1; |
| } |
| |
| /** |
| * Installs various default settings (mostly colors) from the {@link |
| * UIDefaults} into the {@link JList} |
| * |
| * @see #uninstallDefaults |
| */ |
| void installDefaults() |
| { |
| UIDefaults defaults = UIManager.getLookAndFeelDefaults(); |
| list.setForeground(defaults.getColor("List.foreground")); |
| list.setBackground(defaults.getColor("List.background")); |
| list.setSelectionForeground(defaults.getColor("List.selectionForeground")); |
| list.setSelectionBackground(defaults.getColor("List.selectionBackground")); |
| list.setOpaque(true); |
| } |
| |
| /** |
| * Resets to <code>null</code> those defaults which were installed in |
| * {@link #installDefaults} |
| */ |
| void uninstallDefaults() |
| { |
| UIDefaults defaults = UIManager.getLookAndFeelDefaults(); |
| list.setForeground(null); |
| list.setBackground(null); |
| list.setSelectionForeground(null); |
| list.setSelectionBackground(null); |
| } |
| |
| /** |
| * Attaches all the listeners we have in the UI class to the {@link |
| * JList}, its model and its selection model. |
| * |
| * @see #uninstallListeners |
| */ |
| void installListeners() |
| { |
| list.addFocusListener(focusListener); |
| list.getModel().addListDataListener(listDataListener); |
| list.addListSelectionListener(listSelectionListener); |
| list.addMouseListener(mouseInputListener); |
| list.addMouseMotionListener(mouseInputListener); |
| list.addPropertyChangeListener(propertyChangeListener); |
| } |
| |
| /** |
| * Detaches all the listeners we attached in {@link #installListeners}. |
| */ |
| void uninstallListeners() |
| { |
| list.removeFocusListener(focusListener); |
| list.getModel().removeListDataListener(listDataListener); |
| list.removeListSelectionListener(listSelectionListener); |
| list.removeMouseListener(mouseInputListener); |
| list.removeMouseMotionListener(mouseInputListener); |
| list.removePropertyChangeListener(propertyChangeListener); |
| } |
| |
| /** |
| * Installs keyboard actions for this UI in the {@link JList}. |
| */ |
| void installKeyboardActions() |
| { |
| } |
| |
| /** |
| * Uninstalls keyboard actions for this UI in the {@link JList}. |
| */ |
| void uninstallKeyboardActions() |
| { |
| } |
| |
| /** |
| * Installs the various aspects of the UI in the {@link JList}. In |
| * particular, calls {@link #installDefaults}, {@link #installListeners} |
| * and {@link #installKeyboardActions}. Also saves a reference to the |
| * provided component, cast to a {@link JList}. |
| * |
| * @param c The {@link JList} to install the UI into |
| */ |
| public void installUI(final JComponent c) |
| { |
| super.installUI(c); |
| list = (JList) c; |
| installDefaults(); |
| installListeners(); |
| installKeyboardActions(); |
| maybeUpdateLayoutState(); |
| } |
| |
| /** |
| * Uninstalls all the aspects of the UI which were installed in {@link |
| * #installUI}. When finished uninstalling, drops the saved reference to |
| * the {@link JList}. |
| * |
| * @param c Ignored; the UI is uninstalled from the {@link JList} |
| * reference saved during the call to {@link #installUI} |
| */ |
| public void uninstallUI(final JComponent c) |
| { |
| uninstallKeyboardActions(); |
| uninstallListeners(); |
| uninstallDefaults(); |
| list = null; |
| } |
| |
| /** |
| * Gets the maximum size this list can assume. |
| * |
| * @param c The component to measure the size of |
| * |
| * @return A new Dimension representing the component's maximum size |
| */ |
| public Dimension getMaximumSize(JComponent c) |
| { |
| return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Gets the size this list would prefer to assume. This is calculated by |
| * calling {@link #getCellBounds} over the entire list. |
| * |
| * @param c Ignored; uses the saved {@link JList} reference |
| * |
| * @return DOCUMENT ME! |
| */ |
| public Dimension getPreferredSize(JComponent c) |
| { |
| if (list.getModel().getSize() == 0) |
| return new Dimension(0, 0); |
| Rectangle bounds = getCellBounds(list, 0, list.getModel().getSize() - 1); |
| return bounds.getSize(); |
| } |
| |
| /** |
| * Paints the packground of the list using the background color |
| * of the specified component. |
| * |
| * @param g The graphics context to paint in |
| * @param c The component to paint the background of |
| */ |
| public void paintBackground(Graphics g, JComponent c) |
| { |
| Dimension size = getPreferredSize(c); |
| Color save = g.getColor(); |
| g.setColor(c.getBackground()); |
| g.fillRect(0, 0, size.width, size.height); |
| g.setColor(save); |
| } |
| |
| /** |
| * Paints a single cell in the list. |
| * |
| * @param g The graphics context to paint in |
| * @param row The row number to paint |
| * @param bounds The bounds of the cell to paint, assuming a coordinate |
| * system beginning at <code>(0,0)</code> in the upper left corner of the |
| * list |
| * @param rend A cell renderer to paint with |
| * @param data The data to provide to the cell renderer |
| * @param sel A selection model to provide to the cell renderer |
| * @param lead The lead selection index of the list |
| */ |
| void paintCell(Graphics g, int row, Rectangle bounds, ListCellRenderer rend, |
| ListModel data, ListSelectionModel sel, int lead) |
| { |
| boolean is_sel = list.isSelectedIndex(row); |
| boolean has_focus = false; |
| Component comp = rend.getListCellRendererComponent(list, |
| data.getElementAt(row), |
| 0, is_sel, has_focus); |
| g.translate(bounds.x, bounds.y); |
| comp.setBounds(new Rectangle(0, 0, bounds.width, bounds.height)); |
| comp.paint(g); |
| g.translate(-bounds.x, -bounds.y); |
| } |
| |
| /** |
| * Paints the list by calling {@link #paintBackground} and then repeatedly |
| * calling {@link #paintCell} for each visible cell in the list. |
| * |
| * @param g The graphics context to paint with |
| * @param c Ignored; uses the saved {@link JList} reference |
| */ |
| public void paint(Graphics g, JComponent c) |
| { |
| int nrows = list.getModel().getSize(); |
| if (nrows == 0) |
| return; |
| |
| maybeUpdateLayoutState(); |
| ListCellRenderer render = list.getCellRenderer(); |
| ListModel model = list.getModel(); |
| ListSelectionModel sel = list.getSelectionModel(); |
| int lead = sel.getLeadSelectionIndex(); |
| Rectangle clip = g.getClipBounds(); |
| paintBackground(g, list); |
| |
| for (int row = 0; row < nrows; ++row) |
| { |
| Rectangle bounds = getCellBounds(list, row, row); |
| if (bounds.intersects(clip)) |
| paintCell(g, row, bounds, render, model, sel, lead); |
| } |
| } |
| |
| public int locationToIndex(JList list, Point location) |
| { |
| return convertYToRow(location.y); |
| } |
| |
| public Point indexToLocation(JList list, int index) |
| { |
| return new Point(0, convertRowToY(index)); |
| } |
| } |