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      * location.
966      */

967     private int convertLocationToModel(int x, int y) {
968         int row = convertLocationToRow(x, y, true);
969         int column = convertLocationToColumn(x, y);
970
971         if (row >= 0 && column >= 0) {
972             return getModelIndex(column, row);
973         }
974         return -1;
975     }
976
977     /**
978      * Returns the number of rows in the given column.
979      */

980     private int getRowCount(int column) {
981         if (column < 0 || column >= columnCount) {
982             return -1;
983         }
984         if (layoutOrientation == JList.VERTICAL ||
985                   (column == 0 && columnCount == 1)) {
986             return list.getModel().getSize();
987         }
988         if (column >= columnCount) {
989             return -1;
990         }
991         if (layoutOrientation == JList.VERTICAL_WRAP) {
992             if (column < (columnCount - 1)) {
993                 return rowsPerColumn;
994             }
995             return list.getModel().getSize() - (columnCount - 1) *
996                         rowsPerColumn;
997         }
998         // JList.HORIZONTAL_WRAP
999
int diff = columnCount - (columnCount * rowsPerColumn -
1000                                  list.getModel().getSize());
1001
1002        if (column >= diff) {
1003            return Math.max(0, rowsPerColumn - 1);
1004        }
1005        return rowsPerColumn;
1006    }
1007
1008    /**
1009     * Returns the model index for the specified display location.
1010     * If <code>column</code>x<code>row</code> is beyond the length of the
1011     * model, this will return the model size - 1.
1012     */

1013    private int getModelIndex(int column, int row) {
1014        switch (layoutOrientation) {
1015        case JList.VERTICAL_WRAP:
1016            return Math.min(list.getModel().getSize() - 1, rowsPerColumn *
1017                            column + Math.min(row, rowsPerColumn-1));
1018        case JList.HORIZONTAL_WRAP:
1019            return Math.min(list.getModel().getSize() - 1, row * columnCount +
1020                            column);
1021        default:
1022            return row;
1023        }
1024    }
1025
1026    /**
1027     * Returns the closest column to the passed in location.
1028     */

1029    private int convertLocationToColumn(int x, int y) {
1030        if (cellWidth > 0) {
1031            if (layoutOrientation == JList.VERTICAL) {
1032                return 0;
1033            }
1034            Insets insets = list.getInsets();
1035            int col;
1036            if (isLeftToRight) {
1037                col = (x - insets.left) / cellWidth;
1038            } else {
1039                col = (list.getWidth() - x - insets.right - 1) / cellWidth;
1040            }
1041            if (col < 0) {
1042                return 0;
1043            }
1044            else if (col >= columnCount) {
1045                return columnCount - 1;
1046            }
1047            return col;
1048        }
1049        return 0;
1050    }
1051
1052    /**
1053     * Returns the row that the model index <code>index</code> will be
1054     * displayed in..
1055     */

1056    private int convertModelToRow(int index) {
1057        int size = list.getModel().getSize();
1058
1059        if ((index < 0) || (index >= size)) {
1060            return -1;
1061        }
1062
1063        if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
1064                                                   rowsPerColumn > 0) {
1065            if (layoutOrientation == JList.VERTICAL_WRAP) {
1066                return index % rowsPerColumn;
1067            }
1068            return index / columnCount;
1069        }
1070        return index;
1071    }
1072
1073    /**
1074     * Returns the column that the model index <code>index</code> will be
1075     * displayed in.
1076     */

1077    private int convertModelToColumn(int index) {
1078        int size = list.getModel().getSize();
1079
1080        if ((index < 0) || (index >= size)) {
1081            return -1;
1082        }
1083
1084        if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
1085                                                   columnCount > 1) {
1086            if (layoutOrientation == JList.VERTICAL_WRAP) {
1087                return index / rowsPerColumn;
1088            }
1089            return index % columnCount;
1090        }
1091        return 0;
1092    }
1093
1094    /**
1095     * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
1096     * updateLayoutStateNeeded. This method should be called by methods
1097     * before doing any computation based on the geometry of the list.
1098     * For example it's the first call in paint() and getPreferredSize().
1099     *
1100     * @see #updateLayoutState
1101     */

1102    protected void maybeUpdateLayoutState()
1103    {
1104        if (updateLayoutStateNeeded != 0) {
1105            updateLayoutState();
1106            updateLayoutStateNeeded = 0;
1107        }
1108    }
1109
1110
1111    /**
1112     * Recompute the value of cellHeight or cellHeights based
1113     * and cellWidth, based on the current font and the current
1114     * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
1115     *
1116     * @see #maybeUpdateLayoutState
1117     */

1118    protected void updateLayoutState()
1119    {
1120        /* If both JList fixedCellWidth and fixedCellHeight have been
1121         * set, then initialize cellWidth and cellHeight, and set
1122         * cellHeights to null.
1123         */

1124
1125        int fixedCellHeight = list.getFixedCellHeight();
1126        int fixedCellWidth = list.getFixedCellWidth();
1127
1128        cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1129
1130        if (fixedCellHeight != -1) {
1131            cellHeight = fixedCellHeight;
1132            cellHeights = null;
1133        }
1134        else {
1135            cellHeight = -1;
1136            cellHeights = new int[list.getModel().getSize()];
1137        }
1138
1139        /* If either of JList fixedCellWidth and fixedCellHeight haven't
1140         * been set, then initialize cellWidth and cellHeights by
1141         * scanning through the entire model. Note: if the renderer is
1142         * null, we just set cellWidth and cellHeights[*] to zero,
1143         * if they're not set already.
1144         */

1145
1146        if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1147
1148            ListModel dataModel = list.getModel();
1149            int dataModelSize = dataModel.getSize();
1150            ListCellRenderer renderer = list.getCellRenderer();
1151
1152            if (renderer != null) {
1153                for(int index = 0; index < dataModelSize; index++) {
1154                    Object JavaDoc value = dataModel.getElementAt(index);
1155                    Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
1156                    rendererPane.add(c);
1157                    Dimension cellSize = c.getPreferredSize();
1158                    if (fixedCellWidth == -1) {
1159                        cellWidth = Math.max(cellSize.width, cellWidth);
1160                    }
1161                    if (fixedCellHeight == -1) {
1162                        cellHeights[index] = cellSize.height;
1163                    }
1164                }
1165            }
1166            else {
1167                if (cellWidth == -1) {
1168                    cellWidth = 0;
1169                }
1170                if (cellHeights == null) {
1171                    cellHeights = new int[dataModelSize];
1172                }
1173                for(int index = 0; index < dataModelSize; index++) {
1174                    cellHeights[index] = 0;
1175                }
1176            }
1177        }
1178
1179        columnCount = 1;
1180        if (layoutOrientation != JList.VERTICAL) {
1181            updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
1182        }
1183    }
1184
1185    /**
1186     * Invoked when the list is layed out horizontally to determine how
1187     * many columns to create.
1188     * <p>
1189     * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
1190     * <code>preferredHeight</code> and potentially <code>cellHeight</code>
1191     * instance variables.
1192     */

