KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > plaf > basic > BasicListUI


1 /*
2  * @(#)BasicListUI.java 1.110 05/05/03
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing.plaf.basic;
9
10 import sun.swing.DefaultLookup;
11 import sun.swing.UIAction;
12
13 import javax.swing.*;
14 import javax.swing.event.*;
15 import javax.swing.plaf.*;
16 import javax.swing.text.Position JavaDoc;
17
18 import java.awt.*;
19 import java.awt.event.*;
20 import java.awt.datatransfer.Transferable JavaDoc;
21 import java.awt.dnd.*;
22
23 import java.util.ArrayList JavaDoc;
24 import java.util.TooManyListenersException JavaDoc;
25
26 import java.beans.PropertyChangeListener JavaDoc;
27 import java.beans.PropertyChangeEvent JavaDoc;
28
29 import com.sun.java.swing.SwingUtilities2;
30 import static com.sun.java.swing.SwingUtilities2.DRAG_FIX;
31 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag JavaDoc;
32
33 /**
34  * A Windows L&F implementation of ListUI.
35  * <p>
36  *
37  * @version 1.110 05/03/05
38  * @author Hans Muller
39  * @author Philip Milne
40  * @author Shannon Hickey (improved drag recognition)
41  */

42 public class BasicListUI extends ListUI
43 {
44     protected JList list = null;
45     protected CellRendererPane rendererPane;
46
47     // Listeners that this UI attaches to the JList
48
protected FocusListener focusListener;
49     protected MouseInputListener mouseInputListener;
50     protected ListSelectionListener listSelectionListener;
51     protected ListDataListener listDataListener;
52     protected PropertyChangeListener JavaDoc propertyChangeListener;
53     private Handler handler;
54
55     protected int[] cellHeights = null;
56     protected int cellHeight = -1;
57     protected int cellWidth = -1;
58     protected int updateLayoutStateNeeded = modelChanged;
59     /**
60      * Height of the list. When asked to paint, if the current size of
61      * the list differs, this will update the layout state.
62      */

63     private int listHeight;
64
65     /**
66      * Width of the list. When asked to paint, if the current size of
67      * the list differs, this will update the layout state.
68      */

69     private int listWidth;
70
71     /**
72      * The layout orientation of the list.
73      */

74     private int layoutOrientation;
75
76     // Following ivars are used if the list is laying out horizontally
77

78     /**
79      * Number of columns to create.
80      */

81     private int columnCount;
82     /**
83      * Preferred height to make the list, this is only used if the
84      * the list is layed out horizontally.
85      */

86     private int preferredHeight;
87     /**
88      * Number of rows per column. This is only used if the row height is
89      * fixed.
90      */

91     private int rowsPerColumn;
92
93     /**
94      * The time factor to treate the series of typed alphanumeric key
95      * as prefix for first letter navigation.
96      */

97     private long timeFactor = 1000L;
98
99     /**
100      * Local cache of JList's client property "List.isFileList"
101      */

102     private boolean isFileList = false;
103
104     /**
105      * Local cache of JList's component orientation property
106      */

107     private boolean isLeftToRight = true;
108
109     /* The bits below define JList property changes that affect layout.
110      * When one of these properties changes we set a bit in
111      * updateLayoutStateNeeded. The change is dealt with lazily, see
112      * maybeUpdateLayoutState. Changes to the JLists model, e.g. the
113      * models length changed, are handled similarly, see DataListener.
114      */

115
116     protected final static int modelChanged = 1 << 0;
117     protected final static int selectionModelChanged = 1 << 1;
118     protected final static int fontChanged = 1 << 2;
119     protected final static int fixedCellWidthChanged = 1 << 3;
120     protected final static int fixedCellHeightChanged = 1 << 4;
121     protected final static int prototypeCellValueChanged = 1 << 5;
122     protected final static int cellRendererChanged = 1 << 6;
123     private final static int layoutOrientationChanged = 1 << 7;
124     private final static int heightChanged = 1 << 8;
125     private final static int widthChanged = 1 << 9;
126     private final static int componentOrientationChanged = 1 << 10;
127
128
129     static void loadActionMap(LazyActionMap JavaDoc map) {
130     map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
131     map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
132         map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
133     map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
134     map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
135         map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
136     map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
137     map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
138         map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
139     map.put(new Actions(Actions.SELECT_NEXT_ROW));
140     map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
141         map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
142     map.put(new Actions(Actions.SELECT_FIRST_ROW));
143     map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
144         map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
145     map.put(new Actions(Actions.SELECT_LAST_ROW));
146     map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
147         map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
148     map.put(new Actions(Actions.SCROLL_UP));
149     map.put(new Actions(Actions.SCROLL_UP_EXTEND));
150         map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
151     map.put(new Actions(Actions.SCROLL_DOWN));
152     map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
153         map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
154     map.put(new Actions(Actions.SELECT_ALL));
155     map.put(new Actions(Actions.CLEAR_SELECTION));
156         map.put(new Actions(Actions.ADD_TO_SELECTION));
157         map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
158         map.put(new Actions(Actions.EXTEND_TO));
159         map.put(new Actions(Actions.MOVE_SELECTION_TO));
160
161         map.put(TransferHandler.getCutAction().getValue(Action.NAME),
162                 TransferHandler.getCutAction());
163         map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
164                 TransferHandler.getCopyAction());
165         map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
166                 TransferHandler.getPasteAction());
167     }
168
169     /**
170      * Paint one List cell: compute the relevant state, get the "rubber stamp"
171      * cell renderer component, and then use the CellRendererPane to paint it.
172      * Subclasses may want to override this method rather than paint().
173      *
174      * @see #paint
175      */

