| /* 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) |
| { |
| 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); |
| } |
| } |