diff options
Diffstat (limited to 'libjava/classpath/javax/swing/plaf/basic/BasicListUI.java')
-rw-r--r-- | libjava/classpath/javax/swing/plaf/basic/BasicListUI.java | 1421 |
1 files changed, 1421 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java new file mode 100644 index 000000000..0e33957f4 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java @@ -0,0 +1,1421 @@ +/* BasicListUI.java -- + Copyright (C) 2002, 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.plaf.basic; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +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.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.CellRendererPane; +import javax.swing.DefaultListSelectionModel; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.TransferHandler; +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.ActionMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.ListUI; +import javax.swing.plaf.UIResource; + +/** + * The Basic Look and Feel UI delegate for the + * JList. + */ +public class BasicListUI extends ListUI +{ + + /** + * A helper class which listens for {@link FocusEvent}s + * from the JList. + */ + public 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. + */ + protected void repaintCellFocus() + { + // TODO: Implement this properly. + } + } + + /** + * A helper class which listens for {@link ListDataEvent}s generated by + * the {@link JList}'s {@link ListModel}. + * + * @see javax.swing.JList#getModel() + */ + public 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) + { + updateLayoutStateNeeded |= modelChanged; + list.revalidate(); + } + + /** + * Called when an interval of objects has been added to the model. + * + * @param e The event representing the addition + */ + public void intervalAdded(ListDataEvent e) + { + updateLayoutStateNeeded |= modelChanged; + list.revalidate(); + } + + /** + * Called when an inteval of objects has been removed from the model. + * + * @param e The event representing the removal + */ + public void intervalRemoved(ListDataEvent e) + { + updateLayoutStateNeeded |= modelChanged; + list.revalidate(); + } + } + + /** + * A helper class which listens for {@link ListSelectionEvent}s + * from the {@link JList}'s {@link ListSelectionModel}. + */ + public class ListSelectionHandler implements ListSelectionListener + { + /** + * Called when the list selection changes. + * + * @param e The event representing the change + */ + public void valueChanged(ListSelectionEvent e) + { + int index1 = e.getFirstIndex(); + int index2 = e.getLastIndex(); + Rectangle damaged = getCellBounds(list, index1, index2); + if (damaged != null) + list.repaint(damaged); + } + } + + /** + * This class is used to mimmic the behaviour of the JDK when registering + * keyboard actions. It is the same as the private class used in JComponent + * for the same reason. This class receives an action event and dispatches + * it to the true receiver after altering the actionCommand property of the + * event. + */ + private static class ActionListenerProxy + extends AbstractAction + { + ActionListener target; + String bindingCommandName; + + public ActionListenerProxy(ActionListener li, + String cmd) + { + target = li; + bindingCommandName = cmd; + } + + public void actionPerformed(ActionEvent e) + { + ActionEvent derivedEvent = new ActionEvent(e.getSource(), + e.getID(), + bindingCommandName, + e.getModifiers()); + target.actionPerformed(derivedEvent); + } + } + + /** + * Implements the action for the JList's keyboard commands. + */ + private class ListAction + extends AbstractAction + { + // TODO: Maybe make a couple of classes out of this bulk Action. + // Form logical groups of Actions when doing this. + + /** + * Creates a new ListAction for the specified command. + * + * @param cmd the actionCommand to set + */ + ListAction(String cmd) + { + putValue(ACTION_COMMAND_KEY, cmd); + } + + public void actionPerformed(ActionEvent e) + { + int lead = list.getLeadSelectionIndex(); + int max = list.getModel().getSize() - 1; + DefaultListSelectionModel selModel + = (DefaultListSelectionModel) list.getSelectionModel(); + String command = e.getActionCommand(); + // Do nothing if list is empty + if (max == -1) + return; + + if (command.equals("selectNextRow")) + { + selectNextIndex(); + } + else if (command.equals("selectPreviousRow")) + { + selectPreviousIndex(); + } + else if (command.equals("clearSelection")) + { + list.clearSelection(); + } + else if (command.equals("selectAll")) + { + list.setSelectionInterval(0, max); + // this next line is to restore the lead selection index to the old + // position, because select-all should not change the lead index + list.addSelectionInterval(lead, lead); + } + else if (command.equals("selectLastRow")) + { + list.setSelectedIndex(list.getModel().getSize() - 1); + } + else if (command.equals("selectLastRowChangeLead")) + { + selModel.moveLeadSelectionIndex(list.getModel().getSize() - 1); + } + else if (command.equals("scrollDownExtendSelection")) + { + int target; + if (lead == list.getLastVisibleIndex()) + { + target = Math.min(max, lead + (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getLastVisibleIndex(); + selModel.setLeadSelectionIndex(target); + } + else if (command.equals("scrollDownChangeLead")) + { + int target; + if (lead == list.getLastVisibleIndex()) + { + target = Math.min(max, lead + (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getLastVisibleIndex(); + selModel.moveLeadSelectionIndex(target); + } + else if (command.equals("scrollUpExtendSelection")) + { + int target; + if (lead == list.getFirstVisibleIndex()) + { + target = Math.max(0, lead - (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getFirstVisibleIndex(); + selModel.setLeadSelectionIndex(target); + } + else if (command.equals("scrollUpChangeLead")) + { + int target; + if (lead == list.getFirstVisibleIndex()) + { + target = Math.max(0, lead - (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getFirstVisibleIndex(); + selModel.moveLeadSelectionIndex(target); + } + else if (command.equals("selectNextRowExtendSelection")) + { + selModel.setLeadSelectionIndex(Math.min(lead + 1, max)); + } + else if (command.equals("selectFirstRow")) + { + list.setSelectedIndex(0); + } + else if (command.equals("selectFirstRowChangeLead")) + { + selModel.moveLeadSelectionIndex(0); + } + else if (command.equals("selectFirstRowExtendSelection")) + { + selModel.setLeadSelectionIndex(0); + } + else if (command.equals("selectPreviousRowExtendSelection")) + { + selModel.setLeadSelectionIndex(Math.max(0, lead - 1)); + } + else if (command.equals("scrollUp")) + { + int target; + if (lead == list.getFirstVisibleIndex()) + { + target = Math.max(0, lead - (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getFirstVisibleIndex(); + list.setSelectedIndex(target); + } + else if (command.equals("selectLastRowExtendSelection")) + { + selModel.setLeadSelectionIndex(list.getModel().getSize() - 1); + } + else if (command.equals("scrollDown")) + { + int target; + if (lead == list.getLastVisibleIndex()) + { + target = Math.min(max, lead + (list.getLastVisibleIndex() + - list.getFirstVisibleIndex() + 1)); + } + else + target = list.getLastVisibleIndex(); + list.setSelectedIndex(target); + } + else if (command.equals("selectNextRowChangeLead")) + { + if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + selectNextIndex(); + else + { + selModel.moveLeadSelectionIndex(Math.min(max, lead + 1)); + } + } + else if (command.equals("selectPreviousRowChangeLead")) + { + if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + selectPreviousIndex(); + else + { + selModel.moveLeadSelectionIndex(Math.max(0, lead - 1)); + } + } + else if (command.equals("addToSelection")) + { + list.addSelectionInterval(lead, lead); + } + else if (command.equals("extendTo")) + { + selModel.setSelectionInterval(selModel.getAnchorSelectionIndex(), + lead); + } + else if (command.equals("toggleAndAnchor")) + { + if (!list.isSelectedIndex(lead)) + list.addSelectionInterval(lead, lead); + else + list.removeSelectionInterval(lead, lead); + selModel.setAnchorSelectionIndex(lead); + } + else + { + // DEBUG: uncomment the following line to print out + // key bindings that aren't implemented yet + + // System.out.println ("not implemented: "+e.getActionCommand()); + } + + list.ensureIndexIsVisible(list.getLeadSelectionIndex()); + } + } + + /** + * A helper class which listens for {@link MouseEvent}s + * from the {@link JList}. + */ + public 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) + { + Point click = event.getPoint(); + int index = locationToIndex(list, click); + if (index == -1) + return; + if (event.isShiftDown()) + { + if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) + list.setSelectedIndex(index); + else if (list.getSelectionMode() == + ListSelectionModel.SINGLE_INTERVAL_SELECTION) + // COMPAT: the IBM VM is compatible with the following line of code. + // However, compliance with Sun's VM would correspond to replacing + // getAnchorSelectionIndex() with getLeadSelectionIndex().This is + // both unnatural and contradictory to the way they handle other + // similar UI interactions. + list.setSelectionInterval(list.getAnchorSelectionIndex(), index); + else + // COMPAT: both Sun and IBM are compatible instead with: + // list.setSelectionInterval + // (list.getLeadSelectionIndex(),index); + // Note that for IBM this is contradictory to what they did in + // the above situation for SINGLE_INTERVAL_SELECTION. + // The most natural thing to do is the following: + if (list.isSelectedIndex(list.getAnchorSelectionIndex())) + list.getSelectionModel().setLeadSelectionIndex(index); + else + list.addSelectionInterval(list.getAnchorSelectionIndex(), index); + } + else if (event.isControlDown()) + { + if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) + list.setSelectedIndex(index); + else if (list.isSelectedIndex(index)) + list.removeSelectionInterval(index, index); + else + list.addSelectionInterval(index, index); + } + else + list.setSelectedIndex(index); + + list.ensureIndexIsVisible(list.getLeadSelectionIndex()); + } + + /** + * 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) + { + // We need to explicitly request focus. + list.requestFocusInWindow(); + } + + /** + * Called when a mouse button is released on + * the {@link JList} + * + * @param event The event representing the mouse press + */ + public void mouseReleased(MouseEvent event) + { + // TODO: What should be done here, if anything? + } + + /** + * 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) + { + // TODO: What should be done here, if anything? + } + + /** + * 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) + { + // TODO: What should be done here, if anything? + } + + /** + * 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) + { + Point click = event.getPoint(); + int index = locationToIndex(list, click); + if (index == -1) + return; + if (!event.isShiftDown() && !event.isControlDown()) + list.setSelectedIndex(index); + + list.ensureIndexIsVisible(list.getLeadSelectionIndex()); + } + + /** + * 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) + { + // TODO: What should be done here, if anything? + } + } + + /** + * Helper class which listens to {@link PropertyChangeEvent}s + * from the {@link JList}. + */ + public 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.getPropertyName().equals("model")) + { + if (e.getOldValue() != null && e.getOldValue() instanceof ListModel) + { + ListModel oldModel = (ListModel) e.getOldValue(); + oldModel.removeListDataListener(listDataListener); + } + if (e.getNewValue() != null && e.getNewValue() instanceof ListModel) + { + ListModel newModel = (ListModel) e.getNewValue(); + newModel.addListDataListener(BasicListUI.this.listDataListener); + } + + updateLayoutStateNeeded |= modelChanged; + } + else if (e.getPropertyName().equals("selectionModel")) + updateLayoutStateNeeded |= selectionModelChanged; + else if (e.getPropertyName().equals("font")) + updateLayoutStateNeeded |= fontChanged; + else if (e.getPropertyName().equals("fixedCellWidth")) + updateLayoutStateNeeded |= fixedCellWidthChanged; + else if (e.getPropertyName().equals("fixedCellHeight")) + updateLayoutStateNeeded |= fixedCellHeightChanged; + else if (e.getPropertyName().equals("prototypeCellValue")) + updateLayoutStateNeeded |= prototypeCellValueChanged; + else if (e.getPropertyName().equals("cellRenderer")) + updateLayoutStateNeeded |= cellRendererChanged; + } + } + + /** + * A constant to indicate that the model has changed. + */ + protected static final int modelChanged = 1; + + /** + * A constant to indicate that the selection model has changed. + */ + protected static final int selectionModelChanged = 2; + + /** + * A constant to indicate that the font has changed. + */ + protected static final int fontChanged = 4; + + /** + * A constant to indicate that the fixedCellWidth has changed. + */ + protected static final int fixedCellWidthChanged = 8; + + /** + * A constant to indicate that the fixedCellHeight has changed. + */ + protected static final int fixedCellHeightChanged = 16; + + /** + * A constant to indicate that the prototypeCellValue has changed. + */ + protected static final int prototypeCellValueChanged = 32; + + /** + * A constant to indicate that the cellRenderer has changed. + */ + protected static final int cellRendererChanged = 64; + + /** + * 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. */ + protected FocusListener focusListener; + + /** The data listener listening to the model. */ + protected ListDataListener listDataListener; + + /** The selection listener listening to the selection model. */ + protected ListSelectionListener listSelectionListener; + + /** The mouse listener listening to the list. */ + protected MouseInputListener mouseInputListener; + + /** The property change listener listening to the list. */ + protected PropertyChangeListener propertyChangeListener; + + /** Saved reference to the list this UI was created for. */ + protected JList list; + + /** + * The height of a single cell in the list. This field is used when the + * fixedCellHeight property of the list is set. Otherwise this field is + * set to <code>-1</code> and {@link #cellHeights} is used instead. + */ + protected int cellHeight; + + /** The width of a single cell in the list. */ + protected int cellWidth; + + /** + * An array of varying heights of cells in the list, in cases where each + * cell might have a different height. This field is used when the + * <code>fixedCellHeight</code> property of the list is not set. Otherwise + * this field is <code>null</code> and {@link #cellHeight} is used. + */ + protected int[] cellHeights; + + /** + * A bitmask that indicates which properties of the JList have changed. + * 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. + * + * @see #modelChanged + * @see #selectionModelChanged + * @see #fontChanged + * @see #fixedCellWidthChanged + * @see #fixedCellHeightChanged + * @see #prototypeCellValueChanged + * @see #cellRendererChanged + */ + protected int updateLayoutStateNeeded; + + /** + * The {@link CellRendererPane} that is used for painting. + */ + protected CellRendererPane rendererPane; + + /** The action bound to KeyStrokes. */ + ListAction action; + + /** + * 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 + */ + protected int getRowHeight(int row) + { + int height; + if (cellHeights == null) + height = cellHeight; + else + { + if (row < 0 || row >= cellHeights.length) + height = -1; + else + height = cellHeights[row]; + } + return height; + } + + /** + * 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, or null + * such a rectangle couldn't be calculated for the given indexes. + */ + public Rectangle getCellBounds(JList l, int index1, int index2) + { + maybeUpdateLayoutState(); + + if (l != list || cellWidth == -1) + return null; + + int minIndex = Math.min(index1, index2); + int maxIndex = Math.max(index1, index2); + Point loc = indexToLocation(list, minIndex); + + // When the layoutOrientation is VERTICAL, then the width == the list + // width. Otherwise the cellWidth field is used. + int width = cellWidth; + if (l.getLayoutOrientation() == JList.VERTICAL) + width = l.getWidth(); + + Rectangle bounds = new Rectangle(loc.x, loc.y, width, + getCellHeight(minIndex)); + for (int i = minIndex + 1; i <= maxIndex; i++) + { + Point hiLoc = indexToLocation(list, i); + bounds = SwingUtilities.computeUnion(hiLoc.x, hiLoc.y, width, + getCellHeight(i), bounds); + } + + return bounds; + } + + /** + * Calculates the maximum cell height. + * + * @param index the index of the cell + * + * @return the maximum cell height + */ + private int getCellHeight(int index) + { + int height = cellHeight; + if (height <= 0) + { + if (list.getLayoutOrientation() == JList.VERTICAL) + height = getRowHeight(index); + else + { + for (int j = 0; j < cellHeights.length; j++) + height = Math.max(height, cellHeights[j]); + } + } + return height; + } + + /** + * 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 + */ + protected 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 list model is empty + * + * @specnote This method is specified to return -1 for an invalid Y + * coordinate. However, some simple tests show that the behaviour + * is to return the index of the last list element for an Y + * coordinate that lies outside of the list bounds (even for + * negative indices). <code>-1</code> + * is only returned if the list model is empty. + */ + protected int convertYToRow(int y0) + { + if (list.getModel().getSize() == 0) + return -1; + + // When y0 < 0, then the JDK returns the maximum row index of the list. So + // do we. + if (y0 < 0) + return list.getModel().getSize() - 1; + + // Update the layout if necessary. + maybeUpdateLayoutState(); + + int index = list.getModel().getSize() - 1; + + // If a fixed cell height is set, then we can work more efficient. + if (cellHeight > 0) + index = Math.min(y0 / cellHeight, index); + // If we have no fixed cell height, we must add up each cell height up + // to y0. + else + { + int h = 0; + for (int row = 0; row < cellHeights.length; ++row) + { + h += cellHeights[row]; + if (y0 < h) + { + index = row; + break; + } + } + } + return index; + } + + /** + * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link + * #cellWidth} properties by examining the variouis properties of the + * {@link JList}. + */ + protected void updateLayoutState() + { + int nrows = list.getModel().getSize(); + cellHeight = -1; + cellWidth = -1; + if (cellHeights == null || cellHeights.length != nrows) + cellHeights = new int[nrows]; + ListCellRenderer rend = list.getCellRenderer(); + // Update the cellHeight(s) fields. + int fixedCellHeight = list.getFixedCellHeight(); + if (fixedCellHeight > 0) + { + cellHeight = fixedCellHeight; + cellHeights = null; + } + else + { + cellHeight = -1; + for (int i = 0; i < nrows; ++i) + { + Component flyweight = + rend.getListCellRendererComponent(list, + list.getModel().getElementAt(i), + i, list.isSelectedIndex(i), + list.getSelectionModel().getAnchorSelectionIndex() == i); + Dimension dim = flyweight.getPreferredSize(); + cellHeights[i] = dim.height; + } + } + + // Update the cellWidth field. + int fixedCellWidth = list.getFixedCellWidth(); + if (fixedCellWidth > 0) + cellWidth = fixedCellWidth; + else + { + for (int i = 0; i < nrows; ++i) + { + Component flyweight = + rend.getListCellRendererComponent(list, + list.getModel().getElementAt(i), + i, list.isSelectedIndex(i), + list.getSelectionModel().getAnchorSelectionIndex() == i); + Dimension dim = flyweight.getPreferredSize(); + cellWidth = Math.max(cellWidth, dim.width); + } + } + } + + /** + * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded} + * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero. + */ + protected void maybeUpdateLayoutState() + { + if (updateLayoutStateNeeded != 0 || !list.isValid()) + { + updateLayoutState(); + updateLayoutStateNeeded = 0; + } + } + + /** + * Creates a new BasicListUI object. + */ + public BasicListUI() + { + updateLayoutStateNeeded = 1; + rendererPane = new CellRendererPane(); + } + + /** + * Installs various default settings (mostly colors) from the {@link + * UIDefaults} into the {@link JList} + * + * @see #uninstallDefaults + */ + protected void installDefaults() + { + LookAndFeel.installColorsAndFont(list, "List.background", + "List.foreground", "List.font"); + list.setSelectionForeground(UIManager.getColor("List.selectionForeground")); + list.setSelectionBackground(UIManager.getColor("List.selectionBackground")); + list.setOpaque(true); + } + + /** + * Resets to <code>null</code> those defaults which were installed in + * {@link #installDefaults} + */ + protected void uninstallDefaults() + { + 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 + */ + protected void installListeners() + { + if (focusListener == null) + focusListener = createFocusListener(); + list.addFocusListener(focusListener); + if (listDataListener == null) + listDataListener = createListDataListener(); + list.getModel().addListDataListener(listDataListener); + if (listSelectionListener == null) + listSelectionListener = createListSelectionListener(); + list.addListSelectionListener(listSelectionListener); + if (mouseInputListener == null) + mouseInputListener = createMouseInputListener(); + list.addMouseListener(mouseInputListener); + list.addMouseMotionListener(mouseInputListener); + if (propertyChangeListener == null) + propertyChangeListener = createPropertyChangeListener(); + list.addPropertyChangeListener(propertyChangeListener); + } + + /** + * Detaches all the listeners we attached in {@link #installListeners}. + */ + protected 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}. + */ + protected void installKeyboardActions() + { + // Install UI InputMap. + InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap"); + SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, + focusInputMap); + + // Install UI ActionMap. + ActionMap am = (ActionMap) UIManager.get("List.actionMap"); + if (am == null) + { + // Create the actionMap once and store it in the current UIDefaults + // for use in other components. + am = new ActionMapUIResource(); + ListAction action; + action = new ListAction("selectPreviousRow"); + am.put("selectPreviousRow", action); + action = new ListAction("selectNextRow"); + am.put("selectNextRow", action); + action = new ListAction("selectPreviousRowExtendSelection"); + am.put("selectPreviousRowExtendSelection", action); + action = new ListAction("selectNextRowExtendSelection"); + am.put("selectNextRowExtendSelection", action); + + action = new ListAction("selectPreviousColumn"); + am.put("selectPreviousColumn", action); + action = new ListAction("selectNextColumn"); + am.put("selectNextColumn", action); + action = new ListAction("selectPreviousColumnExtendSelection"); + am.put("selectPreviousColumnExtendSelection", action); + action = new ListAction("selectNextColumnExtendSelection"); + am.put("selectNextColumnExtendSelection", action); + + action = new ListAction("selectFirstRow"); + am.put("selectFirstRow", action); + action = new ListAction("selectLastRow"); + am.put("selectLastRow", action); + action = new ListAction("selectFirstRowExtendSelection"); + am.put("selectFirstRowExtendSelection", action); + action = new ListAction("selectLastRowExtendSelection"); + am.put("selectLastRowExtendSelection", action); + + action = new ListAction("scrollUp"); + am.put("scrollUp", action); + action = new ListAction("scrollUpExtendSelection"); + am.put("scrollUpExtendSelection", action); + action = new ListAction("scrollDown"); + am.put("scrollDown", action); + action = new ListAction("scrollDownExtendSelection"); + am.put("scrollDownExtendSelection", action); + + action = new ListAction("selectAll"); + am.put("selectAll", action); + action = new ListAction("clearSelection"); + am.put("clearSelection", action); + + am.put("copy", TransferHandler.getCopyAction()); + am.put("cut", TransferHandler.getCutAction()); + am.put("paste", TransferHandler.getPasteAction()); + + UIManager.put("List.actionMap", am); + } + + SwingUtilities.replaceUIActionMap(list, am); + } + + /** + * Uninstalls keyboard actions for this UI in the {@link JList}. + */ + protected void uninstallKeyboardActions() + { + // Uninstall the InputMap. + InputMap im = SwingUtilities.getUIInputMap(list, JComponent.WHEN_FOCUSED); + if (im instanceof UIResource) + SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null); + + // Uninstall the ActionMap. + if (SwingUtilities.getUIActionMap(list) instanceof UIResource) + SwingUtilities.replaceUIActionMap(list, null); + } + + /** + * 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 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) + { + maybeUpdateLayoutState(); + int size = list.getModel().getSize(); + int visibleRows = list.getVisibleRowCount(); + int layoutOrientation = list.getLayoutOrientation(); + + int h; + int w; + int maxCellHeight = cellHeight; + if (maxCellHeight <= 0) + { + for (int i = 0; i < cellHeights.length; i++) + maxCellHeight = Math.max(maxCellHeight, cellHeights[i]); + } + if (layoutOrientation == JList.HORIZONTAL_WRAP) + { + if (visibleRows > 0) + { + // We cast to double here to force double divisions. + double modelSize = size; + int neededColumns = (int) Math.ceil(modelSize / visibleRows); + int adjustedRows = (int) Math.ceil(modelSize / neededColumns); + h = maxCellHeight * adjustedRows; + w = cellWidth * neededColumns; + } + else + { + int neededColumns = Math.min(1, list.getWidth() / cellWidth); + h = size / neededColumns * maxCellHeight; + w = neededColumns * cellWidth; + } + } + else if (layoutOrientation == JList.VERTICAL_WRAP) + { + if (visibleRows > 0) + h = visibleRows * maxCellHeight; + else + h = Math.max(list.getHeight(), maxCellHeight); + int neededColumns = h / maxCellHeight; + w = cellWidth * neededColumns; + } + else + { + if (list.getFixedCellWidth() > 0) + w = list.getFixedCellWidth(); + else + w = cellWidth; + if (list.getFixedCellHeight() > 0) + // FIXME: We need to add some cellVerticalMargins here, according + // to the specs. + h = list.getFixedCellHeight() * size; + else + h = maxCellHeight * size; + } + Insets insets = list.getInsets(); + Dimension retVal = new Dimension(w + insets.left + insets.right, + h + insets.top + insets.bottom); + return retVal; + } + + /** + * 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 + */ + protected void paintCell(Graphics g, int row, Rectangle bounds, + ListCellRenderer rend, ListModel data, + ListSelectionModel sel, int lead) + { + boolean isSel = list.isSelectedIndex(row); + boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus(); + Component comp = rend.getListCellRendererComponent(list, + data.getElementAt(row), + row, isSel, hasFocus); + rendererPane.paintComponent(g, comp, list, bounds); + } + + /** + * Paints the list by 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(); + + int startIndex = locationToIndex(list, new Point(clip.x, clip.y)); + int endIndex = locationToIndex(list, new Point(clip.x + clip.width, + clip.y + clip.height)); + + for (int row = startIndex; row <= endIndex; ++row) + { + Rectangle bounds = getCellBounds(list, row, row); + if (bounds != null && bounds.intersects(clip)) + paintCell(g, row, bounds, render, model, sel, lead); + } + } + + /** + * Computes the index of a list cell given a point within the list. If the + * location lies outside the bounds of the list, the greatest index in the + * list model is returned. + * + * @param l the list which on which the computation is based on + * @param location the coordinates + * + * @return the index of the list item that is located at the given + * coordinates or <code>-1</code> if the list model is empty + */ + public int locationToIndex(JList l, Point location) + { + int layoutOrientation = list.getLayoutOrientation(); + int index = -1; + switch (layoutOrientation) + { + case JList.VERTICAL: + index = convertYToRow(location.y); + break; + case JList.HORIZONTAL_WRAP: + // determine visible rows and cells per row + int maxCellHeight = getCellHeight(0); + int visibleRows = list.getHeight() / maxCellHeight; + int cellsPerRow = -1; + int numberOfItems = list.getModel().getSize(); + cellsPerRow = numberOfItems / visibleRows + 1; + + // determine index for the given location + int cellsPerColumn = numberOfItems / cellsPerRow + 1; + int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1); + int gridY = Math.min(location.y / maxCellHeight, cellsPerColumn); + index = gridX + gridY * cellsPerRow; + break; + case JList.VERTICAL_WRAP: + // determine visible rows and cells per column + int maxCellHeight2 = getCellHeight(0); + int visibleRows2 = list.getHeight() / maxCellHeight2; + int numberOfItems2 = list.getModel().getSize(); + int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1; + + int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1); + int gridY2 = Math.min(location.y / maxCellHeight2, visibleRows2); + index = gridY2 + gridX2 * visibleRows2; + break; + } + return index; + } + + public Point indexToLocation(JList l, int index) + { + int layoutOrientation = list.getLayoutOrientation(); + Point loc = null; + switch (layoutOrientation) + { + case JList.VERTICAL: + loc = new Point(0, convertRowToY(index)); + break; + case JList.HORIZONTAL_WRAP: + // determine visible rows and cells per row + int maxCellHeight = getCellHeight(0); + int visibleRows = list.getHeight() / maxCellHeight; + int numberOfCellsPerRow = -1; + int numberOfItems = list.getModel().getSize(); + numberOfCellsPerRow = numberOfItems / visibleRows + 1; + + // compute coordinates inside the grid + int gridX = index % numberOfCellsPerRow; + int gridY = index / numberOfCellsPerRow; + int locX = gridX * cellWidth; + int locY; + locY = gridY * maxCellHeight; + loc = new Point(locX, locY); + break; + case JList.VERTICAL_WRAP: + // determine visible rows and cells per column + int maxCellHeight2 = getCellHeight(0); + int visibleRows2 = list.getHeight() / maxCellHeight2; + // compute coordinates inside the grid + if (visibleRows2 > 0) + { + int gridY2 = index % visibleRows2; + int gridX2 = index / visibleRows2; + int locX2 = gridX2 * cellWidth; + int locY2 = gridY2 * maxCellHeight2; + loc = new Point(locX2, locY2); + } + else + loc = new Point(0, convertRowToY(index)); + break; + } + return loc; + } + + /** + * Creates and returns the focus listener for this UI. + * + * @return the focus listener for this UI + */ + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + /** + * Creates and returns the list data listener for this UI. + * + * @return the list data listener for this UI + */ + protected ListDataListener createListDataListener() + { + return new ListDataHandler(); + } + + /** + * Creates and returns the list selection listener for this UI. + * + * @return the list selection listener for this UI + */ + protected ListSelectionListener createListSelectionListener() + { + return new ListSelectionHandler(); + } + + /** + * Creates and returns the mouse input listener for this UI. + * + * @return the mouse input listener for this UI + */ + protected MouseInputListener createMouseInputListener() + { + return new MouseInputHandler(); + } + + /** + * Creates and returns the property change listener for this UI. + * + * @return the property change listener for this UI + */ + protected PropertyChangeListener createPropertyChangeListener() + { + return new PropertyChangeHandler(); + } + + /** + * Selects the next list item and force it to be visible. + */ + protected void selectNextIndex() + { + int index = list.getSelectionModel().getLeadSelectionIndex(); + if (index < list.getModel().getSize() - 1) + { + index++; + list.setSelectedIndex(index); + } + list.ensureIndexIsVisible(index); + } + + /** + * Selects the previous list item and force it to be visible. + */ + protected void selectPreviousIndex() + { + int index = list.getSelectionModel().getLeadSelectionIndex(); + if (index > 0) + { + index--; + list.setSelectedIndex(index); + } + list.ensureIndexIsVisible(index); + } +} |