176     protected void paintCell(
177         Graphics g,
178         int row,
179         Rectangle rowBounds,
180         ListCellRenderer cellRenderer,
181         ListModel dataModel,
182         ListSelectionModel selModel,
183         int leadIndex)
184     {
185         Object JavaDoc value = dataModel.getElementAt(row);
186         boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
187         boolean isSelected = selModel.isSelectedIndex(row);
188
189         Component rendererComponent =
190             cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
191
192         int cx = rowBounds.x;
193         int cy = rowBounds.y;
194         int cw = rowBounds.width;
195         int ch = rowBounds.height;
196
197     if (isFileList) {
198         // Shrink renderer to preferred size. This is mostly used on Windows
199
// where selection is only shown around the file name, instead of
200
// across the whole list cell.
201
int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
202         if (!isLeftToRight) {
203         cx += (cw - w);
204         }
205         cw = w;
206     }
207
208         rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
209     }
210
211
212     /**
213      * Paint the rows that intersect the Graphics objects clipRect. This
214      * method calls paintCell as necessary. Subclasses
215      * may want to override these methods.
216      *
217      * @see #paintCell
218      */

219     public void paint(Graphics g, JComponent c)
220     {
221         switch (layoutOrientation) {
222         case JList.VERTICAL_WRAP:
223             if (list.getHeight() != listHeight) {
224                 updateLayoutStateNeeded |= heightChanged;
225                 redrawList();
226             }
227             break;
228         case JList.HORIZONTAL_WRAP:
229             if (list.getWidth() != listWidth) {
230                 updateLayoutStateNeeded |= widthChanged;
231                 redrawList();
232             }
233             break;
234         default:
235             break;
236         }
237         maybeUpdateLayoutState();
238
239         ListCellRenderer renderer = list.getCellRenderer();
240         ListModel dataModel = list.getModel();
241         ListSelectionModel selModel = list.getSelectionModel();
242         int size;
243
244         if ((renderer == null) || (size = dataModel.getSize()) == 0) {
245             return;
246         }
247
248         // Determine how many columns we need to paint
249
Rectangle paintBounds = g.getClipBounds();
250
251     int startColumn, endColumn;
252     if (c.getComponentOrientation().isLeftToRight()) {
253             startColumn = convertLocationToColumn(paintBounds.x,
254                                                   paintBounds.y);
255         endColumn = convertLocationToColumn(paintBounds.x +
256                                                 paintBounds.width,
257                                                 paintBounds.y);
258     } else {
259         startColumn = convertLocationToColumn(paintBounds.x +
260                                                 paintBounds.width,
261                                                 paintBounds.y);
262             endColumn = convertLocationToColumn(paintBounds.x,
263                                                   paintBounds.y);
264     }
265         int maxY = paintBounds.y + paintBounds.height;
266         int leadIndex = list.getLeadSelectionIndex();
267         int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
268                            columnCount : 1;
269
270
271         for (int colCounter = startColumn; colCounter <= endColumn;
272              colCounter++) {
273             // And then how many rows in this columnn
274
int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
275             int rowCount = getRowCount(colCounter);
276             int index = getModelIndex(colCounter, row);
277             Rectangle rowBounds = getCellBounds(list, index, index);
278
279             if (rowBounds == null) {
280                 // Not valid, bail!
281
return;
282             }
283             while (row < rowCount && rowBounds.y < maxY &&
284                    index < size) {
285                 rowBounds.height = getHeight(colCounter, row);
286                 g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
287                           rowBounds.height);
288                 g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
289                            paintBounds.height);
290                 paintCell(g, index, rowBounds, renderer, dataModel, selModel,
291                           leadIndex);
292                 rowBounds.y += rowBounds.height;
293                 index += rowIncrement;
294                 row++;
295             }
296         }
297     }
298
299
300     /**
301      * The preferredSize of the list depends upon the layout orientation.
302      * <table summary="Describes the preferred size for each layout orientation">
303      * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
304      * <tr>
305      * <td>JList.VERTICAL
306      * <td>The preferredSize of the list is total height of the rows
307      * and the maximum width of the cells. If JList.fixedCellHeight
308      * is specified then the total height of the rows is just
309      * (cellVerticalMargins + fixedCellHeight) * model.getSize() where
310      * rowVerticalMargins is the space we allocate for drawing
311      * the yellow focus outline. Similarly if fixedCellWidth is
312      * specified then we just use that.
313      * </td>
314      * <tr>
315      * <td>JList.VERTICAL_WRAP
316      * <td>If the visible row count is greater than zero, the preferredHeight
317      * is the maximum cell height * visibleRowCount. If the visible row
318      * count is <= 0, the preferred height is either the current height
319      * of the list, or the maximum cell height, whichever is
320      * bigger. The preferred width is than the maximum cell width *
321      * number of columns needed. Where the number of columns needs is
322      * list.height / max cell height. Max cell height is either the fixed
323      * cell height, or is determined by iterating through all the cells
324      * to find the maximum height from the ListCellRenderer.
325      * <tr>
326      * <td>JList.HORIZONTAL_WRAP
327      * <td>If the visible row count is greater than zero, the preferredHeight
328      * is the maximum cell height * adjustedRowCount. Where
329      * visibleRowCount is used to determine the number of columns.
330      * Because this lays out horizontally the number of rows is
331      * then determined from the column count. For example, lets say
332      * you have a model with 10 items and the visible row count is 8.
333      * The number of columns needed to display this is 2, but you no
334      * longer need 8 rows to display this, you only need 5, thus
335      * the adjustedRowCount is 5.
336      * <p>If the visible row
337      * count is <= 0, the preferred height is dictated by the
338      * number of columns, which will be as many as can fit in the width
339      * of the <code>JList</code> (width / max cell width), with at
340      * least one column. The preferred height then becomes the
341      * model size / number of columns * maximum cell height.
342      * Max cell height is either the fixed
343      * cell height, or is determined by iterating through all the cells
344      * to find the maximum height from the ListCellRenderer.
345      * </table>
346      * The above specifies the raw preferred width and height. The resulting
347      * preferred width is the above width + insets.left + insets.right and
348      * the resulting preferred height is the above height + insets.top +
349      * insets.bottom. Where the <code>Insets</code> are determined from
350      * <code>list.getInsets()</code>.
351      *
352      * @param c The JList component.
353      * @return The total size of the list.
354      */