1193    private void updateHorizontalLayoutState(int fixedCellWidth,
1194                                             int fixedCellHeight) {
1195        int visRows = list.getVisibleRowCount();
1196        int dataModelSize = list.getModel().getSize();
1197        Insets insets = list.getInsets();
1198
1199        listHeight = list.getHeight();
1200        listWidth = list.getWidth();
1201
1202        if (dataModelSize == 0) {
1203            rowsPerColumn = columnCount = 0;
1204            preferredHeight = insets.top + insets.bottom;
1205            return;
1206        }
1207
1208        int height;
1209
1210        if (fixedCellHeight != -1) {
1211            height = fixedCellHeight;
1212        }
1213        else {
1214            // Determine the max of the renderer heights.
1215
int maxHeight = 0;
1216            if (cellHeights.length > 0) {
1217                maxHeight = cellHeights[cellHeights.length - 1];
1218                for (int counter = cellHeights.length - 2;
1219                     counter >= 0; counter--) {
1220                    maxHeight = Math.max(maxHeight, cellHeights[counter]);
1221                }
1222            }
1223            height = cellHeight = maxHeight;
1224            cellHeights = null;
1225        }
1226        // The number of rows is either determined by the visible row
1227
// count, or by the height of the list.
1228
rowsPerColumn = dataModelSize;
1229        if (visRows > 0) {
1230            rowsPerColumn = visRows;
1231            columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1232            if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1233                dataModelSize % rowsPerColumn != 0) {
1234                columnCount++;
1235            }
1236            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1237                // Because HORIZONTAL_WRAP flows differently, the
1238
// rowsPerColumn needs to be adjusted.
1239
rowsPerColumn = (dataModelSize / columnCount);
1240                if (dataModelSize % columnCount > 0) {
1241                    rowsPerColumn++;
1242                }
1243            }
1244        }
1245        else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
1246            rowsPerColumn = Math.max(1, (listHeight - insets.top -
1247                                         insets.bottom) / height);
1248            columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1249            if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1250                dataModelSize % rowsPerColumn != 0) {
1251                columnCount++;
1252            }
1253        }
1254        else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
1255                 listWidth > 0) {
1256            columnCount = Math.max(1, (listWidth - insets.left -
1257                                       insets.right) / cellWidth);
1258            rowsPerColumn = dataModelSize / columnCount;
1259            if (dataModelSize % columnCount > 0) {
1260                rowsPerColumn++;
1261            }
1262        }
1263        preferredHeight = rowsPerColumn * cellHeight + insets.top +
1264                              insets.bottom;
1265    }
1266
1267    private Handler getHandler() {
1268        if (handler == null) {
1269            handler = DRAG_FIX ? new DragFixHandler() : new Handler();
1270        }
1271        return handler;
1272    }
1273
1274    /**
1275     * Mouse input, and focus handling for JList. An instance of this
1276     * class is added to the appropriate java.awt.Component lists
1277     * at installUI() time. Note keyboard input is handled with JComponent
1278     * KeyboardActions, see installKeyboardActions().
1279     * <p>
1280     * <strong>Warning:</strong>
1281     * Serialized objects of this class will not be compatible with
1282     * future Swing releases. The current serialization support is
1283     * appropriate for short term storage or RMI between applications running
1284     * the same version of Swing. As of 1.4, support for long term storage
1285     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1286     * has been added to the <code>java.beans</code> package.
1287     * Please see {@link java.beans.XMLEncoder}.
1288     *
1289     * @see #createMouseInputListener
1290     * @see #installKeyboardActions
1291     * @see #installUI
1292     */

1293    public class MouseInputHandler implements MouseInputListener
1294    {
1295        public void mouseClicked(MouseEvent e) {
1296            getHandler().mouseClicked(e);
1297        }
1298
1299        public void mouseEntered(MouseEvent e) {
1300            getHandler().mouseEntered(e);
1301        }
1302
1303        public void mouseExited(MouseEvent e) {
1304            getHandler().mouseExited(e);
1305        }
1306
1307        public void mousePressed(MouseEvent e) {
1308            getHandler().mousePressed(e);
1309    }
1310
1311        public void mouseDragged(MouseEvent e) {
1312            getHandler().mouseDragged(e);
1313        }
1314
1315        public void mouseMoved(MouseEvent e) {
1316            getHandler().mouseMoved(e);
1317        }
1318
1319        public void mouseReleased(MouseEvent e) {
1320            getHandler().mouseReleased(e);
1321        }
1322    }
1323
1324
1325    /**
1326     * Creates a delegate that implements MouseInputListener.
1327     * The delegate is added to the corresponding java.awt.Component listener
1328     * lists at installUI() time. Subclasses can override this method to return
1329     * a custom MouseInputListener, e.g.
1330     * <pre>
1331     * class MyListUI extends BasicListUI {
1332     * protected MouseInputListener <b>createMouseInputListener</b>() {
1333     * return new MyMouseInputHandler();
1334     * }
1335     * public class MyMouseInputHandler extends MouseInputHandler {
1336     * public void mouseMoved(MouseEvent e) {
1337     * // do some extra work when the mouse moves
1338     * super.mouseMoved(e);
1339     * }
1340     * }
1341     * }
1342     * </pre>
1343     *
1344     * @see MouseInputHandler
1345     * @see #installUI
1346     */

1347    protected MouseInputListener createMouseInputListener() {
1348        return getHandler();
1349    }
1350
1351    /**
1352     * This inner class is marked &quot;public&quot; due to a compiler bug.
1353     * This class should be treated as a &quot;protected&quot; inner class.
1354     * Instantiate it only within subclasses of BasicTableUI.
1355     */

1356    public class FocusHandler implements FocusListener
1357    {
1358        protected void repaintCellFocus()
1359        {
1360            getHandler().repaintCellFocus();
1361        }
1362
1363        /* The focusGained() focusLost() methods run when the JList
1364         * focus changes.
1365         */

1366
1367        public void focusGained(FocusEvent e) {
1368            getHandler().focusGained(e);
1369        }
1370
1371        public void focusLost(FocusEvent e) {
1372            getHandler().focusLost(e);
1373        }
1374    }
1375
1376    protected FocusListener createFocusListener() {
1377        return getHandler();
1378    }
1379
1380    /**
1381     * The ListSelectionListener that's added to the JLists selection
1382     * model at installUI time, and whenever the JList.selectionModel property
1383     * changes. When the selection changes we repaint the affected rows.
1384     * <p>
1385     * <strong>Warning:</strong>
1386     * Serialized objects of this class will not be compatible with
1387     * future Swing releases. The current serialization support is
1388     * appropriate for short term storage or RMI between applications running
1389     * the same version of Swing. As of 1.4, support for long term storage
1390     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1391     * has been added to the <code>java.beans</code> package.
1392     * Please see {@link java.beans.XMLEncoder}.
1393     *
1394     * @see #createListSelectionListener
1395     * @see #getCellBounds
1396     * @see #installUI
1397     */

