summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/plaf/basic/BasicListUI.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/plaf/basic/BasicListUI.java')
-rw-r--r--libjava/classpath/javax/swing/plaf/basic/BasicListUI.java1421
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);
+ }
+}