355     public Dimension getPreferredSize(JComponent c) {
356         maybeUpdateLayoutState();
357
358         int lastRow = list.getModel().getSize() - 1;
359         if (lastRow < 0) {
360             return new Dimension(0, 0);
361         }
362
363         Insets insets = list.getInsets();
364         int width = cellWidth * columnCount + insets.left + insets.right;
365         int height;
366
367         if (layoutOrientation != JList.VERTICAL) {
368             height = preferredHeight;
369         }
370         else {
371             Rectangle bounds = getCellBounds(list, lastRow);
372
373             if (bounds != null) {
374                 height = bounds.y + bounds.height + insets.bottom;
375             }
376             else {
377                 height = 0;
378             }
379         }
380         return new Dimension(width, height);
381     }
382
383
384     /**
385      * Selected the previous row and force it to be visible.
386      *
387      * @see JList#ensureIndexIsVisible
388      */

389     protected void selectPreviousIndex() {
390         int s = list.getSelectedIndex();
391         if(s > 0) {
392             s -= 1;
393             list.setSelectedIndex(s);
394             list.ensureIndexIsVisible(s);
395         }
396     }
397
398
399     /**
400      * Selected the previous row and force it to be visible.
401      *
402      * @see JList#ensureIndexIsVisible
403      */