1398    public class ListSelectionHandler implements ListSelectionListener
1399    {
1400        public void valueChanged(ListSelectionEvent e)
1401        {
1402            getHandler().valueChanged(e);
1403        }
1404    }
1405
1406
1407    /**
1408     * Creates an instance of ListSelectionHandler that's added to
1409     * the JLists by selectionModel as needed. Subclasses can override
1410     * this method to return a custom ListSelectionListener, e.g.
1411     * <pre>
1412     * class MyListUI extends BasicListUI {
1413     * protected ListSelectionListener <b>createListSelectionListener</b>() {
1414     * return new MySelectionListener();
1415     * }
1416     * public class MySelectionListener extends ListSelectionHandler {
1417     * public void valueChanged(ListSelectionEvent e) {
1418     * // do some extra work when the selection changes
1419     * super.valueChange(e);
1420     * }
1421     * }
1422     * }
1423     * </pre>
1424     *
1425     * @see ListSelectionHandler
1426     * @see #installUI
1427     */

1428    protected ListSelectionListener createListSelectionListener() {
1429        return getHandler();
1430    }
1431
1432
1433    private void redrawList() {
1434    list.revalidate();
1435    list.repaint();
1436    }
1437
1438
1439    /**
1440     * The ListDataListener that's added to the JLists model at
1441     * installUI time, and whenever the JList.model property changes.
1442     * <p>
1443     * <strong>Warning:</strong>
1444     * Serialized objects of this class will not be compatible with
1445     * future Swing releases. The current serialization support is
1446     * appropriate for short term storage or RMI between applications running
1447     * the same version of Swing. As of 1.4, support for long term storage
1448     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1449     * has been added to the <code>java.beans</code> package.
1450     * Please see {@link java.beans.XMLEncoder}.
1451     *
1452     * @see JList#getModel
1453     * @see #maybeUpdateLayoutState
1454     * @see #createListDataListener
1455     * @see #installUI
1456     */

1457    public class ListDataHandler implements ListDataListener
1458    {
1459        public void intervalAdded(ListDataEvent e) {
1460            getHandler().intervalAdded(e);
1461        }
1462
1463
1464        public void intervalRemoved(ListDataEvent e)
1465        {
1466            getHandler().intervalRemoved(e);
1467        }
1468
1469
1470        public void contentsChanged(ListDataEvent e) {
1471            getHandler().contentsChanged(e);
1472        }
1473    }
1474
1475
1476    /**
1477     * Creates an instance of ListDataListener that's added to
1478     * the JLists by model as needed. Subclasses can override
1479     * this method to return a custom ListDataListener, e.g.
1480     * <pre>
1481     * class MyListUI extends BasicListUI {
1482     * protected ListDataListener <b>createListDataListener</b>() {
1483     * return new MyListDataListener();
1484     * }
1485     * public class MyListDataListener extends ListDataHandler {
1486     * public void contentsChanged(ListDataEvent e) {
1487     * // do some extra work when the models contents change
1488     * super.contentsChange(e);
1489     * }
1490     * }
1491     * }
1492     * </pre>
1493     *
1494     * @see ListDataListener
1495     * @see JList#getModel
1496     * @see #installUI
1497     */

1498    protected ListDataListener createListDataListener() {
1499        return getHandler();
1500    }
1501
1502
1503    /**
1504     * The PropertyChangeListener that's added to the JList at
1505     * installUI time. When the value of a JList property that
1506     * affects layout changes, we set a bit in updateLayoutStateNeeded.
1507     * If the JLists model changes we additionally remove our listeners
1508     * from the old model. Likewise for the JList selectionModel.
1509     * <p>
1510     * <strong>Warning:</strong>
1511     * Serialized objects of this class will not be compatible with
1512     * future Swing releases. The current serialization support is
1513     * appropriate for short term storage or RMI between applications running
1514     * the same version of Swing. As of 1.4, support for long term storage
1515     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1516     * has been added to the <code>java.beans</code> package.
1517     * Please see {@link java.beans.XMLEncoder}.
1518     *
1519     * @see #maybeUpdateLayoutState
1520     * @see #createPropertyChangeListener
1521     * @see #installUI
1522     */

1523    public class PropertyChangeHandler implements PropertyChangeListener JavaDoc
1524    {
1525        public void propertyChange(PropertyChangeEvent JavaDoc e)
1526        {
1527            getHandler().propertyChange(e);
1528        }
1529    }
1530
1531
1532    /**
1533     * Creates an instance of PropertyChangeHandler that's added to
1534     * the JList by installUI(). Subclasses can override this method
1535     * to return a custom PropertyChangeListener, e.g.
1536     * <pre>
1537     * class MyListUI extends BasicListUI {
1538     * protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
1539     * return new MyPropertyChangeListener();
1540     * }
1541     * public class MyPropertyChangeListener extends PropertyChangeHandler {
1542     * public void propertyChange(PropertyChangeEvent e) {
1543     * if (e.getPropertyName().equals("model")) {
1544     * // do some extra work when the model changes
1545     * }
1546     * super.propertyChange(e);
1547     * }
1548     * }
1549     * }
1550     * </pre>
1551     *
1552     * @see PropertyChangeListener
1553     * @see #installUI
1554     */

1555    protected PropertyChangeListener JavaDoc createPropertyChangeListener() {
1556        return getHandler();
1557    }
1558
1559    /** Used by IncrementLeadSelectionAction. Indicates the action should
1560     * change the lead, and not select it. */

1561    private static final int CHANGE_LEAD = 0;
1562    /** Used by IncrementLeadSelectionAction. Indicates the action should
1563     * change the selection and lead. */

1564    private static final int CHANGE_SELECTION = 1;
1565    /** Used by IncrementLeadSelectionAction. Indicates the action should
1566     * extend the selection from the anchor to the next index. */

