| /* BasicListUI.java -- |
| Copyright (C) 2002, 2004, 2005 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.Point; |
| import java.awt.Rectangle; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.ComponentAdapter; |
| import java.awt.event.ComponentEvent; |
| import java.awt.event.ComponentListener; |
| 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.JViewport; |
| import javax.swing.KeyStroke; |
| import javax.swing.ListCellRenderer; |
| import javax.swing.ListModel; |
| import javax.swing.ListSelectionModel; |
| import javax.swing.LookAndFeel; |
| 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.InputMapUIResource; |
| 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 ComponentEvent}s from |
| * the JList. |
| */ |
| private class ComponentHandler extends ComponentAdapter { |
| |
| /** |
| * Called when the component is hidden. Invalidates the internal |
| * layout. |
| */ |
| public void componentResized(ComponentEvent ev) { |
| BasicListUI.this.damageLayout(); |
| } |
| } |
| |
| /** |
| * 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) |
| { |
| 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}. |
| */ |
| 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); |
| 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); |
| } |
| } |
| |
| class ListAction extends AbstractAction |
| { |
| 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) |
| { |
| // TODO: What should be done here, if anything? |
| } |
| |
| /** |
| * 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) |
| { |
| // TODO: What should be done here, if anything? |
| } |
| |
| /** |
| * 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.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); |
| } |
| // Update the updateLayoutStateNeeded flag. |
| if (e.getPropertyName().equals("model")) |
| 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; |
| BasicListUI.this.damageLayout(); |
| } |
| } |
| |
| /** |
| * 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; |
| |
| |
| /** The component listener that receives notification for resizing the |
| * JList component.*/ |
| private ComponentListener componentListener; |
| |
| /** 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 |
| */ |
| 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); |
| Rectangle bounds = new Rectangle(loc.x, loc.y, cellWidth, |
| getRowHeight(minIndex)); |
| |
| for (int i = minIndex + 1; i <= maxIndex; i++) |
| { |
| Point hiLoc = indexToLocation(list, i); |
| Rectangle hibounds = new Rectangle(hiLoc.x, hiLoc.y, cellWidth, |
| getRowHeight(i)); |
| bounds = bounds.union(hibounds); |
| } |
| |
| return bounds; |
| } |
| |
| /** |
| * 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); |
| } |
| if (list.getLayoutOrientation() == JList.VERTICAL) |
| cellWidth = Math.max(cellWidth, list.getSize().width); |
| } |
| } |
| |
| /** |
| * Marks the current layout as damaged and requests revalidation from the |
| * JList. |
| * This is package-private to avoid an accessor method. |
| * |
| * @see #updateLayoutStateNeeded |
| */ |
| void damageLayout() |
| { |
| updateLayoutStateNeeded = 1; |
| } |
| |
| /** |
| * 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); |
| |
| // FIXME: Are these two really needed? At least they are not documented. |
| //keyListener = new KeyHandler(); |
| componentListener = new ComponentHandler(); |
| list.addComponentListener(componentListener); |
| //list.addKeyListener(keyListener); |
| } |
| |
| /** |
| * 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.removeKeyListener(keyListener); |
| list.removeMouseMotionListener(mouseInputListener); |
| list.removePropertyChangeListener(propertyChangeListener); |
| } |
| |
| /** |
| * Installs keyboard actions for this UI in the {@link JList}. |
| */ |
| protected void installKeyboardActions() |
| { |
| InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap"); |
| InputMapUIResource parentInputMap = new InputMapUIResource(); |
| // FIXME: The JDK uses a LazyActionMap for parentActionMap |
| ActionMap parentActionMap = new ActionMapUIResource(); |
| action = new ListAction(); |
| Object keys[] = focusInputMap.allKeys(); |
| // Register key bindings in the UI InputMap-ActionMap pair |
| for (int i = 0; i < keys.length; i++) |
| { |
| KeyStroke stroke = (KeyStroke)keys[i]; |
| String actionString = (String) focusInputMap.get(stroke); |
| parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(), |
| stroke.getModifiers()), |
| actionString); |
| |
| parentActionMap.put (actionString, |
| new ActionListenerProxy(action, actionString)); |
| } |
| // Register the new InputMap-ActionMap as the parents of the list's |
| // InputMap and ActionMap |
| parentInputMap.setParent(list.getInputMap().getParent()); |
| parentActionMap.setParent(list.getActionMap().getParent()); |
| list.getInputMap().setParent(parentInputMap); |
| list.getActionMap().setParent(parentActionMap); |
| } |
| |
| /** |
| * Uninstalls keyboard actions for this UI in the {@link JList}. |
| */ |
| protected void uninstallKeyboardActions() |
| { |
| // TODO: Implement this properly. |
| } |
| |
| /** |
| * 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) |
| { |
| int size = list.getModel().getSize(); |
| if (size == 0) |
| return new Dimension(0, 0); |
| int visibleRows = list.getVisibleRowCount(); |
| int layoutOrientation = list.getLayoutOrientation(); |
| Rectangle bounds = getCellBounds(list, 0, list.getModel().getSize() - 1); |
| Dimension retVal = bounds.getSize(); |
| Component parent = list.getParent(); |
| if ((visibleRows == -1) && (parent instanceof JViewport)) |
| { |
| JViewport viewport = (JViewport) parent; |
| |
| if (layoutOrientation == JList.HORIZONTAL_WRAP) |
| { |
| int h = viewport.getSize().height; |
| int cellsPerCol = h / cellHeight; |
| int w = size / cellsPerCol * cellWidth; |
| retVal = new Dimension(w, h); |
| } |
| else if (layoutOrientation == JList.VERTICAL_WRAP) |
| { |
| int w = viewport.getSize().width; |
| int cellsPerRow = Math.max(w / cellWidth, 1); |
| int h = size / cellsPerRow * cellHeight; |
| retVal = new Dimension(w, h); |
| } |
| } |
| 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), |
| 0, 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 = list.locationToIndex(new Point(clip.x, clip.y)); |
| int endIndex = list.locationToIndex(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.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 list 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 list, 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 visibleRows = list.getVisibleRowCount(); |
| int cellsPerRow = -1; |
| int numberOfItems = list.getModel().getSize(); |
| Dimension listDim = list.getSize(); |
| if (visibleRows <= 0) |
| { |
| try |
| { |
| cellsPerRow = listDim.width / cellWidth; |
| } |
| catch (ArithmeticException ex) |
| { |
| cellsPerRow = 1; |
| } |
| } |
| else |
| { |
| 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 / cellHeight, cellsPerColumn); |
| index = gridX + gridY * cellsPerRow; |
| break; |
| case JList.VERTICAL_WRAP: |
| // determine visible rows and cells per column |
| int visibleRows2 = list.getVisibleRowCount(); |
| if (visibleRows2 <= 0) |
| { |
| Dimension listDim2 = list.getSize(); |
| visibleRows2 = listDim2.height / cellHeight; |
| } |
| 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 / cellHeight, visibleRows2); |
| index = gridY2 + gridX2 * visibleRows2; |
| break; |
| } |
| return index; |
| } |
| |
| public Point indexToLocation(JList list, 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 visibleRows = list.getVisibleRowCount(); |
| int numberOfCellsPerRow = -1; |
| if (visibleRows <= 0) |
| { |
| Dimension listDim = list.getSize(); |
| numberOfCellsPerRow = Math.max(listDim.width / cellWidth, 1); |
| } |
| else |
| { |
| 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 = gridY * cellHeight; |
| loc = new Point(locX, locY); |
| break; |
| case JList.VERTICAL_WRAP: |
| // determine visible rows and cells per column |
| int visibleRows2 = list.getVisibleRowCount(); |
| if (visibleRows2 <= 0) |
| { |
| Dimension listDim2 = list.getSize(); |
| visibleRows2 = listDim2.height / cellHeight; |
| } |
| // compute coordinates inside the grid |
| if (visibleRows2 > 0) |
| { |
| int gridY2 = index % visibleRows2; |
| int gridX2 = index / visibleRows2; |
| int locX2 = gridX2 * cellWidth; |
| int locY2 = gridY2 * cellHeight; |
| 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); |
| } |
| } |