404     protected void selectNextIndex()
405     {
406         int s = list.getSelectedIndex();
407         if((s + 1) < list.getModel().getSize()) {
408             s += 1;
409             list.setSelectedIndex(s);
410             list.ensureIndexIsVisible(s);
411         }
412     }
413
414
415     /**
416      * Registers the keyboard bindings on the <code>JList</code> that the
417      * <code>BasicListUI</code> is associated with. This method is called at
418      * installUI() time.
419      *
420      * @see #installUI
421      */

422     protected void installKeyboardActions() {
423     InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
424
425     SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
426                        inputMap);
427
428         LazyActionMap.installLazyActionMap(list, BasicListUI JavaDoc.class,
429                                            "List.actionMap");
430     }
431
432     InputMap getInputMap(int condition) {
433     if (condition == JComponent.WHEN_FOCUSED) {
434         InputMap keyMap = (InputMap)DefaultLookup.get(
435                              list, this, "List.focusInputMap");
436         InputMap rtlKeyMap;
437
438         if (isLeftToRight ||
439         ((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
440                               "List.focusInputMap.RightToLeft")) == null)) {
441             return keyMap;
442         } else {
443         rtlKeyMap.setParent(keyMap);
444         return rtlKeyMap;
445         }
446     }
447     return null;
448     }
449
450     /**
451      * Unregisters keyboard actions installed from
452      * <code>installKeyboardActions</code>.
453      * This method is called at uninstallUI() time - subclassess should
454      * ensure that all of the keyboard actions registered at installUI
455      * time are removed here.
456      *
457      * @see #installUI
458      */

459     protected void uninstallKeyboardActions() {
460     SwingUtilities.replaceUIActionMap(list, null);
461     SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
462     }
463
464
465     /**
466      * Create and install the listeners for the JList, its model, and its
467      * selectionModel. This method is called at installUI() time.
468      *
469      * @see #installUI
470      * @see #uninstallListeners
471      */

472     protected void installListeners()
473     {
474     TransferHandler th = list.getTransferHandler();
475     if (th == null || th instanceof UIResource) {
476         list.setTransferHandler(defaultTransferHandler);
477     }
478     DropTarget dropTarget = list.getDropTarget();
479     if (dropTarget instanceof UIResource) {
480         try {
481         dropTarget.addDropTargetListener(new ListDropTargetListener());
482         } catch (TooManyListenersException JavaDoc tmle) {
483         // should not happen... swing drop target is multicast
484
}
485     }
486
487         focusListener = createFocusListener();
488         mouseInputListener = createMouseInputListener();
489         propertyChangeListener = createPropertyChangeListener();
490         listSelectionListener = createListSelectionListener();
491         listDataListener = createListDataListener();
492
493         list.addFocusListener(focusListener);
494         if (!DRAG_FIX) {
495             list.addMouseListener(defaultDragRecognizer);
496             list.addMouseMotionListener(defaultDragRecognizer);
497         }
498         list.addMouseListener(mouseInputListener);
499         list.addMouseMotionListener(mouseInputListener);
500         list.addPropertyChangeListener(propertyChangeListener);
501         list.addKeyListener(getHandler());
502
503         ListModel model = list.getModel();
504         if (model != null) {
505             model.addListDataListener(listDataListener);
506         }
507
508         ListSelectionModel selectionModel = list.getSelectionModel();
509         if (selectionModel != null) {
510             selectionModel.addListSelectionListener(listSelectionListener);
511         }
512     }
513
514
515     /**
516      * Remove the listeners for the JList, its model, and its
517      * selectionModel. All of the listener fields, are reset to
518      * null here. This method is called at uninstallUI() time,
519      * it should be kept in sync with installListeners.
520      *
521      * @see #uninstallUI
522      * @see #installListeners
523      */