1567    private static final int EXTEND_SELECTION = 2;
1568
1569
1570    private static class Actions extends UIAction {
1571        private static final String JavaDoc SELECT_PREVIOUS_COLUMN =
1572                                    "selectPreviousColumn";
1573        private static final String JavaDoc SELECT_PREVIOUS_COLUMN_EXTEND =
1574                                    "selectPreviousColumnExtendSelection";
1575        private static final String JavaDoc SELECT_PREVIOUS_COLUMN_CHANGE_LEAD =
1576                                    "selectPreviousColumnChangeLead";
1577        private static final String JavaDoc SELECT_NEXT_COLUMN = "selectNextColumn";
1578        private static final String JavaDoc SELECT_NEXT_COLUMN_EXTEND =
1579                                    "selectNextColumnExtendSelection";
1580        private static final String JavaDoc SELECT_NEXT_COLUMN_CHANGE_LEAD =
1581                                    "selectNextColumnChangeLead";
1582        private static final String JavaDoc SELECT_PREVIOUS_ROW = "selectPreviousRow";
1583        private static final String JavaDoc SELECT_PREVIOUS_ROW_EXTEND =
1584                                     "selectPreviousRowExtendSelection";
1585        private static final String JavaDoc SELECT_PREVIOUS_ROW_CHANGE_LEAD =
1586                                     "selectPreviousRowChangeLead";
1587        private static final String JavaDoc SELECT_NEXT_ROW = "selectNextRow";
1588        private static final String JavaDoc SELECT_NEXT_ROW_EXTEND =
1589                                     "selectNextRowExtendSelection";
1590        private static final String JavaDoc SELECT_NEXT_ROW_CHANGE_LEAD =
1591                                     "selectNextRowChangeLead";
1592        private static final String JavaDoc SELECT_FIRST_ROW = "selectFirstRow";
1593        private static final String JavaDoc SELECT_FIRST_ROW_EXTEND =
1594                                     "selectFirstRowExtendSelection";
1595        private static final String JavaDoc SELECT_FIRST_ROW_CHANGE_LEAD =
1596                                     "selectFirstRowChangeLead";
1597        private static final String JavaDoc SELECT_LAST_ROW = "selectLastRow";
1598        private static final String JavaDoc SELECT_LAST_ROW_EXTEND =
1599                                     "selectLastRowExtendSelection";
1600        private static final String JavaDoc SELECT_LAST_ROW_CHANGE_LEAD =
1601                                     "selectLastRowChangeLead";
1602        private static final String JavaDoc SCROLL_UP = "scrollUp";
1603        private static final String JavaDoc SCROLL_UP_EXTEND =
1604                                     "scrollUpExtendSelection";
1605        private static final String JavaDoc SCROLL_UP_CHANGE_LEAD =
1606                                     "scrollUpChangeLead";
1607        private static final String JavaDoc SCROLL_DOWN = "scrollDown";
1608        private static final String JavaDoc SCROLL_DOWN_EXTEND =
1609                                     "scrollDownExtendSelection";
1610        private static final String JavaDoc SCROLL_DOWN_CHANGE_LEAD =
1611                                     "scrollDownChangeLead";
1612        private static final String JavaDoc SELECT_ALL = "selectAll";
1613        private static final String JavaDoc CLEAR_SELECTION = "clearSelection";
1614
1615        // add the lead item to the selection without changing lead or anchor
1616
private static final String JavaDoc ADD_TO_SELECTION = "addToSelection";
1617
1618        // toggle the selected state of the lead item and move the anchor to it
1619
private static final String JavaDoc TOGGLE_AND_ANCHOR = "toggleAndAnchor";
1620
1621        // extend the selection to the lead item
1622
private static final String JavaDoc EXTEND_TO = "extendTo";
1623
1624        // move the anchor to the lead and ensure only that item is selected
1625
private static final String JavaDoc MOVE_SELECTION_TO = "moveSelectionTo";
1626
1627        Actions(String JavaDoc name) {
1628            super(name);
1629        }
1630        public void actionPerformed(ActionEvent e) {
1631            String JavaDoc name = getName();
1632            JList list = (JList)e.getSource();
1633            BasicListUI JavaDoc ui = (BasicListUI JavaDoc)BasicLookAndFeel.getUIOfType(
1634                     list.getUI(), BasicListUI JavaDoc.class);
1635
1636            if (name == SELECT_PREVIOUS_COLUMN) {
1637                changeSelection(list, CHANGE_SELECTION,
1638                                getNextColumnIndex(list, ui, -1), true);
1639            }
1640            else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
1641                changeSelection(list, EXTEND_SELECTION,
1642                                getNextColumnIndex(list, ui, -1), true);
1643            }
1644            else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
1645                changeSelection(list, CHANGE_LEAD,
1646                                getNextColumnIndex(list, ui, -1), true);
1647            }
1648            else if (name == SELECT_NEXT_COLUMN) {
1649                changeSelection(list, CHANGE_SELECTION,
1650                                getNextColumnIndex(list, ui, 1), true);
1651            }
1652            else if (name == SELECT_NEXT_COLUMN_EXTEND) {
1653                changeSelection(list, EXTEND_SELECTION,
1654                                getNextColumnIndex(list, ui, 1), true);
1655            }
1656            else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
1657                changeSelection(list, CHANGE_LEAD,
1658                                getNextColumnIndex(list, ui, 1), true);
1659            }
1660            else if (name == SELECT_PREVIOUS_ROW) {
1661                changeSelection(list, CHANGE_SELECTION,
1662                                getNextIndex(list, ui, -1), true);
1663            }
1664            else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
1665                changeSelection(list, EXTEND_SELECTION,
1666                                getNextIndex(list, ui, -1), true);
1667            }
1668            else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
1669                changeSelection(list, CHANGE_LEAD,
1670                                getNextIndex(list, ui, -1), true);
1671            }
1672            else if (name == SELECT_NEXT_ROW) {
1673                changeSelection(list, CHANGE_SELECTION,
1674                                getNextIndex(list, ui, 1), true);
1675            }
1676            else if (name == SELECT_NEXT_ROW_EXTEND) {
1677                changeSelection(list, EXTEND_SELECTION,
1678                                getNextIndex(list, ui, 1), true);
1679            }
1680            else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
1681                changeSelection(list, CHANGE_LEAD,
1682                                getNextIndex(list, ui, 1), true);
1683            }
1684            else if (name == SELECT_FIRST_ROW) {
1685                changeSelection(list, CHANGE_SELECTION, 0, true);
1686            }
1687            else if (name == SELECT_FIRST_ROW_EXTEND) {
1688                changeSelection(list, EXTEND_SELECTION, 0, true);
1689            }
1690            else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
1691                changeSelection(list, CHANGE_LEAD, 0, true);
1692            }
1693            else if (name == SELECT_LAST_ROW) {
1694                changeSelection(list, CHANGE_SELECTION,
1695                                list.getModel().getSize() - 1, true);
1696            }
1697            else if (name == SELECT_LAST_ROW_EXTEND) {
1698                changeSelection(list, EXTEND_SELECTION,
1699                                list.getModel().getSize() - 1, true);
1700            }
1701            else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
1702                changeSelection(list, CHANGE_LEAD,
1703                                list.getModel().getSize() - 1, true);
1704            }
1705            else if (name == SCROLL_UP) {
1706                scroll(list, CHANGE_SELECTION, true);
1707            }
1708            else if (name == SCROLL_UP_EXTEND) {
1709                scroll(list, EXTEND_SELECTION, true);
1710            }
1711            else if (name == SCROLL_UP_CHANGE_LEAD) {
1712                scroll(list, CHANGE_LEAD, true);
1713            }
1714            else if (name == SCROLL_DOWN) {
1715                scroll(list, CHANGE_SELECTION, false);
1716            }
1717            else if (name == SCROLL_DOWN_EXTEND) {
1718                scroll(list, EXTEND_SELECTION, false);
1719            }
1720            else if (name == SCROLL_DOWN_CHANGE_LEAD) {
1721                scroll(list, CHANGE_LEAD, false);
1722            }
1723            else if (name == SELECT_ALL) {
1724                selectAll(list);
1725            }
1726            else if (name == CLEAR_SELECTION) {
1727                clearSelection(list);
1728            }
1729            else if (name == ADD_TO_SELECTION) {
1730                int index = list.getSelectionModel().getLeadSelectionIndex();
1731                if (!list.isSelectedIndex(index)) {
1732                    int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
1733                    list.setValueIsAdjusting(true);
1734                    list.addSelectionInterval(index, index);
1735                    list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
1736                    list.setValueIsAdjusting(false);
1737                }
1738            }
1739            else if (name == TOGGLE_AND_ANCHOR) {
1740                int index = list.getSelectionModel().getLeadSelectionIndex();
1741                if (list.isSelectedIndex(index)) {
1742                    list.removeSelectionInterval(index, index);
1743                } else {
1744                    list.addSelectionInterval(index, index);
1745                }
1746            }
1747            else if (name == EXTEND_TO) {
1748                changeSelection(list, EXTEND_SELECTION,
1749                                list.getSelectionModel().getLeadSelectionIndex(),
1750                                false);
1751            }
1752            else if (name == MOVE_SELECTION_TO) {
1753                changeSelection(list, CHANGE_SELECTION,
1754                                list.getSelectionModel().getLeadSelectionIndex(),
1755                                false);
1756            }
1757        }
1758
1759        public boolean isEnabled(Object JavaDoc c) {
1760            Object JavaDoc name = getName();
1761            if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
1762                    name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
1763                    name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
1764                    name == SELECT_NEXT_ROW_CHANGE_LEAD ||
1765                    name == SELECT_FIRST_ROW_CHANGE_LEAD ||
1766                    name == SELECT_LAST_ROW_CHANGE_LEAD ||
1767                    name == SCROLL_UP_CHANGE_LEAD ||
1768                    name == SCROLL_DOWN_CHANGE_LEAD) {
1769
1770                // discontinuous selection actions are only enabled for
1771
// DefaultListSelectionModel
1772
return c != null && ((JList)c).getSelectionModel()
1773                                        instanceof DefaultListSelectionModel;
1774            }
1775
1776            return true;
1777        }
1778
1779        private void clearSelection(JList list) {
1780        list.clearSelection();
1781        }
1782
1783        private void selectAll(JList list) {
1784            int size = list.getModel().getSize();
1785            if (size > 0) {
1786                ListSelectionModel lsm = list.getSelectionModel();
1787                if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
1788                    int leadIndex = list.getLeadSelectionIndex();
1789                    if (leadIndex != -1) {
1790                        list.setSelectionInterval(leadIndex, leadIndex);
1791                    } else if (list.getMinSelectionIndex() == -1) {
1792                        list.setSelectionInterval(0, 0);
1793                        list.ensureIndexIsVisible(0);
1794                    }
1795                } else {
1796                    list.setValueIsAdjusting(true);
1797
1798                    int anchor = lsm.getAnchorSelectionIndex();
1799                    int lead = lsm.getLeadSelectionIndex();
1800                    list.setSelectionInterval(0, size - 1);
1801
1802                    // this is called simply to restore the anchor and lead
1803
list.addSelectionInterval(anchor, lead);
1804
1805                    list.setValueIsAdjusting(false);
1806                }
1807            }
1808        }
1809
1810        private void scroll(JList list, int type,
1811                            boolean up) {
1812            int index = getNextPageIndex(list, up);
1813
1814            if (index != -1) {
1815                changeSelection(list, type, index, false);
1816
1817                // down
1818
Rectangle visRect = list.getVisibleRect();
1819                Rectangle cellBounds = list.getCellBounds(index, index);
1820
1821                if (!up) {
1822                    cellBounds.y = Math.max(0, cellBounds.y +
1823                                            cellBounds.height -visRect.height);
1824                    cellBounds.height = visRect.height;
1825                }
1826                else {
1827                    cellBounds.height = visRect.height;
1828                }
1829                list.scrollRectToVisible(cellBounds);
1830            }
1831        }
1832
1833    private int getNextPageIndex(JList list, boolean up) {
1834            if (up) {
1835                int index = list.getFirstVisibleIndex();
1836                ListSelectionModel lsm = list.getSelectionModel();
1837
1838                if (lsm.getLeadSelectionIndex() == index) {
1839                    Rectangle visRect = list.getVisibleRect();
1840                    visRect.y = Math.max(0, visRect.y - visRect.height);
1841                    index = list.locationToIndex(visRect.getLocation());
1842                }
1843                return index;
1844            }
1845            // down
1846
int index = list.getLastVisibleIndex();
1847        ListSelectionModel lsm = list.getSelectionModel();
1848
1849        if (index == -1) {
1850        // Will happen if size < viewport size.
1851
index = list.getModel().getSize() - 1;
1852        }
1853        if (lsm.getLeadSelectionIndex() == index) {
1854        Rectangle visRect = list.getVisibleRect();
1855        visRect.y += visRect.height + visRect.height - 1;
1856        index = list.locationToIndex(visRect.getLocation());
1857        if (index == -1) {
1858            index = list.getModel().getSize() - 1;
1859        }
1860        }
1861        return index;
1862    }
1863
1864        private void changeSelection(JList list, int type,
1865                                     int index, boolean scroll) {
1866        if (index >= 0 && index < list.getModel().getSize()) {
1867        ListSelectionModel lsm = list.getSelectionModel();
1868
1869                // CHANGE_LEAD is only valid with multiple interval selection
1870
if (type == CHANGE_LEAD &&
1871                        list.getSelectionMode()
1872                            != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
1873
1874                    type = CHANGE_SELECTION;
1875                }
1876
1877        if (type == EXTEND_SELECTION) {
1878            int anchor = lsm.getAnchorSelectionIndex();
1879            if (anchor == -1) {
1880            anchor = index;
1881            }
1882                    
1883            list.setSelectionInterval(anchor, index);
1884        }
1885        else if (type == CHANGE_SELECTION) {
1886            list.setSelectedIndex(index);
1887        }
1888        else {
1889                    // casting should be safe since the action is only enabled
1890
// for DefaultListSelectionModel
1891
((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
1892        }
1893                if (scroll) {
1894                    list.ensureIndexIsVisible(index);
1895                }
1896        }
1897        }
1898
1899    private int getNextColumnIndex(JList list, BasicListUI JavaDoc ui,
1900                                       int amount) {
1901            if (list.getLayoutOrientation() != JList.VERTICAL) {
1902                int index = list.getLeadSelectionIndex();
1903                int size = list.getModel().getSize();
1904
1905                if (index == -1) {
1906                    return 0;
1907                } else if (size == 1) {
1908                    // there's only one item so we should select it
1909
return 0;
1910                } else if (ui == null || ui.columnCount <= 1) {
1911                    return -1;
1912                }
1913
1914                int column = ui.convertModelToColumn(index);
1915                int row = ui.convertModelToRow(index);
1916
1917                column += amount;
1918                if (column >= ui.columnCount || column < 0) {
1919                    // No wrapping.
1920
return -1;
1921                }
1922                int maxRowCount = ui.getRowCount(column);
1923                if (row >= maxRowCount) {
1924                    row = maxRowCount - 1;
1925                }
1926                return ui.getModelIndex(column, row);
1927            }
1928            // Won't change the selection.
1929
return -1;
1930        }
1931
1932    private int getNextIndex(JList list, BasicListUI JavaDoc ui, int amount) {
1933        int index = list.getLeadSelectionIndex();
1934        int size = list.getModel().getSize();
1935
1936        if (index == -1) {
1937        if (size > 0) {
1938            if (amount > 0) {
1939            index = 0;
1940            }
1941            else {
1942            index = size - 1;
1943            }
1944        }
1945            } else if (size == 1) {
1946                // there's only one item so we should select it
1947
index = 0;
1948            } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
1949                if (ui != null) {
1950                    index += ui.columnCount * amount;
1951                }
1952            } else {
1953                index += amount;
1954            }
1955
1956            return index;
1957        }
1958    }
1959
1960
1961    private class Handler implements FocusListener, KeyListener,
1962                          ListDataListener, ListSelectionListener,
1963                          MouseInputListener, PropertyChangeListener JavaDoc {
1964        //
1965
// KeyListener
1966
//
1967
private String JavaDoc prefix = "";
1968    private String JavaDoc typedString = "";
1969    private long lastTime = 0L;
1970
1971    /**
1972     * Invoked when a key has been typed.
1973     *
1974     * Moves the keyboard focus to the first element whose prefix matches the
1975     * sequence of alphanumeric keys pressed by the user with delay less
1976     * than value of <code>timeFactor</code> property (or 1000 milliseconds
1977     * if it is not defined). Subsequent same key presses move the keyboard
1978     * focus to the next object that starts with the same letter until another
1979     * key is pressed, then it is treated as the prefix with appropriate number
1980     * of the same letters followed by first typed anothe letter.
1981     */

1982    public void keyTyped(KeyEvent e) {
1983        JList src = (JList)e.getSource();
1984        ListModel model = src.getModel();
1985
1986            if (model.getSize() == 0 || e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
1987        isNavigationKey(e)) {
1988                // Nothing to select
1989
return;
1990            }
1991        boolean startingFromSelection = true;
1992
1993        char c = e.getKeyChar();
1994
1995        long time = e.getWhen();
1996        int startIndex = src.getLeadSelectionIndex();
1997        if (time - lastTime < timeFactor) {
1998        typedString += c;
1999        if((prefix.length() == 1) && (c == prefix.charAt(0))) {
2000            // Subsequent same key presses move the keyboard focus to the next
2001
// object that starts with the same letter.
2002
startIndex++;
2003        } else {
2004            prefix = typedString;
2005        }
2006        } else {
2007            startIndex++;
2008        typedString = "" + c;
2009        prefix = typedString;
2010        }
2011        lastTime = time;
2012
2013        if (startIndex < 0 || startIndex >= model.getSize()) {
2014        startingFromSelection = false;
2015        startIndex = 0;
2016        }
2017        int index = src.getNextMatch(prefix, startIndex,
2018                     Position.Bias.Forward);
2019        if (index >= 0) {
2020        src.setSelectedIndex(index);
2021        src.ensureIndexIsVisible(index);
2022        } else if (startingFromSelection) { // wrap
2023
index = src.getNextMatch(prefix, 0,
2024                     Position.Bias.Forward);
2025        if (index >= 0) {
2026            src.setSelectedIndex(index);
2027            src.ensureIndexIsVisible(index);
2028        }
2029        }
2030    }
2031    
2032    /**
2033     * Invoked when a key has been pressed.
2034     *
2035     * Checks to see if the key event is a navigation key to prevent
2036     * dispatching these keys for the first letter navigation.
2037     */

2038    public void keyPressed(KeyEvent e) {
2039        if ( isNavigationKey(e) ) {
2040        prefix = "";
2041        typedString = "";
2042        lastTime = 0L;
2043        }
2044    }
2045    
2046    /**
2047     * Invoked when a key has been released.
2048     * See the class description for {@link KeyEvent} for a definition of
2049     * a key released event.
2050     */

2051    public void keyReleased(KeyEvent e) {
2052    }
2053
2054    /**
2055     * Returns whether or not the supplied key event maps to a key that is used for
2056     * navigation. This is used for optimizing key input by only passing non-
2057     * navigation keys to the first letter navigation mechanism.
2058     */

2059    private boolean isNavigationKey(KeyEvent event) {
2060        InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2061        KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2062
2063        if (inputMap != null && inputMap.get(key) != null) {
2064        return true;
2065        }
2066        return false;
2067    }
2068
2069        //
2070
// PropertyChangeListener
2071
//
2072
public void propertyChange(PropertyChangeEvent JavaDoc e) {
2073            String JavaDoc propertyName = e.getPropertyName();
2074
2075            /* If the JList.model property changes, remove our listener,
2076             * listDataListener from the old model and add it to the new one.
2077             */

2078            if (propertyName == "model") {
2079                ListModel oldModel = (ListModel)e.getOldValue();
2080                ListModel newModel = (ListModel)e.getNewValue();
2081                if (oldModel != null) {
2082                    oldModel.removeListDataListener(listDataListener);
2083                }
2084                if (newModel != null) {
2085                    newModel.addListDataListener(listDataListener);
2086                }
2087                updateLayoutStateNeeded |= modelChanged;
2088        redrawList();
2089            }
2090
2091            /* If the JList.selectionModel property changes, remove our listener,
2092             * listSelectionListener from the old selectionModel and add it to the new one.
2093             */

2094            else if (propertyName == "selectionModel") {
2095                ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
2096                ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
2097                if (oldModel != null) {
2098                    oldModel.removeListSelectionListener(listSelectionListener);
2099                }
2100                if (newModel != null) {
2101                    newModel.addListSelectionListener(listSelectionListener);
2102                }
2103                updateLayoutStateNeeded |= modelChanged;
2104        redrawList();
2105            }
2106            else if (propertyName == "cellRenderer") {
2107                updateLayoutStateNeeded |= cellRendererChanged;
2108        redrawList();
2109            }
2110            else if (propertyName == "font") {
2111                updateLayoutStateNeeded |= fontChanged;
2112        redrawList();
2113            }
2114            else if (propertyName == "prototypeCellValue") {
2115                updateLayoutStateNeeded |= prototypeCellValueChanged;
2116        redrawList();
2117            }
2118            else if (propertyName == "fixedCellHeight") {
2119                updateLayoutStateNeeded |= fixedCellHeightChanged;
2120        redrawList();
2121            }
2122            else if (propertyName == "fixedCellWidth") {
2123                updateLayoutStateNeeded |= fixedCellWidthChanged;
2124        redrawList();
2125            }
2126            else if (propertyName == "cellRenderer") {
2127                updateLayoutStateNeeded |= cellRendererChanged;
2128        redrawList();
2129            }
2130            else if (propertyName == "selectionForeground") {
2131        list.repaint();
2132            }
2133            else if (propertyName == "selectionBackground") {
2134        list.repaint();
2135            }
2136            else if ("layoutOrientation" == propertyName) {
2137                updateLayoutStateNeeded |= layoutOrientationChanged;
2138                layoutOrientation = list.getLayoutOrientation();
2139                redrawList();
2140            }
2141            else if ("visibleRowCount" == propertyName) {
2142                if (layoutOrientation != JList.VERTICAL) {
2143                    updateLayoutStateNeeded |= layoutOrientationChanged;
2144                    redrawList();
2145                }
2146            }
2147            else if ("componentOrientation" == propertyName) {
2148        isLeftToRight = list.getComponentOrientation().isLeftToRight();
2149                updateLayoutStateNeeded |= componentOrientationChanged;
2150                redrawList();
2151
2152        InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
2153        SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
2154                         inputMap);
2155        } else if ("List.isFileList" == propertyName) {
2156                updateIsFileList();
2157        redrawList();
2158            } else if ("transferHandler" == propertyName) {
2159                DropTarget dropTarget = list.getDropTarget();
2160                if (dropTarget instanceof UIResource) {
2161                    try {
2162                        dropTarget.addDropTargetListener(new ListDropTargetListener());
2163                    } catch (TooManyListenersException JavaDoc tmle) {
2164                        // should not happen... swing drop target is multicast
2165
}
2166                }
2167            }
2168        }
2169
2170        //
2171
// ListDataListener
2172
//
2173
public void intervalAdded(ListDataEvent e) {
2174            updateLayoutStateNeeded = modelChanged;
2175
2176            int minIndex = Math.min(e.getIndex0(), e.getIndex1());
2177            int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
2178
2179            /* Sync the SelectionModel with the DataModel.
2180             */

2181
2182            ListSelectionModel sm = list.getSelectionModel();
2183            if (sm != null) {
2184                sm.insertIndexInterval(minIndex, maxIndex - minIndex+1, true);
2185            }
2186
2187            /* Repaint the entire list, from the origin of
2188             * the first added cell, to the bottom of the
2189             * component.
2190             */

2191            redrawList();
2192        }
2193
2194
2195        public void intervalRemoved(ListDataEvent e)
2196        {
2197            updateLayoutStateNeeded = modelChanged;
2198
2199            /* Sync the SelectionModel with the DataModel.
2200             */

2201
2202            ListSelectionModel sm = list.getSelectionModel();
2203            if (sm != null) {
2204                sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
2205            }
2206
2207            /* Repaint the entire list, from the origin of
2208             * the first removed cell, to the bottom of the
2209             * component.
2210             */

2211
2212            redrawList();
2213        }
2214
2215
2216        public void contentsChanged(ListDataEvent e) {
2217            updateLayoutStateNeeded = modelChanged;
2218        redrawList();
2219        }
2220
2221
2222        //
2223
// ListSelectionListener
2224
//
2225
public void valueChanged(ListSelectionEvent e) {
2226            maybeUpdateLayoutState();
2227
2228            Rectangle bounds = getCellBounds(list, e.getFirstIndex(),
2229                                             e.getLastIndex());
2230
2231            if (bounds != null) {
2232                list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2233            }
2234        }
2235
2236        //
2237
// MouseListener
2238
//
2239
private boolean selectedOnPress;
2240
2241        public void mouseClicked(MouseEvent e) {
2242        }
2243
2244        public void mouseEntered(MouseEvent e) {
2245        }
2246
2247        public void mouseExited(MouseEvent e) {
2248        }
2249
2250        public void mousePressed(MouseEvent e) {
2251        if (e.isConsumed()) {
2252        selectedOnPress = false;
2253        return;
2254        }
2255        selectedOnPress = true;
2256        adjustFocusAndSelection(e);
2257    }
2258
2259    private void adjustFocusAndSelection(MouseEvent e) {
2260        if (!SwingUtilities.isLeftMouseButton(e)) {
2261            return;
2262        }
2263
2264        if (!list.isEnabled()) {
2265        return;
2266        }
2267
2268        /* Request focus before updating the list selection. This implies
2269         * that the current focus owner will see a focusLost() event
2270         * before the lists selection is updated IF requestFocus() is
2271         * synchronous (it is on Windows). See bug 4122345
2272         */

2273            SwingUtilities2.adjustFocus(list);
2274
2275            adjustSelection(e);
2276        }
2277
2278        protected void adjustSelection(MouseEvent e) {
2279            int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
2280            if (row < 0) {
2281                // If shift is down in multi-select, we should do nothing.
2282
// For single select or non-shift-click, clear the selection
2283
if (isFileList &&
2284                    e.getID() == MouseEvent.MOUSE_PRESSED &&
2285                    (!e.isShiftDown() ||
2286                     list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
2287                    list.clearSelection();
2288                }
2289            }
2290            else {
2291                if (!DRAG_FIX) {
2292                    boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
2293                    list.setValueIsAdjusting(adjusting);
2294                }
2295                int anchorIndex = list.getAnchorSelectionIndex();
2296                if (e.isControlDown()) {
2297                    if (e.isShiftDown() && anchorIndex != -1) {
2298                        if (list.isSelectedIndex(anchorIndex)) {
2299                            list.addSelectionInterval(anchorIndex, row);
2300                        } else {
2301                            list.removeSelectionInterval(anchorIndex, row);
2302                            list.addSelectionInterval(row, row);
2303                            list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
2304                        }
2305                    } else if (list.isSelectedIndex(row)) {
2306                        list.removeSelectionInterval(row, row);
2307                    }
2308                    else {
2309                        list.addSelectionInterval(row, row);
2310                    }
2311                }
2312                else if (e.isShiftDown() && (anchorIndex != -1)) {
2313                    list.setSelectionInterval(anchorIndex, row);
2314                }
2315                else {
2316                    list.setSelectionInterval(row, row);
2317                }
2318            }
2319        }
2320
2321        public void mouseDragged(MouseEvent e) {
2322        if (e.isConsumed()) {
2323        return;
2324        }
2325        if (!SwingUtilities.isLeftMouseButton(e)) {
2326            return;
2327        }
2328        if (!list.isEnabled()) {
2329        return;
2330        }
2331
2332            mouseDraggedImpl(e);
2333        }
2334
2335        protected void mouseDraggedImpl(MouseEvent e) {
2336            if (e.isShiftDown() || e.isControlDown()) {
2337                return;
2338            }
2339
2340            int row = locationToIndex(list, e.getPoint());
2341            if (row != -1) {
2342                // 4835633. Dragging onto a File should not select it.
2343
if (isFileList) {
2344                    return;
2345                }
2346                Rectangle cellBounds = getCellBounds(list, row, row);
2347                if (cellBounds != null) {
2348                    list.scrollRectToVisible(cellBounds);
2349                    list.setSelectionInterval(row, row);
2350                }
2351            }
2352        }
2353
2354        public void mouseMoved(MouseEvent e) {
2355        }
2356
2357        public void mouseReleased(MouseEvent e) {
2358        if (selectedOnPress) {
2359        if (!SwingUtilities.isLeftMouseButton(e)) {
2360            return;
2361        }
2362
2363        list.setValueIsAdjusting(false);
2364        } else {
2365        adjustFocusAndSelection(e);
2366        }
2367        }
2368
2369        //
2370
// FocusListener
2371
//
2372
protected void repaintCellFocus()
2373        {
2374            int leadIndex = list.getLeadSelectionIndex();
2375            if (leadIndex != -1) {
2376                Rectangle r = getCellBounds(list, leadIndex, leadIndex);
2377                if (r != null) {
2378                    list.repaint(r.x, r.y, r.width, r.height);
2379                }
2380            }
2381        }
2382
2383        /* The focusGained() focusLost() methods run when the JList
2384         * focus changes.
2385         */

2386
2387        public void focusGained(FocusEvent e) {
2388            repaintCellFocus();
2389        }
2390
2391        public void focusLost(FocusEvent e) {
2392            repaintCellFocus();
2393        }
2394    }
2395
2396
2397    private class DragFixHandler extends Handler implements BeforeDrag {
2398
2399        // Whether or not the mouse press (which is being considered as part
2400
// of a drag sequence) also caused the selection change to be fully
2401
// processed.
2402
private boolean dragPressDidSelection;
2403
2404        public void mousePressed(MouseEvent e) {
2405            if (SwingUtilities2.shouldIgnore(e, list)) {
2406                return;
2407            }
2408
2409            boolean dragEnabled = list.getDragEnabled();
2410            boolean grabFocus = true;
2411
2412            // different behavior if drag is enabled
2413
if (dragEnabled) {
2414                int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
2415                // if we have a valid row and this is a drag initiating event
2416
if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
2417                    dragPressDidSelection = false;
2418
2419                    if (e.isControlDown()) {
2420                        // do nothing for control - will be handled on release
2421
// or when drag starts
2422
return;
2423                    } else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
2424                        // clicking on something that's already selected
2425
// and need to make it the lead now
2426
list.addSelectionInterval(row, row);
2427                        return;
2428                    }
2429
2430                    // could be a drag initiating event - don't grab focus
2431
grabFocus = false;
2432
2433                    dragPressDidSelection = true;
2434                }
2435            } else {
2436                // When drag is enabled mouse drags won't change the selection
2437
// in the list, so we only set the isAdjusting flag when it's
2438
// not enabled
2439
list.setValueIsAdjusting(true);
2440            }
2441
2442            if (grabFocus) {
2443                SwingUtilities2.adjustFocus(list);
2444            }
2445
2446            adjustSelection(e);
2447        }
2448
2449        public void dragStarting(MouseEvent me) {
2450            if (me.isControlDown()) {
2451                int row = SwingUtilities2.loc2IndexFileList(list, me.getPoint());
2452                list.addSelectionInterval(row, row);
2453            }
2454        }
2455
2456        public void mouseDragged(MouseEvent e) {
2457            if (SwingUtilities2.shouldIgnore(e, list)) {
2458                return;
2459            }
2460
2461            if (list.getDragEnabled()) {
2462                DragRecognitionSupport.mouseDragged(e, this);
2463                return;
2464            }
2465
2466            mouseDraggedImpl(e);
2467        }
2468
2469        public void mouseReleased(MouseEvent e) {
2470            if (SwingUtilities2.shouldIgnore(e, list)) {
2471                return;
2472            }
2473
2474            if (list.getDragEnabled()) {
2475                MouseEvent me = DragRecognitionSupport.mouseReleased(e);
2476                if (me != null) {
2477                    SwingUtilities2.adjustFocus(list);
2478                    if (!dragPressDidSelection) {
2479                        adjustSelection(me);
2480                    }
2481                }
2482            } else {
2483                list.setValueIsAdjusting(false);
2484            }
2485        }
2486    }
2487
2488
2489    private static final ListDragGestureRecognizer defaultDragRecognizer =
2490        DRAG_FIX ? null : new ListDragGestureRecognizer();
2491
2492    /**
2493     * Drag gesture recognizer for JList components
2494     */