524     protected void uninstallListeners()
525     {
526         list.removeFocusListener(focusListener);
527         if (!DRAG_FIX) {
528             list.removeMouseListener(defaultDragRecognizer);
529             list.removeMouseMotionListener(defaultDragRecognizer);
530         }
531         list.removeMouseListener(mouseInputListener);
532         list.removeMouseMotionListener(mouseInputListener);
533         list.removePropertyChangeListener(propertyChangeListener);
534         list.removeKeyListener(getHandler());
535
536         ListModel model = list.getModel();
537         if (model != null) {
538             model.removeListDataListener(listDataListener);
539         }
540
541         ListSelectionModel selectionModel = list.getSelectionModel();
542         if (selectionModel != null) {
543             selectionModel.removeListSelectionListener(listSelectionListener);
544         }
545
546         focusListener = null;
547         mouseInputListener = null;
548         listSelectionListener = null;
549         listDataListener = null;
550         propertyChangeListener = null;
551         handler = null;
552     }
553
554
555     /**
556      * Initialize JList properties, e.g. font, foreground, and background,
557      * and add the CellRendererPane. The font, foreground, and background
558      * properties are only set if their current value is either null
559      * or a UIResource, other properties are set if the current
560      * value is null.
561      *
562      * @see #uninstallDefaults
563      * @see #installUI
564      * @see CellRendererPane
565      */

566     protected void installDefaults()
567     {
568         list.setLayout(null);
569
570         LookAndFeel.installBorder(list, "List.border");
571
572         LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
573
574         LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
575
576         if (list.getCellRenderer() == null) {
577             list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
578         }
579
580         Color sbg = list.getSelectionBackground();
581         if (sbg == null || sbg instanceof UIResource) {
582             list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
583         }
584
585         Color sfg = list.getSelectionForeground();
586         if (sfg == null || sfg instanceof UIResource) {
587             list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
588         }
589
590     Long JavaDoc l = (Long JavaDoc)UIManager.get("List.timeFactor");
591     timeFactor = (l!=null) ? l.longValue() : 1000L;
592
593         updateIsFileList();
594     isLeftToRight = list.getComponentOrientation().isLeftToRight();
595     }
596
597     private void updateIsFileList() {
598         boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
599         if (b != isFileList) {
600             isFileList = b;
601             Font oldFont = list.getFont();
602             if (oldFont == null || oldFont instanceof UIResource) {
603                 Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
604                 if (newFont != null && newFont != oldFont) {
605                     list.setFont(newFont);
606                 }
607             }
608         }
609     }
610
611
612     /**
613      * Set the JList properties that haven't been explicitly overridden to
614      * null. A property is considered overridden if its current value
615      * is not a UIResource.
616      *
617      * @see #installDefaults
618      * @see #uninstallUI
619      * @see CellRendererPane
620      */

621     protected void uninstallDefaults()
622     {
623         LookAndFeel.uninstallBorder(list);
624         if (list.getFont() instanceof UIResource) {
625             list.setFont(null);
626         }
627         if (list.getForeground() instanceof UIResource) {
628             list.setForeground(null);
629         }
630         if (list.getBackground() instanceof UIResource) {
631             list.setBackground(null);
632         }
633         if (list.getSelectionBackground() instanceof UIResource) {
634             list.setSelectionBackground(null);
635         }
636         if (list.getSelectionForeground() instanceof UIResource) {
637             list.setSelectionForeground(null);
638         }
639         if (list.getCellRenderer() instanceof UIResource) {
640             list.setCellRenderer(null);
641         }
642     if (list.getTransferHandler() instanceof UIResource) {
643         list.setTransferHandler(null);
644     }
645     }
646
647
648     /**
649      * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
650      * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
651      * in order.
652      *
653      * @see #installDefaults
654      * @see #installListeners
655      * @see #installKeyboardActions
656      */

657     public void installUI(JComponent c)
658     {
659         list = (JList)c;
660
661         layoutOrientation = list.getLayoutOrientation();
662
663         rendererPane = new CellRendererPane();
664         list.add(rendererPane);
665
666         columnCount = 1;
667
668         installDefaults();
669         installListeners();
670         installKeyboardActions();
671     }
672
673
674     /**
675      * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
676      * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
677      * in order. Sets this.list to null.
678      *
679      * @see #uninstallListeners
680      * @see #uninstallKeyboardActions
681      * @see #uninstallDefaults
682      */

683     public void uninstallUI(JComponent c)
684     {
685         uninstallListeners();
686         uninstallDefaults();
687         uninstallKeyboardActions();
688
689         cellWidth = cellHeight = -1;
690         cellHeights = null;
691
692         listWidth = listHeight = -1;
693
694         list.remove(rendererPane);
695         rendererPane = null;
696         list = null;
697     }
698
699
700     /**
701      * Returns a new instance of BasicListUI. BasicListUI delegates are
702      * allocated one per JList.
703      *
704      * @return A new ListUI implementation for the Windows look and feel.
705      */

706     public static ComponentUI createUI(JComponent list) {
707         return new BasicListUI JavaDoc();
708     }
709
710
711     /**
712      * Convert a point in <code>JList</code> coordinates to the closest index
713      * of the cell at that location. To determine if the cell actually
714      * contains the specified location use a combination of this method and
715      * <code>getCellBounds</code>. Returns -1 if the model is empty.
716      *
717      * @return The index of the cell at location, or -1.
718      * @see ListUI#locationToIndex
719      */

720     public int locationToIndex(JList list, Point location) {
721         maybeUpdateLayoutState();
722         return convertLocationToModel(location.x, location.y);
723     }
724
725
726     /**
727      * @return The origin of the index'th cell, null if index is invalid.
728      * @see ListUI#indexToLocation
729      */

730     public Point indexToLocation(JList list, int index) {
731         maybeUpdateLayoutState();
732         Rectangle rect = getCellBounds(list, index, index);
733
734         if (rect != null) {
735             return new Point(rect.x, rect.y);
736         }
737         return null;
738     }
739
740
741     /**
742      * @return The bounds of the index'th cell.
743      * @see ListUI#getCellBounds
744      */

745     public Rectangle getCellBounds(JList list, int index1, int index2) {
746         maybeUpdateLayoutState();
747
748         int minIndex = Math.min(index1, index2);
749         int maxIndex = Math.max(index1, index2);
750
751         if (minIndex >= list.getModel().getSize()) {
752             return null;
753         }
754
755         Rectangle minBounds = getCellBounds(list, minIndex);
756
757         if (minBounds == null) {
758             return null;
759         }
760         if (minIndex == maxIndex) {
761             return minBounds;
762         }
763         Rectangle maxBounds = getCellBounds(list, maxIndex);
764
765         if (maxBounds != null) {
766             if (layoutOrientation == JList.HORIZONTAL_WRAP) {
767                 int minRow = convertModelToRow(minIndex);
768                 int maxRow = convertModelToRow(maxIndex);
769
770                 if (minRow != maxRow) {
771                     minBounds.x = 0;
772                     minBounds.width = list.getWidth();
773                 }
774             }
775             else if (minBounds.x != maxBounds.x) {
776                 // Different columns
777
minBounds.y = 0;
778                 minBounds.height = list.getHeight();
779             }
780             minBounds.add(maxBounds);
781         }
782         return minBounds;
783     }
784
785     /**
786      * Gets the bounds of the specified model index, returning the resulting
787      * bounds, or null if <code>index</code> is not valid.
788      */