2495    static class ListDragGestureRecognizer extends BasicDragGestureRecognizer JavaDoc {
2496
2497    /**
2498     * Determines if the following are true:
2499     * <ul>
2500     * <li>the press event is located over a selection
2501     * <li>the dragEnabled property is true
2502     * <li>A TranferHandler is installed
2503     * </ul>
2504     * <p>
2505     * This is implemented to perform the superclass behavior
2506     * followed by a check if the dragEnabled
2507     * property is set and if the location picked is selected.
2508     */

2509        protected boolean isDragPossible(MouseEvent e) {
2510        if (super.isDragPossible(e)) {
2511        JList list = (JList) this.getComponent(e);
2512        if (list.getDragEnabled()) {
2513            ListUI ui = list.getUI();
2514            int row = SwingUtilities2.loc2IndexFileList(list,
2515                                                        e.getPoint());
2516            if ((row != -1) && list.isSelectedIndex(row)) {
2517            return true;
2518            }
2519        }
2520        }
2521        return false;
2522    }
2523    }
2524
2525    /**
2526     * A DropTargetListener to extend the default Swing handling of drop operations
2527     * by moving the list selection to the nearest location to the mouse pointer.
2528     * Also adds autoscroll.
2529     */

2530    class ListDropTargetListener extends BasicDropTargetListener JavaDoc {
2531    /**
2532     * called to save the state of a component in case it needs to
2533     * be restored because a drop is not performed.
2534     */

2535        protected void saveComponentState(JComponent comp) {
2536        JList list = (JList) comp;
2537        selectedIndices = list.getSelectedIndices();
2538    }
2539
2540    /**
2541     * called to restore the state of a component
2542     * because a drop was not performed.
2543     */

2544        protected void restoreComponentState(JComponent comp) {
2545        JList list = (JList) comp;
2546        list.setSelectedIndices(selectedIndices);
2547    }
2548
2549    /**
2550     * called to set the insertion location to match the current
2551     * mouse pointer coordinates.
2552     */

2553        protected void updateInsertionLocation(JComponent comp, Point p) {
2554        JList list = (JList) comp;
2555            int index = locationToIndex(list, p);
2556            if (index != -1) {
2557        list.setSelectionInterval(index, index);
2558        }
2559    }
2560
2561    private int[] selectedIndices;
2562    }
2563
2564    private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
2565
2566    static class ListTransferHandler extends TransferHandler implements UIResource {
2567
2568    /**
2569     * Create a Transferable to use as the source for a data transfer.
2570     *
2571     * @param c The component holding the data to be transfered. This
2572     * argument is provided to enable sharing of TransferHandlers by
2573     * multiple components.
2574     * @return The representation of the data to be transfered.
2575     *
2576     */

2577        protected Transferable JavaDoc createTransferable(JComponent c) {
2578        if (c instanceof JList) {
2579        JList list = (JList) c;
2580        Object JavaDoc[] values = list.getSelectedValues();
2581
2582                if (values == null || values.length == 0) {
2583                    return null;
2584                }
2585        
2586                StringBuffer JavaDoc plainBuf = new StringBuffer JavaDoc();
2587                StringBuffer JavaDoc htmlBuf = new StringBuffer JavaDoc();
2588        
2589                htmlBuf.append("<html>\n<body>\n<ul>\n");
2590
2591                for (int i = 0; i < values.length; i++) {
2592                    Object JavaDoc obj = values[i];
2593                    String JavaDoc val = ((obj == null) ? "" : obj.toString());
2594                    plainBuf.append(val + "\n");
2595                    htmlBuf.append(" <li>" + val + "\n");
2596                }
2597                
2598                // remove the last newline
2599
plainBuf.deleteCharAt(plainBuf.length() - 1);
2600                htmlBuf.append("</ul>\n</body>\n</html>");
2601                
2602                return new BasicTransferable JavaDoc(plainBuf.toString(), htmlBuf.toString());
2603        }
2604
2605        return null;
2606    }
2607
2608        public int getSourceActions(JComponent c) {
2609        return COPY;
2610    }
2611
2612    }
2613}
2614
Popular Tags