789     private Rectangle getCellBounds(JList list, int index) {
790         maybeUpdateLayoutState();
791
792         int row = convertModelToRow(index);
793         int column = convertModelToColumn(index);
794
795         if (row == -1 || column == -1) {
796             return null;
797         }
798
799         Insets insets = list.getInsets();
800         int x;
801         int w = cellWidth;
802         int y = insets.top;
803         int h;
804         switch (layoutOrientation) {
805         case JList.VERTICAL_WRAP:
806         case JList.HORIZONTAL_WRAP:
807             if (isLeftToRight) {
808                 x = insets.left + column * cellWidth;
809             } else {
810                 x = list.getWidth() - insets.right - (column+1) * cellWidth;
811             }
812             y += cellHeight * row;
813             h = cellHeight;
814             break;
815         default:
816             x = insets.left;
817             if (cellHeights == null) {
818                 y += (cellHeight * row);
819             }
820             else if (row >= cellHeights.length) {
821                 y = 0;
822             }
823             else {
824                 for(int i = 0; i < row; i++) {
825                     y += cellHeights[i];
826                 }
827             }
828             w = list.getWidth() - (insets.left + insets.right);
829             h = getRowHeight(index);
830             break;
831         }
832         return new Rectangle(x, y, w, h);
833     }
834
835     /**
836      * Returns the height of the specified row based on the current layout.
837      *
838      * @return The specified row height or -1 if row isn't valid.
839      * @see #convertYToRow
840      * @see #convertRowToY
841      * @see #updateLayoutState
842      */

843     protected int getRowHeight(int row)
844     {
845         return getHeight(0, row);
846     }
847
848
849     /**
850      * Convert the JList relative coordinate to the row that contains it,
851      * based on the current layout. If y0 doesn't fall within any row,
852      * return -1.
853      *
854      * @return The row that contains y0, or -1.
855      * @see #getRowHeight
856      * @see #updateLayoutState
857      */

858     protected int convertYToRow(int y0)
859     {
860         return convertLocationToRow(0, y0, false);
861     }
862
863
864     /**
865      * Return the JList relative Y coordinate of the origin of the specified
866      * row or -1 if row isn't valid.
867      *
868      * @return The Y coordinate of the origin of row, or -1.
869      * @see #getRowHeight
870      * @see #updateLayoutState
871      */

872     protected int convertRowToY(int row)
873     {
874         if (row >= getRowCount(0) || row < 0) {
875             return -1;
876         }
877         Rectangle bounds = getCellBounds(list, row, row);
878         return bounds.y;
879     }
880
881     /**
882      * Returns the height of the cell at the passed in location.
883      */

884     private int getHeight(int column, int row) {
885         if (column < 0 || column > columnCount || row < 0) {
886             return -1;
887         }
888         if (layoutOrientation != JList.VERTICAL) {
889             return cellHeight;
890         }
891         if (row >= list.getModel().getSize()) {
892             return -1;
893         }
894         return (cellHeights == null) ? cellHeight :
895                            ((row < cellHeights.length) ? cellHeights[row] : -1);
896     }
897
898     /**
899      * Returns the row at location x/y.
900      *
901      * @param closest If true and the location doesn't exactly match a
902      * particular location, this will return the closest row.
903      */

904     private int convertLocationToRow(int x, int y0, boolean closest) {
905         int size = list.getModel().getSize();
906
907         if (size <= 0) {
908             return -1;
909         }
910         Insets insets = list.getInsets();
911         if (cellHeights == null) {
912             int row = (cellHeight == 0) ? 0 :
913                            ((y0 - insets.top) / cellHeight);
914             if (closest) {
915                 if (row < 0) {
916                     row = 0;
917                 }
918                 else if (row >= size) {
919                     row = size - 1;
920                 }
921             }
922             return row;
923         }
924         else if (size > cellHeights.length) {
925             return -1;
926         }
927         else {
928             int y = insets.top;
929             int row = 0;
930
931             if (closest && y0 < y) {
932                 return 0;
933             }
934             int i;
935             for (i = 0; i < size; i++) {
936                 if ((y0 >= y) && (y0 < y + cellHeights[i])) {
937                     return row;
938                 }
939                 y += cellHeights[i];
940                 row += 1;
941             }
942             return i - 1;
943         }
944     }
945
946     /**
947      * Returns the closest row that starts at the specified y-location
948      * in the passed in column.
949      */

950     private int convertLocationToRowInColumn(int y, int column) {
951         int x = 0;
952
953         if (layoutOrientation != JList.VERTICAL) {
954             if (isLeftToRight) {
955                 x = column * cellWidth;
956             } else {
957                 x = list.getWidth() - (column+1)*cellWidth - list.getInsets().right;
958             }
959         }
960         return convertLocationToRow(x, y, true);
961     }
962
963     /**
964      * Returns the closest location to the model index of the passed in
965      * l