1/*
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.swing.plaf.basic;
27
28import sun.swing.DefaultLookup;
29import sun.swing.UIAction;
30
31import javax.swing.*;
32import javax.swing.event.*;
33import javax.swing.plaf.*;
34import javax.swing.text.Position;
35
36import java.awt.*;
37import java.awt.event.*;
38import java.awt.datatransfer.Transferable;
39import java.awt.geom.Point2D;
40
41import java.beans.PropertyChangeListener;
42import java.beans.PropertyChangeEvent;
43
44import sun.swing.SwingUtilities2;
45import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
46
47/**
48 * An extensible implementation of {@code ListUI}.
49 * <p>
50 * {@code BasicListUI} instances cannot be shared between multiple
51 * lists.
52 *
53 * @author Hans Muller
54 * @author Philip Milne
55 * @author Shannon Hickey (drag and drop)
56 */
57public class BasicListUI extends ListUI
58{
59    private static final StringBuilder BASELINE_COMPONENT_KEY =
60        new StringBuilder("List.baselineComponent");
61
62    /**
63     * The instance of {@code JList}.
64     */
65    protected JList<Object> list = null;
66    /**
67     * The instance of {@code CellRendererPane}.
68     */
69    protected CellRendererPane rendererPane;
70
71    // Listeners that this UI attaches to the JList
72    /**
73     * {@code FocusListener} that attached to {@code JList}.
74     */
75    protected FocusListener focusListener;
76    /**
77     * {@code MouseInputListener} that attached to {@code JList}.
78     */
79    protected MouseInputListener mouseInputListener;
80    /**
81     * {@code ListSelectionListener} that attached to {@code JList}.
82     */
83    protected ListSelectionListener listSelectionListener;
84    /**
85     * {@code ListDataListener} that attached to {@code JList}.
86     */
87    protected ListDataListener listDataListener;
88    /**
89     * {@code PropertyChangeListener} that attached to {@code JList}.
90     */
91    protected PropertyChangeListener propertyChangeListener;
92    private Handler handler;
93
94    /**
95     * The array of cells' height
96     */
97    protected int[] cellHeights = null;
98    /**
99     * The height of cell.
100     */
101    protected int cellHeight = -1;
102    /**
103     * The width of cell.
104     */
105    protected int cellWidth = -1;
106    /**
107     * The value represents changes to {@code JList} model.
108     */
109    protected int updateLayoutStateNeeded = modelChanged;
110    /**
111     * Height of the list. When asked to paint, if the current size of
112     * the list differs, this will update the layout state.
113     */
114    private int listHeight;
115
116    /**
117     * Width of the list. When asked to paint, if the current size of
118     * the list differs, this will update the layout state.
119     */
120    private int listWidth;
121
122    /**
123     * The layout orientation of the list.
124     */
125    private int layoutOrientation;
126
127    // Following ivars are used if the list is laying out horizontally
128
129    /**
130     * Number of columns to create.
131     */
132    private int columnCount;
133    /**
134     * Preferred height to make the list, this is only used if the
135     * the list is layed out horizontally.
136     */
137    private int preferredHeight;
138    /**
139     * Number of rows per column. This is only used if the row height is
140     * fixed.
141     */
142    private int rowsPerColumn;
143
144    /**
145     * The time factor to treate the series of typed alphanumeric key
146     * as prefix for first letter navigation.
147     */
148    private long timeFactor = 1000L;
149
150    /**
151     * Local cache of JList's client property "List.isFileList"
152     */
153    private boolean isFileList = false;
154
155    /**
156     * Local cache of JList's component orientation property
157     */
158    private boolean isLeftToRight = true;
159
160    /* The bits below define JList property changes that affect layout.
161     * When one of these properties changes we set a bit in
162     * updateLayoutStateNeeded.  The change is dealt with lazily, see
163     * maybeUpdateLayoutState.  Changes to the JLists model, e.g. the
164     * models length changed, are handled similarly, see DataListener.
165     */
166
167    /**
168     * The bit relates to model changed property.
169     */
170    protected static final int modelChanged = 1 << 0;
171    /**
172     * The bit relates to selection model changed property.
173     */
174    protected static final int selectionModelChanged = 1 << 1;
175    /**
176     * The bit relates to font changed property.
177     */
178    protected static final int fontChanged = 1 << 2;
179    /**
180     * The bit relates to fixed cell width changed property.
181     */
182    protected static final int fixedCellWidthChanged = 1 << 3;
183    /**
184     * The bit relates to fixed cell height changed property.
185     */
186    protected static final int fixedCellHeightChanged = 1 << 4;
187    /**
188     * The bit relates to prototype cell value changed property.
189     */
190    protected static final int prototypeCellValueChanged = 1 << 5;
191    /**
192     * The bit relates to cell renderer changed property.
193     */
194    protected static final int cellRendererChanged = 1 << 6;
195    private static final int layoutOrientationChanged = 1 << 7;
196    private static final int heightChanged = 1 << 8;
197    private static final int widthChanged = 1 << 9;
198    private static final int componentOrientationChanged = 1 << 10;
199
200    private static final int DROP_LINE_THICKNESS = 2;
201
202    static void loadActionMap(LazyActionMap map) {
203        map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
204        map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
205        map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
206        map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
207        map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
208        map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
209        map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
210        map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
211        map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
212        map.put(new Actions(Actions.SELECT_NEXT_ROW));
213        map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
214        map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
215        map.put(new Actions(Actions.SELECT_FIRST_ROW));
216        map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
217        map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
218        map.put(new Actions(Actions.SELECT_LAST_ROW));
219        map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
220        map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
221        map.put(new Actions(Actions.SCROLL_UP));
222        map.put(new Actions(Actions.SCROLL_UP_EXTEND));
223        map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
224        map.put(new Actions(Actions.SCROLL_DOWN));
225        map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
226        map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
227        map.put(new Actions(Actions.SELECT_ALL));
228        map.put(new Actions(Actions.CLEAR_SELECTION));
229        map.put(new Actions(Actions.ADD_TO_SELECTION));
230        map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
231        map.put(new Actions(Actions.EXTEND_TO));
232        map.put(new Actions(Actions.MOVE_SELECTION_TO));
233
234        map.put(TransferHandler.getCutAction().getValue(Action.NAME),
235                TransferHandler.getCutAction());
236        map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
237                TransferHandler.getCopyAction());
238        map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
239                TransferHandler.getPasteAction());
240    }
241
242    /**
243     * Paint one List cell: compute the relevant state, get the "rubber stamp"
244     * cell renderer component, and then use the {@code CellRendererPane} to paint it.
245     * Subclasses may want to override this method rather than {@code paint()}.
246     *
247     * @param g an instance of {@code Graphics}
248     * @param row a row
249     * @param rowBounds a bounding rectangle to render to
250     * @param cellRenderer a list of {@code ListCellRenderer}
251     * @param dataModel a list model
252     * @param selModel a selection model
253     * @param leadIndex a lead index
254     * @see #paint
255     */
256    protected void paintCell(
257        Graphics g,
258        int row,
259        Rectangle rowBounds,
260        ListCellRenderer<Object> cellRenderer,
261        ListModel<Object> dataModel,
262        ListSelectionModel selModel,
263        int leadIndex)
264    {
265        Object value = dataModel.getElementAt(row);
266        boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
267        boolean isSelected = selModel.isSelectedIndex(row);
268
269        Component rendererComponent =
270            cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
271
272        int cx = rowBounds.x;
273        int cy = rowBounds.y;
274        int cw = rowBounds.width;
275        int ch = rowBounds.height;
276
277        if (isFileList) {
278            // Shrink renderer to preferred size. This is mostly used on Windows
279            // where selection is only shown around the file name, instead of
280            // across the whole list cell.
281            int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
282            if (!isLeftToRight) {
283                cx += (cw - w);
284            }
285            cw = w;
286        }
287
288        rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
289    }
290
291
292    /**
293     * Paint the rows that intersect the Graphics objects clipRect.  This
294     * method calls paintCell as necessary.  Subclasses
295     * may want to override these methods.
296     *
297     * @see #paintCell
298     */
299    public void paint(Graphics g, JComponent c) {
300        Shape clip = g.getClip();
301        paintImpl(g, c);
302        g.setClip(clip);
303
304        paintDropLine(g);
305    }
306
307    private void paintImpl(Graphics g, JComponent c)
308    {
309        switch (layoutOrientation) {
310        case JList.VERTICAL_WRAP:
311            if (list.getHeight() != listHeight) {
312                updateLayoutStateNeeded |= heightChanged;
313                redrawList();
314            }
315            break;
316        case JList.HORIZONTAL_WRAP:
317            if (list.getWidth() != listWidth) {
318                updateLayoutStateNeeded |= widthChanged;
319                redrawList();
320            }
321            break;
322        default:
323            break;
324        }
325        maybeUpdateLayoutState();
326
327        ListCellRenderer<Object> renderer = list.getCellRenderer();
328        ListModel<Object> dataModel = list.getModel();
329        ListSelectionModel selModel = list.getSelectionModel();
330        int size;
331
332        if ((renderer == null) || (size = dataModel.getSize()) == 0) {
333            return;
334        }
335
336        // Determine how many columns we need to paint
337        Rectangle paintBounds = g.getClipBounds();
338
339        int startColumn, endColumn;
340        if (c.getComponentOrientation().isLeftToRight()) {
341            startColumn = convertLocationToColumn(paintBounds.x,
342                                                  paintBounds.y);
343            endColumn = convertLocationToColumn(paintBounds.x +
344                                                paintBounds.width,
345                                                paintBounds.y);
346        } else {
347            startColumn = convertLocationToColumn(paintBounds.x +
348                                                paintBounds.width,
349                                                paintBounds.y);
350            endColumn = convertLocationToColumn(paintBounds.x,
351                                                  paintBounds.y);
352        }
353        int maxY = paintBounds.y + paintBounds.height;
354        int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
355        int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
356                           columnCount : 1;
357
358
359        for (int colCounter = startColumn; colCounter <= endColumn;
360             colCounter++) {
361            // And then how many rows in this columnn
362            int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
363            int rowCount = getRowCount(colCounter);
364            int index = getModelIndex(colCounter, row);
365            Rectangle rowBounds = getCellBounds(list, index, index);
366
367            if (rowBounds == null) {
368                // Not valid, bail!
369                return;
370            }
371            while (row < rowCount && rowBounds.y < maxY &&
372                   index < size) {
373                rowBounds.height = getHeight(colCounter, row);
374                g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
375                          rowBounds.height);
376                g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
377                           paintBounds.height);
378                paintCell(g, index, rowBounds, renderer, dataModel, selModel,
379                          leadIndex);
380                rowBounds.y += rowBounds.height;
381                index += rowIncrement;
382                row++;
383            }
384        }
385        // Empty out the renderer pane, allowing renderers to be gc'ed.
386        rendererPane.removeAll();
387    }
388
389    private void paintDropLine(Graphics g) {
390        JList.DropLocation loc = list.getDropLocation();
391        if (loc == null || !loc.isInsert()) {
392            return;
393        }
394
395        Color c = DefaultLookup.getColor(list, this, "List.dropLineColor", null);
396        if (c != null) {
397            g.setColor(c);
398            Rectangle rect = getDropLineRect(loc);
399            g.fillRect(rect.x, rect.y, rect.width, rect.height);
400        }
401    }
402
403    private Rectangle getDropLineRect(JList.DropLocation loc) {
404        int size = list.getModel().getSize();
405
406        if (size == 0) {
407            Insets insets = list.getInsets();
408            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
409                if (isLeftToRight) {
410                    return new Rectangle(insets.left, insets.top, DROP_LINE_THICKNESS, 20);
411                } else {
412                    return new Rectangle(list.getWidth() - DROP_LINE_THICKNESS - insets.right,
413                                         insets.top, DROP_LINE_THICKNESS, 20);
414                }
415            } else {
416                return new Rectangle(insets.left, insets.top,
417                                     list.getWidth() - insets.left - insets.right,
418                                     DROP_LINE_THICKNESS);
419            }
420        }
421
422        Rectangle rect = null;
423        int index = loc.getIndex();
424        boolean decr = false;
425
426        if (layoutOrientation == JList.HORIZONTAL_WRAP) {
427            if (index == size) {
428                decr = true;
429            } else if (index != 0 && convertModelToRow(index)
430                                         != convertModelToRow(index - 1)) {
431
432                Rectangle prev = getCellBounds(list, index - 1);
433                Rectangle me = getCellBounds(list, index);
434                Point p = loc.getDropPoint();
435
436                if (isLeftToRight) {
437                    decr = Point2D.distance(prev.x + prev.width,
438                                            prev.y + (int)(prev.height / 2.0),
439                                            p.x, p.y)
440                           < Point2D.distance(me.x,
441                                              me.y + (int)(me.height / 2.0),
442                                              p.x, p.y);
443                } else {
444                    decr = Point2D.distance(prev.x,
445                                            prev.y + (int)(prev.height / 2.0),
446                                            p.x, p.y)
447                           < Point2D.distance(me.x + me.width,
448                                              me.y + (int)(prev.height / 2.0),
449                                              p.x, p.y);
450                }
451            }
452
453            if (decr) {
454                index--;
455                rect = getCellBounds(list, index);
456                if (isLeftToRight) {
457                    rect.x += rect.width;
458                } else {
459                    rect.x -= DROP_LINE_THICKNESS;
460                }
461            } else {
462                rect = getCellBounds(list, index);
463                if (!isLeftToRight) {
464                    rect.x += rect.width - DROP_LINE_THICKNESS;
465                }
466            }
467
468            if (rect.x >= list.getWidth()) {
469                rect.x = list.getWidth() - DROP_LINE_THICKNESS;
470            } else if (rect.x < 0) {
471                rect.x = 0;
472            }
473
474            rect.width = DROP_LINE_THICKNESS;
475        } else if (layoutOrientation == JList.VERTICAL_WRAP) {
476            if (index == size) {
477                index--;
478                rect = getCellBounds(list, index);
479                rect.y += rect.height;
480            } else if (index != 0 && convertModelToColumn(index)
481                                         != convertModelToColumn(index - 1)) {
482
483                Rectangle prev = getCellBounds(list, index - 1);
484                Rectangle me = getCellBounds(list, index);
485                Point p = loc.getDropPoint();
486                if (Point2D.distance(prev.x + (int)(prev.width / 2.0),
487                                     prev.y + prev.height,
488                                     p.x, p.y)
489                        < Point2D.distance(me.x + (int)(me.width / 2.0),
490                                           me.y,
491                                           p.x, p.y)) {
492
493                    index--;
494                    rect = getCellBounds(list, index);
495                    rect.y += rect.height;
496                } else {
497                    rect = getCellBounds(list, index);
498                }
499            } else {
500                rect = getCellBounds(list, index);
501            }
502
503            if (rect.y >= list.getHeight()) {
504                rect.y = list.getHeight() - DROP_LINE_THICKNESS;
505            }
506
507            rect.height = DROP_LINE_THICKNESS;
508        } else {
509            if (index == size) {
510                index--;
511                rect = getCellBounds(list, index);
512                rect.y += rect.height;
513            } else {
514                rect = getCellBounds(list, index);
515            }
516
517            if (rect.y >= list.getHeight()) {
518                rect.y = list.getHeight() - DROP_LINE_THICKNESS;
519            }
520
521            rect.height = DROP_LINE_THICKNESS;
522        }
523
524        return rect;
525    }
526
527    /**
528     * Returns the baseline.
529     *
530     * @throws NullPointerException {@inheritDoc}
531     * @throws IllegalArgumentException {@inheritDoc}
532     * @see javax.swing.JComponent#getBaseline(int, int)
533     * @since 1.6
534     */
535    public int getBaseline(JComponent c, int width, int height) {
536        super.getBaseline(c, width, height);
537        int rowHeight = list.getFixedCellHeight();
538        UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
539        Component renderer = (Component)lafDefaults.get(
540                BASELINE_COMPONENT_KEY);
541        if (renderer == null) {
542            @SuppressWarnings("unchecked")
543            ListCellRenderer<Object> lcr = (ListCellRenderer)UIManager.get(
544                    "List.cellRenderer");
545
546            // fix for 6711072 some LAFs like Nimbus do not provide this
547            // UIManager key and we should not through a NPE here because of it
548            if (lcr == null) {
549                lcr = new DefaultListCellRenderer();
550            }
551            renderer = lcr.getListCellRendererComponent(
552                    list, "a", -1, false, false);
553            lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
554        }
555        renderer.setFont(list.getFont());
556        // JList actually has much more complex behavior here.
557        // If rowHeight != -1 the rowHeight is either the max of all cell
558        // heights (layout orientation != VERTICAL), or is variable depending
559        // upon the cell.  We assume a default size.
560        // We could theoretically query the real renderer, but that would
561        // not work for an empty model and the results may vary with
562        // the content.
563        if (rowHeight == -1) {
564            rowHeight = renderer.getPreferredSize().height;
565        }
566        return renderer.getBaseline(Integer.MAX_VALUE, rowHeight) +
567                list.getInsets().top;
568    }
569
570    /**
571     * Returns an enum indicating how the baseline of the component
572     * changes as the size changes.
573     *
574     * @throws NullPointerException {@inheritDoc}
575     * @see javax.swing.JComponent#getBaseline(int, int)
576     * @since 1.6
577     */
578    public Component.BaselineResizeBehavior getBaselineResizeBehavior(
579            JComponent c) {
580        super.getBaselineResizeBehavior(c);
581        return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
582    }
583
584    /**
585     * The preferredSize of the list depends upon the layout orientation.
586     *
587     * <table class="striped">
588     * <caption>Describes the preferred size for each layout orientation
589     * </caption>
590     * <thead>
591     * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
592     * </thead>
593     * <tbody>
594     * <tr>
595     *   <td>JList.VERTICAL
596     *   <td>The preferredSize of the list is total height of the rows
597     *       and the maximum width of the cells.  If JList.fixedCellHeight
598     *       is specified then the total height of the rows is just
599     *       (cellVerticalMargins + fixedCellHeight) * model.getSize() where
600     *       rowVerticalMargins is the space we allocate for drawing
601     *       the yellow focus outline.  Similarly if fixedCellWidth is
602     *       specified then we just use that.
603     *   </td>
604     * <tr>
605     *   <td>JList.VERTICAL_WRAP
606     *   <td>If the visible row count is greater than zero, the preferredHeight
607     *       is the maximum cell height * visibleRowCount. If the visible row
608     *       count is &lt;= 0, the preferred height is either the current height
609     *       of the list, or the maximum cell height, whichever is
610     *       bigger. The preferred width is than the maximum cell width *
611     *       number of columns needed. Where the number of columns needs is
612     *       list.height / max cell height. Max cell height is either the fixed
613     *       cell height, or is determined by iterating through all the cells
614     *       to find the maximum height from the ListCellRenderer.
615     * <tr>
616     *   <td>JList.HORIZONTAL_WRAP
617     *   <td>If the visible row count is greater than zero, the preferredHeight
618     *       is the maximum cell height * adjustedRowCount.  Where
619     *       visibleRowCount is used to determine the number of columns.
620     *       Because this lays out horizontally the number of rows is
621     *       then determined from the column count.  For example, lets say
622     *       you have a model with 10 items and the visible row count is 8.
623     *       The number of columns needed to display this is 2, but you no
624     *       longer need 8 rows to display this, you only need 5, thus
625     *       the adjustedRowCount is 5.
626     *       <p>If the visible row
627     *       count is &lt;= 0, the preferred height is dictated by the
628     *       number of columns, which will be as many as can fit in the width
629     *       of the <code>JList</code> (width / max cell width), with at
630     *       least one column.  The preferred height then becomes the
631     *       model size / number of columns * maximum cell height.
632     *       Max cell height is either the fixed
633     *       cell height, or is determined by iterating through all the cells
634     *       to find the maximum height from the ListCellRenderer.
635     * </tbody>
636     * </table>
637     * The above specifies the raw preferred width and height. The resulting
638     * preferred width is the above width + insets.left + insets.right and
639     * the resulting preferred height is the above height + insets.top +
640     * insets.bottom. Where the <code>Insets</code> are determined from
641     * <code>list.getInsets()</code>.
642     *
643     * @param c The JList component.
644     * @return The total size of the list.
645     */
646    public Dimension getPreferredSize(JComponent c) {
647        maybeUpdateLayoutState();
648
649        int lastRow = list.getModel().getSize() - 1;
650        if (lastRow < 0) {
651            return new Dimension(0, 0);
652        }
653
654        Insets insets = list.getInsets();
655        int width = cellWidth * columnCount + insets.left + insets.right;
656        int height;
657
658        if (layoutOrientation != JList.VERTICAL) {
659            height = preferredHeight;
660        }
661        else {
662            Rectangle bounds = getCellBounds(list, lastRow);
663
664            if (bounds != null) {
665                height = bounds.y + bounds.height + insets.bottom;
666            }
667            else {
668                height = 0;
669            }
670        }
671        return new Dimension(width, height);
672    }
673
674
675    /**
676     * Selected the previous row and force it to be visible.
677     *
678     * @see JList#ensureIndexIsVisible
679     */
680    protected void selectPreviousIndex() {
681        int s = list.getSelectedIndex();
682        if(s > 0) {
683            s -= 1;
684            list.setSelectedIndex(s);
685            list.ensureIndexIsVisible(s);
686        }
687    }
688
689
690    /**
691     * Selected the previous row and force it to be visible.
692     *
693     * @see JList#ensureIndexIsVisible
694     */
695    protected void selectNextIndex()
696    {
697        int s = list.getSelectedIndex();
698        if((s + 1) < list.getModel().getSize()) {
699            s += 1;
700            list.setSelectedIndex(s);
701            list.ensureIndexIsVisible(s);
702        }
703    }
704
705
706    /**
707     * Registers the keyboard bindings on the <code>JList</code> that the
708     * <code>BasicListUI</code> is associated with. This method is called at
709     * installUI() time.
710     *
711     * @see #installUI
712     */
713    protected void installKeyboardActions() {
714        InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
715
716        SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
717                                           inputMap);
718
719        LazyActionMap.installLazyActionMap(list, BasicListUI.class,
720                                           "List.actionMap");
721    }
722
723    InputMap getInputMap(int condition) {
724        if (condition == JComponent.WHEN_FOCUSED) {
725            InputMap keyMap = (InputMap)DefaultLookup.get(
726                             list, this, "List.focusInputMap");
727            InputMap rtlKeyMap;
728
729            if (isLeftToRight ||
730                ((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
731                              "List.focusInputMap.RightToLeft")) == null)) {
732                    return keyMap;
733            } else {
734                rtlKeyMap.setParent(keyMap);
735                return rtlKeyMap;
736            }
737        }
738        return null;
739    }
740
741    /**
742     * Unregisters keyboard actions installed from
743     * <code>installKeyboardActions</code>.
744     * This method is called at uninstallUI() time - subclassess should
745     * ensure that all of the keyboard actions registered at installUI
746     * time are removed here.
747     *
748     * @see #installUI
749     */
750    protected void uninstallKeyboardActions() {
751        SwingUtilities.replaceUIActionMap(list, null);
752        SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
753    }
754
755
756    /**
757     * Creates and installs the listeners for the JList, its model, and its
758     * selectionModel.  This method is called at installUI() time.
759     *
760     * @see #installUI
761     * @see #uninstallListeners
762     */
763    protected void installListeners()
764    {
765        TransferHandler th = list.getTransferHandler();
766        if (th == null || th instanceof UIResource) {
767            list.setTransferHandler(defaultTransferHandler);
768            // default TransferHandler doesn't support drop
769            // so we don't want drop handling
770            if (list.getDropTarget() instanceof UIResource) {
771                list.setDropTarget(null);
772            }
773        }
774
775        focusListener = createFocusListener();
776        mouseInputListener = createMouseInputListener();
777        propertyChangeListener = createPropertyChangeListener();
778        listSelectionListener = createListSelectionListener();
779        listDataListener = createListDataListener();
780
781        list.addFocusListener(focusListener);
782        list.addMouseListener(mouseInputListener);
783        list.addMouseMotionListener(mouseInputListener);
784        list.addPropertyChangeListener(propertyChangeListener);
785        list.addKeyListener(getHandler());
786
787        ListModel<Object> model = list.getModel();
788        if (model != null) {
789            model.addListDataListener(listDataListener);
790        }
791
792        ListSelectionModel selectionModel = list.getSelectionModel();
793        if (selectionModel != null) {
794            selectionModel.addListSelectionListener(listSelectionListener);
795        }
796    }
797
798
799    /**
800     * Removes the listeners from the JList, its model, and its
801     * selectionModel.  All of the listener fields, are reset to
802     * null here.  This method is called at uninstallUI() time,
803     * it should be kept in sync with installListeners.
804     *
805     * @see #uninstallUI
806     * @see #installListeners
807     */
808    protected void uninstallListeners()
809    {
810        list.removeFocusListener(focusListener);
811        list.removeMouseListener(mouseInputListener);
812        list.removeMouseMotionListener(mouseInputListener);
813        list.removePropertyChangeListener(propertyChangeListener);
814        list.removeKeyListener(getHandler());
815
816        ListModel<Object> model = list.getModel();
817        if (model != null) {
818            model.removeListDataListener(listDataListener);
819        }
820
821        ListSelectionModel selectionModel = list.getSelectionModel();
822        if (selectionModel != null) {
823            selectionModel.removeListSelectionListener(listSelectionListener);
824        }
825
826        focusListener = null;
827        mouseInputListener  = null;
828        listSelectionListener = null;
829        listDataListener = null;
830        propertyChangeListener = null;
831        handler = null;
832    }
833
834
835    /**
836     * Initializes list properties such as font, foreground, and background,
837     * and adds the CellRendererPane. The font, foreground, and background
838     * properties are only set if their current value is either null
839     * or a UIResource, other properties are set if the current
840     * value is null.
841     *
842     * @see #uninstallDefaults
843     * @see #installUI
844     * @see CellRendererPane
845     */
846    protected void installDefaults()
847    {
848        list.setLayout(null);
849
850        LookAndFeel.installBorder(list, "List.border");
851
852        LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
853
854        LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
855
856        if (list.getCellRenderer() == null) {
857            @SuppressWarnings("unchecked")
858            ListCellRenderer<Object> tmp = (ListCellRenderer)(UIManager.get("List.cellRenderer"));
859            list.setCellRenderer(tmp);
860        }
861
862        Color sbg = list.getSelectionBackground();
863        if (sbg == null || sbg instanceof UIResource) {
864            list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
865        }
866
867        Color sfg = list.getSelectionForeground();
868        if (sfg == null || sfg instanceof UIResource) {
869            list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
870        }
871
872        Long l = (Long)UIManager.get("List.timeFactor");
873        timeFactor = (l!=null) ? l.longValue() : 1000L;
874
875        updateIsFileList();
876    }
877
878    private void updateIsFileList() {
879        boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
880        if (b != isFileList) {
881            isFileList = b;
882            Font oldFont = list.getFont();
883            if (oldFont == null || oldFont instanceof UIResource) {
884                Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
885                if (newFont != null && newFont != oldFont) {
886                    list.setFont(newFont);
887                }
888            }
889        }
890    }
891
892
893    /**
894     * Sets the list properties that have not been explicitly overridden to
895     * {@code null}. A property is considered overridden if its current value
896     * is not a {@code UIResource}.
897     *
898     * @see #installDefaults
899     * @see #uninstallUI
900     * @see CellRendererPane
901     */
902    protected void uninstallDefaults()
903    {
904        LookAndFeel.uninstallBorder(list);
905        if (list.getFont() instanceof UIResource) {
906            list.setFont(null);
907        }
908        if (list.getForeground() instanceof UIResource) {
909            list.setForeground(null);
910        }
911        if (list.getBackground() instanceof UIResource) {
912            list.setBackground(null);
913        }
914        if (list.getSelectionBackground() instanceof UIResource) {
915            list.setSelectionBackground(null);
916        }
917        if (list.getSelectionForeground() instanceof UIResource) {
918            list.setSelectionForeground(null);
919        }
920        if (list.getCellRenderer() instanceof UIResource) {
921            list.setCellRenderer(null);
922        }
923        if (list.getTransferHandler() instanceof UIResource) {
924            list.setTransferHandler(null);
925        }
926    }
927
928
929    /**
930     * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
931     * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
932     * in order.
933     *
934     * @see #installDefaults
935     * @see #installListeners
936     * @see #installKeyboardActions
937     */
938    public void installUI(JComponent c)
939    {
940        @SuppressWarnings("unchecked")
941        JList<Object> tmp = (JList)c;
942        list = tmp;
943
944        layoutOrientation = list.getLayoutOrientation();
945
946        rendererPane = new CellRendererPane();
947        list.add(rendererPane);
948
949        columnCount = 1;
950
951        updateLayoutStateNeeded = modelChanged;
952        isLeftToRight = list.getComponentOrientation().isLeftToRight();
953
954        installDefaults();
955        installListeners();
956        installKeyboardActions();
957    }
958
959
960    /**
961     * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
962     * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
963     * in order.  Sets this.list to null.
964     *
965     * @see #uninstallListeners
966     * @see #uninstallKeyboardActions
967     * @see #uninstallDefaults
968     */
969    public void uninstallUI(JComponent c)
970    {
971        uninstallListeners();
972        uninstallDefaults();
973        uninstallKeyboardActions();
974
975        cellWidth = cellHeight = -1;
976        cellHeights = null;
977
978        listWidth = listHeight = -1;
979
980        list.remove(rendererPane);
981        rendererPane = null;
982        list = null;
983    }
984
985
986    /**
987     * Returns a new instance of {@code BasicListUI}.
988     * {@code BasicListUI} delegates are allocated one per {@code JList}.
989     *
990     * @param list a component
991     * @return a new {@code ListUI} implementation for the Windows look and feel.
992     */
993    public static ComponentUI createUI(JComponent list) {
994        return new BasicListUI();
995    }
996
997
998    /**
999     * {@inheritDoc}
1000     * @throws NullPointerException {@inheritDoc}
1001     */
1002    public int locationToIndex(JList<?> list, Point location) {
1003        maybeUpdateLayoutState();
1004        return convertLocationToModel(location.x, location.y);
1005    }
1006
1007
1008    /**
1009     * {@inheritDoc}
1010     */
1011    public Point indexToLocation(JList<?> list, int index) {
1012        maybeUpdateLayoutState();
1013        Rectangle rect = getCellBounds(list, index, index);
1014
1015        if (rect != null) {
1016            return new Point(rect.x, rect.y);
1017        }
1018        return null;
1019    }
1020
1021
1022    /**
1023     * {@inheritDoc}
1024     */
1025    public Rectangle getCellBounds(JList<?> list, int index1, int index2) {
1026        maybeUpdateLayoutState();
1027
1028        int minIndex = Math.min(index1, index2);
1029        int maxIndex = Math.max(index1, index2);
1030
1031        if (minIndex >= list.getModel().getSize()) {
1032            return null;
1033        }
1034
1035        Rectangle minBounds = getCellBounds(list, minIndex);
1036
1037        if (minBounds == null) {
1038            return null;
1039        }
1040        if (minIndex == maxIndex) {
1041            return minBounds;
1042        }
1043        Rectangle maxBounds = getCellBounds(list, maxIndex);
1044
1045        if (maxBounds != null) {
1046            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1047                int minRow = convertModelToRow(minIndex);
1048                int maxRow = convertModelToRow(maxIndex);
1049
1050                if (minRow != maxRow) {
1051                    minBounds.x = 0;
1052                    minBounds.width = list.getWidth();
1053                }
1054            }
1055            else if (minBounds.x != maxBounds.x) {
1056                // Different columns
1057                minBounds.y = 0;
1058                minBounds.height = list.getHeight();
1059            }
1060            minBounds.add(maxBounds);
1061        }
1062        return minBounds;
1063    }
1064
1065    /**
1066     * Gets the bounds of the specified model index, returning the resulting
1067     * bounds, or null if <code>index</code> is not valid.
1068     */
1069    private Rectangle getCellBounds(JList<?> list, int index) {
1070        maybeUpdateLayoutState();
1071
1072        int row = convertModelToRow(index);
1073        int column = convertModelToColumn(index);
1074
1075        if (row == -1 || column == -1) {
1076            return null;
1077        }
1078
1079        Insets insets = list.getInsets();
1080        int x;
1081        int w = cellWidth;
1082        int y = insets.top;
1083        int h;
1084        switch (layoutOrientation) {
1085        case JList.VERTICAL_WRAP:
1086        case JList.HORIZONTAL_WRAP:
1087            if (isLeftToRight) {
1088                x = insets.left + column * cellWidth;
1089            } else {
1090                x = list.getWidth() - insets.right - (column+1) * cellWidth;
1091            }
1092            y += cellHeight * row;
1093            h = cellHeight;
1094            break;
1095        default:
1096            x = insets.left;
1097            if (cellHeights == null) {
1098                y += (cellHeight * row);
1099            }
1100            else if (row >= cellHeights.length) {
1101                y = 0;
1102            }
1103            else {
1104                for(int i = 0; i < row; i++) {
1105                    y += cellHeights[i];
1106                }
1107            }
1108            w = list.getWidth() - (insets.left + insets.right);
1109            h = getRowHeight(index);
1110            break;
1111        }
1112        return new Rectangle(x, y, w, h);
1113    }
1114
1115    /**
1116     * Returns the height of the specified row based on the current layout.
1117     *
1118     * @param row a row
1119     * @return the specified row height or -1 if row isn't valid
1120     * @see #convertYToRow
1121     * @see #convertRowToY
1122     * @see #updateLayoutState
1123     */
1124    protected int getRowHeight(int row)
1125    {
1126        return getHeight(0, row);
1127    }
1128
1129
1130    /**
1131     * Convert the {@code JList} relative coordinate to the row that contains it,
1132     * based on the current layout. If {@code y0} doesn't fall within any row,
1133     * return -1.
1134     *
1135     * @param y0 a relative Y coordinate
1136     * @return the row that contains y0, or -1
1137     * @see #getRowHeight
1138     * @see #updateLayoutState
1139     */
1140    protected int convertYToRow(int y0)
1141    {
1142        return convertLocationToRow(0, y0, false);
1143    }
1144
1145
1146    /**
1147     * Return the {@code JList} relative Y coordinate of the origin of the specified
1148     * row or -1 if row isn't valid.
1149     *
1150     * @param row a row
1151     * @return the Y coordinate of the origin of row, or -1
1152     * @see #getRowHeight
1153     * @see #updateLayoutState
1154     */
1155    protected int convertRowToY(int row)
1156    {
1157        if (row >= getRowCount(0) || row < 0) {
1158            return -1;
1159        }
1160        Rectangle bounds = getCellBounds(list, row, row);
1161        return bounds.y;
1162    }
1163
1164    /**
1165     * Returns the height of the cell at the passed in location.
1166     */
1167    private int getHeight(int column, int row) {
1168        if (column < 0 || column > columnCount || row < 0) {
1169            return -1;
1170        }
1171        if (layoutOrientation != JList.VERTICAL) {
1172            return cellHeight;
1173        }
1174        if (row >= list.getModel().getSize()) {
1175            return -1;
1176        }
1177        return (cellHeights == null) ? cellHeight :
1178                           ((row < cellHeights.length) ? cellHeights[row] : -1);
1179    }
1180
1181    /**
1182     * Returns the row at location x/y.
1183     *
1184     * @param closest If true and the location doesn't exactly match a
1185     *                particular location, this will return the closest row.
1186     */
1187    private int convertLocationToRow(int x, int y0, boolean closest) {
1188        int size = list.getModel().getSize();
1189
1190        if (size <= 0) {
1191            return -1;
1192        }
1193        Insets insets = list.getInsets();
1194        if (cellHeights == null) {
1195            int row = (cellHeight == 0) ? 0 :
1196                           ((y0 - insets.top) / cellHeight);
1197            if (closest) {
1198                if (row < 0) {
1199                    row = 0;
1200                }
1201                else if (row >= size) {
1202                    row = size - 1;
1203                }
1204            }
1205            return row;
1206        }
1207        else if (size > cellHeights.length) {
1208            return -1;
1209        }
1210        else {
1211            int y = insets.top;
1212            int row = 0;
1213
1214            if (closest && y0 < y) {
1215                return 0;
1216            }
1217            int i;
1218            for (i = 0; i < size; i++) {
1219                if ((y0 >= y) && (y0 < y + cellHeights[i])) {
1220                    return row;
1221                }
1222                y += cellHeights[i];
1223                row += 1;
1224            }
1225            return i - 1;
1226        }
1227    }
1228
1229    /**
1230     * Returns the closest row that starts at the specified y-location
1231     * in the passed in column.
1232     */
1233    private int convertLocationToRowInColumn(int y, int column) {
1234        int x = 0;
1235
1236        if (layoutOrientation != JList.VERTICAL) {
1237            if (isLeftToRight) {
1238                x = column * cellWidth;
1239            } else {
1240                x = list.getWidth() - (column+1)*cellWidth - list.getInsets().right;
1241            }
1242        }
1243        return convertLocationToRow(x, y, true);
1244    }
1245
1246    /**
1247     * Returns the closest location to the model index of the passed in
1248     * location.
1249     */
1250    private int convertLocationToModel(int x, int y) {
1251        int row = convertLocationToRow(x, y, true);
1252        int column = convertLocationToColumn(x, y);
1253
1254        if (row >= 0 && column >= 0) {
1255            return getModelIndex(column, row);
1256        }
1257        return -1;
1258    }
1259
1260    /**
1261     * Returns the number of rows in the given column.
1262     */
1263    private int getRowCount(int column) {
1264        if (column < 0 || column >= columnCount) {
1265            return -1;
1266        }
1267        if (layoutOrientation == JList.VERTICAL ||
1268                  (column == 0 && columnCount == 1)) {
1269            return list.getModel().getSize();
1270        }
1271        if (column >= columnCount) {
1272            return -1;
1273        }
1274        if (layoutOrientation == JList.VERTICAL_WRAP) {
1275            if (column < (columnCount - 1)) {
1276                return rowsPerColumn;
1277            }
1278            return list.getModel().getSize() - (columnCount - 1) *
1279                        rowsPerColumn;
1280        }
1281        // JList.HORIZONTAL_WRAP
1282        int diff = columnCount - (columnCount * rowsPerColumn -
1283                                  list.getModel().getSize());
1284
1285        if (column >= diff) {
1286            return Math.max(0, rowsPerColumn - 1);
1287        }
1288        return rowsPerColumn;
1289    }
1290
1291    /**
1292     * Returns the model index for the specified display location.
1293     * If <code>column</code>x<code>row</code> is beyond the length of the
1294     * model, this will return the model size - 1.
1295     */
1296    private int getModelIndex(int column, int row) {
1297        switch (layoutOrientation) {
1298        case JList.VERTICAL_WRAP:
1299            return Math.min(list.getModel().getSize() - 1, rowsPerColumn *
1300                            column + Math.min(row, rowsPerColumn-1));
1301        case JList.HORIZONTAL_WRAP:
1302            return Math.min(list.getModel().getSize() - 1, row * columnCount +
1303                            column);
1304        default:
1305            return row;
1306        }
1307    }
1308
1309    /**
1310     * Returns the closest column to the passed in location.
1311     */
1312    private int convertLocationToColumn(int x, int y) {
1313        if (cellWidth > 0) {
1314            if (layoutOrientation == JList.VERTICAL) {
1315                return 0;
1316            }
1317            Insets insets = list.getInsets();
1318            int col;
1319            if (isLeftToRight) {
1320                col = (x - insets.left) / cellWidth;
1321            } else {
1322                col = (list.getWidth() - x - insets.right - 1) / cellWidth;
1323            }
1324            if (col < 0) {
1325                return 0;
1326            }
1327            else if (col >= columnCount) {
1328                return columnCount - 1;
1329            }
1330            return col;
1331        }
1332        return 0;
1333    }
1334
1335    /**
1336     * Returns the row that the model index <code>index</code> will be
1337     * displayed in..
1338     */
1339    private int convertModelToRow(int index) {
1340        int size = list.getModel().getSize();
1341
1342        if ((index < 0) || (index >= size)) {
1343            return -1;
1344        }
1345
1346        if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
1347                                                   rowsPerColumn > 0) {
1348            if (layoutOrientation == JList.VERTICAL_WRAP) {
1349                return index % rowsPerColumn;
1350            }
1351            return index / columnCount;
1352        }
1353        return index;
1354    }
1355
1356    /**
1357     * Returns the column that the model index <code>index</code> will be
1358     * displayed in.
1359     */
1360    private int convertModelToColumn(int index) {
1361        int size = list.getModel().getSize();
1362
1363        if ((index < 0) || (index >= size)) {
1364            return -1;
1365        }
1366
1367        if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
1368                                                   columnCount > 1) {
1369            if (layoutOrientation == JList.VERTICAL_WRAP) {
1370                return index / rowsPerColumn;
1371            }
1372            return index % columnCount;
1373        }
1374        return 0;
1375    }
1376
1377    /**
1378     * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
1379     * updateLayoutStateNeeded.  This method should be called by methods
1380     * before doing any computation based on the geometry of the list.
1381     * For example it's the first call in paint() and getPreferredSize().
1382     *
1383     * @see #updateLayoutState
1384     */
1385    protected void maybeUpdateLayoutState()
1386    {
1387        if (updateLayoutStateNeeded != 0) {
1388            updateLayoutState();
1389            updateLayoutStateNeeded = 0;
1390        }
1391    }
1392
1393
1394    /**
1395     * Recompute the value of cellHeight or cellHeights based
1396     * and cellWidth, based on the current font and the current
1397     * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
1398     *
1399     * @see #maybeUpdateLayoutState
1400     */
1401    protected void updateLayoutState()
1402    {
1403        /* If both JList fixedCellWidth and fixedCellHeight have been
1404         * set, then initialize cellWidth and cellHeight, and set
1405         * cellHeights to null.
1406         */
1407
1408        int fixedCellHeight = list.getFixedCellHeight();
1409        int fixedCellWidth = list.getFixedCellWidth();
1410
1411        cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1412
1413        if (fixedCellHeight != -1) {
1414            cellHeight = fixedCellHeight;
1415            cellHeights = null;
1416        }
1417        else {
1418            cellHeight = -1;
1419            cellHeights = new int[list.getModel().getSize()];
1420        }
1421
1422        /* If either of  JList fixedCellWidth and fixedCellHeight haven't
1423         * been set, then initialize cellWidth and cellHeights by
1424         * scanning through the entire model.  Note: if the renderer is
1425         * null, we just set cellWidth and cellHeights[*] to zero,
1426         * if they're not set already.
1427         */
1428
1429        if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1430
1431            ListModel<Object> dataModel = list.getModel();
1432            int dataModelSize = dataModel.getSize();
1433            ListCellRenderer<Object> renderer = list.getCellRenderer();
1434
1435            if (renderer != null) {
1436                for(int index = 0; index < dataModelSize; index++) {
1437                    Object value = dataModel.getElementAt(index);
1438                    Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
1439                    rendererPane.add(c);
1440                    Dimension cellSize = c.getPreferredSize();
1441                    if (fixedCellWidth == -1) {
1442                        cellWidth = Math.max(cellSize.width, cellWidth);
1443                    }
1444                    if (fixedCellHeight == -1) {
1445                        cellHeights[index] = cellSize.height;
1446                    }
1447                }
1448            }
1449            else {
1450                if (cellWidth == -1) {
1451                    cellWidth = 0;
1452                }
1453                if (cellHeights == null) {
1454                    cellHeights = new int[dataModelSize];
1455                }
1456                for(int index = 0; index < dataModelSize; index++) {
1457                    cellHeights[index] = 0;
1458                }
1459            }
1460        }
1461
1462        columnCount = 1;
1463        if (layoutOrientation != JList.VERTICAL) {
1464            updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
1465        }
1466    }
1467
1468    /**
1469     * Invoked when the list is layed out horizontally to determine how
1470     * many columns to create.
1471     * <p>
1472     * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
1473     * <code>preferredHeight</code> and potentially <code>cellHeight</code>
1474     * instance variables.
1475     */
1476    private void updateHorizontalLayoutState(int fixedCellWidth,
1477                                             int fixedCellHeight) {
1478        int visRows = list.getVisibleRowCount();
1479        int dataModelSize = list.getModel().getSize();
1480        Insets insets = list.getInsets();
1481
1482        listHeight = list.getHeight();
1483        listWidth = list.getWidth();
1484
1485        if (dataModelSize == 0) {
1486            rowsPerColumn = columnCount = 0;
1487            preferredHeight = insets.top + insets.bottom;
1488            return;
1489        }
1490
1491        int height;
1492
1493        if (fixedCellHeight != -1) {
1494            height = fixedCellHeight;
1495        }
1496        else {
1497            // Determine the max of the renderer heights.
1498            int maxHeight = 0;
1499            if (cellHeights.length > 0) {
1500                maxHeight = cellHeights[cellHeights.length - 1];
1501                for (int counter = cellHeights.length - 2;
1502                     counter >= 0; counter--) {
1503                    maxHeight = Math.max(maxHeight, cellHeights[counter]);
1504                }
1505            }
1506            height = cellHeight = maxHeight;
1507            cellHeights = null;
1508        }
1509        // The number of rows is either determined by the visible row
1510        // count, or by the height of the list.
1511        rowsPerColumn = dataModelSize;
1512        if (visRows > 0) {
1513            rowsPerColumn = visRows;
1514            columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1515            if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1516                dataModelSize % rowsPerColumn != 0) {
1517                columnCount++;
1518            }
1519            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1520                // Because HORIZONTAL_WRAP flows differently, the
1521                // rowsPerColumn needs to be adjusted.
1522                rowsPerColumn = (dataModelSize / columnCount);
1523                if (dataModelSize % columnCount > 0) {
1524                    rowsPerColumn++;
1525                }
1526            }
1527        }
1528        else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
1529            rowsPerColumn = Math.max(1, (listHeight - insets.top -
1530                                         insets.bottom) / height);
1531            columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1532            if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1533                dataModelSize % rowsPerColumn != 0) {
1534                columnCount++;
1535            }
1536        }
1537        else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
1538                 listWidth > 0) {
1539            columnCount = Math.max(1, (listWidth - insets.left -
1540                                       insets.right) / cellWidth);
1541            rowsPerColumn = dataModelSize / columnCount;
1542            if (dataModelSize % columnCount > 0) {
1543                rowsPerColumn++;
1544            }
1545        }
1546        preferredHeight = rowsPerColumn * cellHeight + insets.top +
1547                              insets.bottom;
1548    }
1549
1550    private Handler getHandler() {
1551        if (handler == null) {
1552            handler = new Handler();
1553        }
1554        return handler;
1555    }
1556
1557    /**
1558     * Mouse input, and focus handling for JList.  An instance of this
1559     * class is added to the appropriate java.awt.Component lists
1560     * at installUI() time.  Note keyboard input is handled with JComponent
1561     * KeyboardActions, see installKeyboardActions().
1562     * <p>
1563     * <strong>Warning:</strong>
1564     * Serialized objects of this class will not be compatible with
1565     * future Swing releases. The current serialization support is
1566     * appropriate for short term storage or RMI between applications running
1567     * the same version of Swing.  As of 1.4, support for long term storage
1568     * of all JavaBeans&trade;
1569     * has been added to the <code>java.beans</code> package.
1570     * Please see {@link java.beans.XMLEncoder}.
1571     *
1572     * @see #createMouseInputListener
1573     * @see #installKeyboardActions
1574     * @see #installUI
1575     */
1576    @SuppressWarnings("serial") // Same-version serialization only
1577    public class MouseInputHandler implements MouseInputListener
1578    {
1579        public void mouseClicked(MouseEvent e) {
1580            getHandler().mouseClicked(e);
1581        }
1582
1583        public void mouseEntered(MouseEvent e) {
1584            getHandler().mouseEntered(e);
1585        }
1586
1587        public void mouseExited(MouseEvent e) {
1588            getHandler().mouseExited(e);
1589        }
1590
1591        public void mousePressed(MouseEvent e) {
1592            getHandler().mousePressed(e);
1593        }
1594
1595        public void mouseDragged(MouseEvent e) {
1596            getHandler().mouseDragged(e);
1597        }
1598
1599        public void mouseMoved(MouseEvent e) {
1600            getHandler().mouseMoved(e);
1601        }
1602
1603        public void mouseReleased(MouseEvent e) {
1604            getHandler().mouseReleased(e);
1605        }
1606    }
1607
1608
1609    /**
1610     * Creates a delegate that implements {@code MouseInputListener}.
1611     * The delegate is added to the corresponding {@code java.awt.Component} listener
1612     * lists at {@code installUI()} time. Subclasses can override this method to return
1613     * a custom {@code MouseInputListener}, e.g.
1614     * <pre>
1615     * class MyListUI extends BasicListUI {
1616     *    protected MouseInputListener <b>createMouseInputListener</b>() {
1617     *        return new MyMouseInputHandler();
1618     *    }
1619     *    public class MyMouseInputHandler extends MouseInputHandler {
1620     *        public void mouseMoved(MouseEvent e) {
1621     *            // do some extra work when the mouse moves
1622     *            super.mouseMoved(e);
1623     *        }
1624     *    }
1625     * }
1626     * </pre>
1627     *
1628     * @return an instance of {@code MouseInputListener}
1629     * @see MouseInputHandler
1630     * @see #installUI
1631     */
1632    protected MouseInputListener createMouseInputListener() {
1633        return getHandler();
1634    }
1635
1636    /**
1637     * This class should be treated as a &quot;protected&quot; inner class.
1638     * Instantiate it only within subclasses of {@code BasicListUI}.
1639     */
1640    public class FocusHandler implements FocusListener
1641    {
1642        /**
1643         * Repaints focused cells.
1644         */
1645        protected void repaintCellFocus()
1646        {
1647            getHandler().repaintCellFocus();
1648        }
1649
1650        /* The focusGained() focusLost() methods run when the JList
1651         * focus changes.
1652         */
1653
1654        public void focusGained(FocusEvent e) {
1655            getHandler().focusGained(e);
1656        }
1657
1658        public void focusLost(FocusEvent e) {
1659            getHandler().focusLost(e);
1660        }
1661    }
1662
1663    /**
1664     * Returns an instance of {@code FocusListener}.
1665     *
1666     * @return an instance of {@code FocusListener}
1667     */
1668    protected FocusListener createFocusListener() {
1669        return getHandler();
1670    }
1671
1672    /**
1673     * The ListSelectionListener that's added to the JLists selection
1674     * model at installUI time, and whenever the JList.selectionModel property
1675     * changes.  When the selection changes we repaint the affected rows.
1676     * <p>
1677     * <strong>Warning:</strong>
1678     * Serialized objects of this class will not be compatible with
1679     * future Swing releases. The current serialization support is
1680     * appropriate for short term storage or RMI between applications running
1681     * the same version of Swing.  As of 1.4, support for long term storage
1682     * of all JavaBeans&trade;
1683     * has been added to the <code>java.beans</code> package.
1684     * Please see {@link java.beans.XMLEncoder}.
1685     *
1686     * @see #createListSelectionListener
1687     * @see #getCellBounds
1688     * @see #installUI
1689     */
1690    @SuppressWarnings("serial") // Same-version serialization only
1691    public class ListSelectionHandler implements ListSelectionListener
1692    {
1693        public void valueChanged(ListSelectionEvent e)
1694        {
1695            getHandler().valueChanged(e);
1696        }
1697    }
1698
1699
1700    /**
1701     * Creates an instance of {@code ListSelectionHandler} that's added to
1702     * the {@code JLists} by selectionModel as needed.  Subclasses can override
1703     * this method to return a custom {@code ListSelectionListener}, e.g.
1704     * <pre>
1705     * class MyListUI extends BasicListUI {
1706     *    protected ListSelectionListener <b>createListSelectionListener</b>() {
1707     *        return new MySelectionListener();
1708     *    }
1709     *    public class MySelectionListener extends ListSelectionHandler {
1710     *        public void valueChanged(ListSelectionEvent e) {
1711     *            // do some extra work when the selection changes
1712     *            super.valueChange(e);
1713     *        }
1714     *    }
1715     * }
1716     * </pre>
1717     *
1718     * @return an instance of {@code ListSelectionHandler}
1719     * @see ListSelectionHandler
1720     * @see #installUI
1721     */
1722    protected ListSelectionListener createListSelectionListener() {
1723        return getHandler();
1724    }
1725
1726
1727    private void redrawList() {
1728        list.revalidate();
1729        list.repaint();
1730    }
1731
1732
1733    /**
1734     * The {@code ListDataListener} that's added to the {@code JLists} model at
1735     * {@code installUI time}, and whenever the JList.model property changes.
1736     * <p>
1737     * <strong>Warning:</strong>
1738     * Serialized objects of this class will not be compatible with
1739     * future Swing releases. The current serialization support is
1740     * appropriate for short term storage or RMI between applications running
1741     * the same version of Swing.  As of 1.4, support for long term storage
1742     * of all JavaBeans&trade;
1743     * has been added to the <code>java.beans</code> package.
1744     * Please see {@link java.beans.XMLEncoder}.
1745     *
1746     * @see JList#getModel
1747     * @see #maybeUpdateLayoutState
1748     * @see #createListDataListener
1749     * @see #installUI
1750     */
1751    @SuppressWarnings("serial") // Same-version serialization only
1752    public class ListDataHandler implements ListDataListener
1753    {
1754        public void intervalAdded(ListDataEvent e) {
1755            getHandler().intervalAdded(e);
1756        }
1757
1758
1759        public void intervalRemoved(ListDataEvent e)
1760        {
1761            getHandler().intervalRemoved(e);
1762        }
1763
1764
1765        public void contentsChanged(ListDataEvent e) {
1766            getHandler().contentsChanged(e);
1767        }
1768    }
1769
1770
1771    /**
1772     * Creates an instance of {@code ListDataListener} that's added to
1773     * the {@code JLists} by model as needed. Subclasses can override
1774     * this method to return a custom {@code ListDataListener}, e.g.
1775     * <pre>
1776     * class MyListUI extends BasicListUI {
1777     *    protected ListDataListener <b>createListDataListener</b>() {
1778     *        return new MyListDataListener();
1779     *    }
1780     *    public class MyListDataListener extends ListDataHandler {
1781     *        public void contentsChanged(ListDataEvent e) {
1782     *            // do some extra work when the models contents change
1783     *            super.contentsChange(e);
1784     *        }
1785     *    }
1786     * }
1787     * </pre>
1788     *
1789     * @return an instance of {@code ListDataListener}
1790     * @see ListDataListener
1791     * @see JList#getModel
1792     * @see #installUI
1793     */
1794    protected ListDataListener createListDataListener() {
1795        return getHandler();
1796    }
1797
1798
1799    /**
1800     * The PropertyChangeListener that's added to the JList at
1801     * installUI time.  When the value of a JList property that
1802     * affects layout changes, we set a bit in updateLayoutStateNeeded.
1803     * If the JLists model changes we additionally remove our listeners
1804     * from the old model.  Likewise for the JList selectionModel.
1805     * <p>
1806     * <strong>Warning:</strong>
1807     * Serialized objects of this class will not be compatible with
1808     * future Swing releases. The current serialization support is
1809     * appropriate for short term storage or RMI between applications running
1810     * the same version of Swing.  As of 1.4, support for long term storage
1811     * of all JavaBeans&trade;
1812     * has been added to the <code>java.beans</code> package.
1813     * Please see {@link java.beans.XMLEncoder}.
1814     *
1815     * @see #maybeUpdateLayoutState
1816     * @see #createPropertyChangeListener
1817     * @see #installUI
1818     */
1819    @SuppressWarnings("serial") // Same-version serialization only
1820    public class PropertyChangeHandler implements PropertyChangeListener
1821    {
1822        public void propertyChange(PropertyChangeEvent e)
1823        {
1824            getHandler().propertyChange(e);
1825        }
1826    }
1827
1828
1829    /**
1830     * Creates an instance of {@code PropertyChangeHandler} that's added to
1831     * the {@code JList} by {@code installUI()}. Subclasses can override this method
1832     * to return a custom {@code PropertyChangeListener}, e.g.
1833     * <pre>
1834     * class MyListUI extends BasicListUI {
1835     *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
1836     *        return new MyPropertyChangeListener();
1837     *    }
1838     *    public class MyPropertyChangeListener extends PropertyChangeHandler {
1839     *        public void propertyChange(PropertyChangeEvent e) {
1840     *            if (e.getPropertyName().equals("model")) {
1841     *                // do some extra work when the model changes
1842     *            }
1843     *            super.propertyChange(e);
1844     *        }
1845     *    }
1846     * }
1847     * </pre>
1848     *
1849     * @return an instance of {@code PropertyChangeHandler}
1850     * @see PropertyChangeListener
1851     * @see #installUI
1852     */
1853    protected PropertyChangeListener createPropertyChangeListener() {
1854        return getHandler();
1855    }
1856
1857    /** Used by IncrementLeadSelectionAction. Indicates the action should
1858     * change the lead, and not select it. */
1859    private static final int CHANGE_LEAD = 0;
1860    /** Used by IncrementLeadSelectionAction. Indicates the action should
1861     * change the selection and lead. */
1862    private static final int CHANGE_SELECTION = 1;
1863    /** Used by IncrementLeadSelectionAction. Indicates the action should
1864     * extend the selection from the anchor to the next index. */
1865    private static final int EXTEND_SELECTION = 2;
1866
1867
1868    private static class Actions extends UIAction {
1869        private static final String SELECT_PREVIOUS_COLUMN =
1870                                    "selectPreviousColumn";
1871        private static final String SELECT_PREVIOUS_COLUMN_EXTEND =
1872                                    "selectPreviousColumnExtendSelection";
1873        private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD =
1874                                    "selectPreviousColumnChangeLead";
1875        private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
1876        private static final String SELECT_NEXT_COLUMN_EXTEND =
1877                                    "selectNextColumnExtendSelection";
1878        private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD =
1879                                    "selectNextColumnChangeLead";
1880        private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
1881        private static final String SELECT_PREVIOUS_ROW_EXTEND =
1882                                     "selectPreviousRowExtendSelection";
1883        private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD =
1884                                     "selectPreviousRowChangeLead";
1885        private static final String SELECT_NEXT_ROW = "selectNextRow";
1886        private static final String SELECT_NEXT_ROW_EXTEND =
1887                                     "selectNextRowExtendSelection";
1888        private static final String SELECT_NEXT_ROW_CHANGE_LEAD =
1889                                     "selectNextRowChangeLead";
1890        private static final String SELECT_FIRST_ROW = "selectFirstRow";
1891        private static final String SELECT_FIRST_ROW_EXTEND =
1892                                     "selectFirstRowExtendSelection";
1893        private static final String SELECT_FIRST_ROW_CHANGE_LEAD =
1894                                     "selectFirstRowChangeLead";
1895        private static final String SELECT_LAST_ROW = "selectLastRow";
1896        private static final String SELECT_LAST_ROW_EXTEND =
1897                                     "selectLastRowExtendSelection";
1898        private static final String SELECT_LAST_ROW_CHANGE_LEAD =
1899                                     "selectLastRowChangeLead";
1900        private static final String SCROLL_UP = "scrollUp";
1901        private static final String SCROLL_UP_EXTEND =
1902                                     "scrollUpExtendSelection";
1903        private static final String SCROLL_UP_CHANGE_LEAD =
1904                                     "scrollUpChangeLead";
1905        private static final String SCROLL_DOWN = "scrollDown";
1906        private static final String SCROLL_DOWN_EXTEND =
1907                                     "scrollDownExtendSelection";
1908        private static final String SCROLL_DOWN_CHANGE_LEAD =
1909                                     "scrollDownChangeLead";
1910        private static final String SELECT_ALL = "selectAll";
1911        private static final String CLEAR_SELECTION = "clearSelection";
1912
1913        // add the lead item to the selection without changing lead or anchor
1914        private static final String ADD_TO_SELECTION = "addToSelection";
1915
1916        // toggle the selected state of the lead item and move the anchor to it
1917        private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
1918
1919        // extend the selection to the lead item
1920        private static final String EXTEND_TO = "extendTo";
1921
1922        // move the anchor to the lead and ensure only that item is selected
1923        private static final String MOVE_SELECTION_TO = "moveSelectionTo";
1924
1925        Actions(String name) {
1926            super(name);
1927        }
1928        public void actionPerformed(ActionEvent e) {
1929            String name = getName();
1930            @SuppressWarnings("unchecked")
1931            JList<Object> list = (JList)e.getSource();
1932            BasicListUI ui = (BasicListUI)BasicLookAndFeel.getUIOfType(
1933                     list.getUI(), BasicListUI.class);
1934
1935            if (name == SELECT_PREVIOUS_COLUMN) {
1936                changeSelection(list, CHANGE_SELECTION,
1937                                getNextColumnIndex(list, ui, -1), -1);
1938            }
1939            else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
1940                changeSelection(list, EXTEND_SELECTION,
1941                                getNextColumnIndex(list, ui, -1), -1);
1942            }
1943            else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
1944                changeSelection(list, CHANGE_LEAD,
1945                                getNextColumnIndex(list, ui, -1), -1);
1946            }
1947            else if (name == SELECT_NEXT_COLUMN) {
1948                changeSelection(list, CHANGE_SELECTION,
1949                                getNextColumnIndex(list, ui, 1), 1);
1950            }
1951            else if (name == SELECT_NEXT_COLUMN_EXTEND) {
1952                changeSelection(list, EXTEND_SELECTION,
1953                                getNextColumnIndex(list, ui, 1), 1);
1954            }
1955            else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
1956                changeSelection(list, CHANGE_LEAD,
1957                                getNextColumnIndex(list, ui, 1), 1);
1958            }
1959            else if (name == SELECT_PREVIOUS_ROW) {
1960                changeSelection(list, CHANGE_SELECTION,
1961                                getNextIndex(list, ui, -1), -1);
1962            }
1963            else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
1964                changeSelection(list, EXTEND_SELECTION,
1965                                getNextIndex(list, ui, -1), -1);
1966            }
1967            else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
1968                changeSelection(list, CHANGE_LEAD,
1969                                getNextIndex(list, ui, -1), -1);
1970            }
1971            else if (name == SELECT_NEXT_ROW) {
1972                changeSelection(list, CHANGE_SELECTION,
1973                                getNextIndex(list, ui, 1), 1);
1974            }
1975            else if (name == SELECT_NEXT_ROW_EXTEND) {
1976                changeSelection(list, EXTEND_SELECTION,
1977                                getNextIndex(list, ui, 1), 1);
1978            }
1979            else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
1980                changeSelection(list, CHANGE_LEAD,
1981                                getNextIndex(list, ui, 1), 1);
1982            }
1983            else if (name == SELECT_FIRST_ROW) {
1984                changeSelection(list, CHANGE_SELECTION, 0, -1);
1985            }
1986            else if (name == SELECT_FIRST_ROW_EXTEND) {
1987                changeSelection(list, EXTEND_SELECTION, 0, -1);
1988            }
1989            else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
1990                changeSelection(list, CHANGE_LEAD, 0, -1);
1991            }
1992            else if (name == SELECT_LAST_ROW) {
1993                changeSelection(list, CHANGE_SELECTION,
1994                                list.getModel().getSize() - 1, 1);
1995            }
1996            else if (name == SELECT_LAST_ROW_EXTEND) {
1997                changeSelection(list, EXTEND_SELECTION,
1998                                list.getModel().getSize() - 1, 1);
1999            }
2000            else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
2001                changeSelection(list, CHANGE_LEAD,
2002                                list.getModel().getSize() - 1, 1);
2003            }
2004            else if (name == SCROLL_UP) {
2005                changeSelection(list, CHANGE_SELECTION,
2006                                getNextPageIndex(list, -1), -1);
2007            }
2008            else if (name == SCROLL_UP_EXTEND) {
2009                changeSelection(list, EXTEND_SELECTION,
2010                                getNextPageIndex(list, -1), -1);
2011            }
2012            else if (name == SCROLL_UP_CHANGE_LEAD) {
2013                changeSelection(list, CHANGE_LEAD,
2014                                getNextPageIndex(list, -1), -1);
2015            }
2016            else if (name == SCROLL_DOWN) {
2017                changeSelection(list, CHANGE_SELECTION,
2018                                getNextPageIndex(list, 1), 1);
2019            }
2020            else if (name == SCROLL_DOWN_EXTEND) {
2021                changeSelection(list, EXTEND_SELECTION,
2022                                getNextPageIndex(list, 1), 1);
2023            }
2024            else if (name == SCROLL_DOWN_CHANGE_LEAD) {
2025                changeSelection(list, CHANGE_LEAD,
2026                                getNextPageIndex(list, 1), 1);
2027            }
2028            else if (name == SELECT_ALL) {
2029                selectAll(list);
2030            }
2031            else if (name == CLEAR_SELECTION) {
2032                clearSelection(list);
2033            }
2034            else if (name == ADD_TO_SELECTION) {
2035                int index = adjustIndex(
2036                    list.getSelectionModel().getLeadSelectionIndex(), list);
2037
2038                if (!list.isSelectedIndex(index)) {
2039                    int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
2040                    list.setValueIsAdjusting(true);
2041                    list.addSelectionInterval(index, index);
2042                    list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
2043                    list.setValueIsAdjusting(false);
2044                }
2045            }
2046            else if (name == TOGGLE_AND_ANCHOR) {
2047                int index = adjustIndex(
2048                    list.getSelectionModel().getLeadSelectionIndex(), list);
2049
2050                if (list.isSelectedIndex(index)) {
2051                    list.removeSelectionInterval(index, index);
2052                } else {
2053                    list.addSelectionInterval(index, index);
2054                }
2055            }
2056            else if (name == EXTEND_TO) {
2057                changeSelection(
2058                    list, EXTEND_SELECTION,
2059                    adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
2060                    0);
2061            }
2062            else if (name == MOVE_SELECTION_TO) {
2063                changeSelection(
2064                    list, CHANGE_SELECTION,
2065                    adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
2066                    0);
2067            }
2068        }
2069
2070        @Override
2071        public boolean accept(Object c) {
2072            Object name = getName();
2073            if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
2074                    name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
2075                    name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
2076                    name == SELECT_NEXT_ROW_CHANGE_LEAD ||
2077                    name == SELECT_FIRST_ROW_CHANGE_LEAD ||
2078                    name == SELECT_LAST_ROW_CHANGE_LEAD ||
2079                    name == SCROLL_UP_CHANGE_LEAD ||
2080                    name == SCROLL_DOWN_CHANGE_LEAD) {
2081
2082                // discontinuous selection actions are only enabled for
2083                // DefaultListSelectionModel
2084                return c != null && ((JList)c).getSelectionModel()
2085                                        instanceof DefaultListSelectionModel;
2086            }
2087
2088            return true;
2089        }
2090
2091        private void clearSelection(JList<?> list) {
2092            list.clearSelection();
2093        }
2094
2095        private void selectAll(JList<?> list) {
2096            int size = list.getModel().getSize();
2097            if (size > 0) {
2098                ListSelectionModel lsm = list.getSelectionModel();
2099                int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2100
2101                if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
2102                    if (lead == -1) {
2103                        int min = adjustIndex(list.getMinSelectionIndex(), list);
2104                        lead = (min == -1 ? 0 : min);
2105                    }
2106
2107                    list.setSelectionInterval(lead, lead);
2108                    list.ensureIndexIsVisible(lead);
2109                } else {
2110                    list.setValueIsAdjusting(true);
2111
2112                    int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2113
2114                    list.setSelectionInterval(0, size - 1);
2115
2116                    // this is done to restore the anchor and lead
2117                    SwingUtilities2.setLeadAnchorWithoutSelection(lsm, anchor, lead);
2118
2119                    list.setValueIsAdjusting(false);
2120                }
2121            }
2122        }
2123
2124        private int getNextPageIndex(JList<?> list, int direction) {
2125            if (list.getModel().getSize() == 0) {
2126                return -1;
2127            }
2128
2129            int index = -1;
2130            Rectangle visRect = list.getVisibleRect();
2131            ListSelectionModel lsm = list.getSelectionModel();
2132            int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2133            Rectangle leadRect =
2134                (lead==-1) ? new Rectangle() : list.getCellBounds(lead, lead);
2135
2136            if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2137                list.getVisibleRowCount() <= 0) {
2138                if (!list.getComponentOrientation().isLeftToRight()) {
2139                    direction = -direction;
2140                }
2141                // apply for horizontal scrolling: the step for next
2142                // page index is number of visible columns
2143                if (direction < 0) {
2144                    // left
2145                    visRect.x = leadRect.x + leadRect.width - visRect.width;
2146                    Point p = new Point(visRect.x - 1, leadRect.y);
2147                    index = list.locationToIndex(p);
2148                    Rectangle cellBounds = list.getCellBounds(index, index);
2149                    if (visRect.intersects(cellBounds)) {
2150                        p.x = cellBounds.x - 1;
2151                        index = list.locationToIndex(p);
2152                        cellBounds = list.getCellBounds(index, index);
2153                    }
2154                    // this is necessary for right-to-left orientation only
2155                    if (cellBounds.y != leadRect.y) {
2156                        p.x = cellBounds.x + cellBounds.width;
2157                        index = list.locationToIndex(p);
2158                    }
2159                }
2160                else {
2161                    // right
2162                    visRect.x = leadRect.x;
2163                    Point p = new Point(visRect.x + visRect.width, leadRect.y);
2164                    index = list.locationToIndex(p);
2165                    Rectangle cellBounds = list.getCellBounds(index, index);
2166                    if (visRect.intersects(cellBounds)) {
2167                        p.x = cellBounds.x + cellBounds.width;
2168                        index = list.locationToIndex(p);
2169                        cellBounds = list.getCellBounds(index, index);
2170                    }
2171                    if (cellBounds.y != leadRect.y) {
2172                        p.x = cellBounds.x - 1;
2173                        index = list.locationToIndex(p);
2174                    }
2175                }
2176            }
2177            else {
2178                if (direction < 0) {
2179                    // up
2180                    // go to the first visible cell
2181                    Point p = new Point(leadRect.x, visRect.y);
2182                    index = list.locationToIndex(p);
2183                    if (lead <= index) {
2184                        // if lead is the first visible cell (or above it)
2185                        // adjust the visible rect up
2186                        visRect.y = leadRect.y + leadRect.height - visRect.height;
2187                        p.y = visRect.y;
2188                        index = list.locationToIndex(p);
2189                        Rectangle cellBounds = list.getCellBounds(index, index);
2190                        // go one cell down if first visible cell doesn't fit
2191                        // into adjasted visible rectangle
2192                        if (cellBounds.y < visRect.y) {
2193                            p.y = cellBounds.y + cellBounds.height;
2194                            index = list.locationToIndex(p);
2195                            cellBounds = list.getCellBounds(index, index);
2196                        }
2197                        // if index isn't less then lead
2198                        // try to go to cell previous to lead
2199                        if (cellBounds.y >= leadRect.y) {
2200                            p.y = leadRect.y - 1;
2201                            index = list.locationToIndex(p);
2202                        }
2203                    }
2204                }
2205                else {
2206                    // down
2207                    // go to the last completely visible cell
2208                    Point p = new Point(leadRect.x,
2209                                        visRect.y + visRect.height - 1);
2210                    index = list.locationToIndex(p);
2211                    Rectangle cellBounds = list.getCellBounds(index, index);
2212                    // go up one cell if last visible cell doesn't fit
2213                    // into visible rectangle
2214                    if (cellBounds.y + cellBounds.height >
2215                        visRect.y + visRect.height) {
2216                        p.y = cellBounds.y - 1;
2217                        index = list.locationToIndex(p);
2218                        cellBounds = list.getCellBounds(index, index);
2219                        index = Math.max(index, lead);
2220                    }
2221
2222                    if (lead >= index) {
2223                        // if lead is the last completely visible index
2224                        // (or below it) adjust the visible rect down
2225                        visRect.y = leadRect.y;
2226                        p.y = visRect.y + visRect.height - 1;
2227                        index = list.locationToIndex(p);
2228                        cellBounds = list.getCellBounds(index, index);
2229                        // go one cell up if last visible cell doesn't fit
2230                        // into adjasted visible rectangle
2231                        if (cellBounds.y + cellBounds.height >
2232                            visRect.y + visRect.height) {
2233                            p.y = cellBounds.y - 1;
2234                            index = list.locationToIndex(p);
2235                            cellBounds = list.getCellBounds(index, index);
2236                        }
2237                        // if index isn't greater then lead
2238                        // try to go to cell next after lead
2239                        if (cellBounds.y <= leadRect.y) {
2240                            p.y = leadRect.y + leadRect.height;
2241                            index = list.locationToIndex(p);
2242                        }
2243                    }
2244                }
2245            }
2246            return index;
2247        }
2248
2249        private void changeSelection(JList<?> list, int type,
2250                                     int index, int direction) {
2251            if (index >= 0 && index < list.getModel().getSize()) {
2252                ListSelectionModel lsm = list.getSelectionModel();
2253
2254                // CHANGE_LEAD is only valid with multiple interval selection
2255                if (type == CHANGE_LEAD &&
2256                        list.getSelectionMode()
2257                            != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
2258
2259                    type = CHANGE_SELECTION;
2260                }
2261
2262                // IMPORTANT - This needs to happen before the index is changed.
2263                // This is because JFileChooser, which uses JList, also scrolls
2264                // the selected item into view. If that happens first, then
2265                // this method becomes a no-op.
2266                adjustScrollPositionIfNecessary(list, index, direction);
2267
2268                if (type == EXTEND_SELECTION) {
2269                    int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2270                    if (anchor == -1) {
2271                        anchor = 0;
2272                    }
2273
2274                    list.setSelectionInterval(anchor, index);
2275                }
2276                else if (type == CHANGE_SELECTION) {
2277                    list.setSelectedIndex(index);
2278                }
2279                else {
2280                    // casting should be safe since the action is only enabled
2281                    // for DefaultListSelectionModel
2282                    ((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
2283                }
2284            }
2285        }
2286
2287        /**
2288         * When scroll down makes selected index the last completely visible
2289         * index. When scroll up makes selected index the first visible index.
2290         * Adjust visible rectangle respect to list's component orientation.
2291         */
2292        private void adjustScrollPositionIfNecessary(JList<?> list, int index,
2293                                                     int direction) {
2294            if (direction == 0) {
2295                return;
2296            }
2297            Rectangle cellBounds = list.getCellBounds(index, index);
2298            Rectangle visRect = list.getVisibleRect();
2299            if (cellBounds != null && !visRect.contains(cellBounds)) {
2300                if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2301                    list.getVisibleRowCount() <= 0) {
2302                    // horizontal
2303                    if (list.getComponentOrientation().isLeftToRight()) {
2304                        if (direction > 0) {
2305                            // right for left-to-right
2306                            int x =Math.max(0,
2307                                cellBounds.x + cellBounds.width - visRect.width);
2308                            int startIndex =
2309                                list.locationToIndex(new Point(x, cellBounds.y));
2310                            Rectangle startRect = list.getCellBounds(startIndex,
2311                                                                     startIndex);
2312                            if (startRect.x < x && startRect.x < cellBounds.x) {
2313                                startRect.x += startRect.width;
2314                                startIndex =
2315                                    list.locationToIndex(startRect.getLocation());
2316                                startRect = list.getCellBounds(startIndex,
2317                                                               startIndex);
2318                            }
2319                            cellBounds = startRect;
2320                        }
2321                        cellBounds.width = visRect.width;
2322                    }
2323                    else {
2324                        if (direction > 0) {
2325                            // left for right-to-left
2326                            int x = cellBounds.x + visRect.width;
2327                            int rightIndex =
2328                                list.locationToIndex(new Point(x, cellBounds.y));
2329                            Rectangle rightRect = list.getCellBounds(rightIndex,
2330                                                                     rightIndex);
2331                            if (rightRect.x + rightRect.width > x &&
2332                                rightRect.x > cellBounds.x) {
2333                                rightRect.width = 0;
2334                            }
2335                            cellBounds.x = Math.max(0,
2336                                rightRect.x + rightRect.width - visRect.width);
2337                            cellBounds.width = visRect.width;
2338                        }
2339                        else {
2340                            cellBounds.x += Math.max(0,
2341                                cellBounds.width - visRect.width);
2342                            // adjust width to fit into visible rectangle
2343                            cellBounds.width = Math.min(cellBounds.width,
2344                                                        visRect.width);
2345                        }
2346                    }
2347                }
2348                else {
2349                    // vertical
2350                    if (direction > 0 &&
2351                            (cellBounds.y < visRect.y ||
2352                                    cellBounds.y + cellBounds.height
2353                                            > visRect.y + visRect.height)) {
2354                        //down
2355                        int y = Math.max(0,
2356                            cellBounds.y + cellBounds.height - visRect.height);
2357                        int startIndex =
2358                            list.locationToIndex(new Point(cellBounds.x, y));
2359                        Rectangle startRect = list.getCellBounds(startIndex,
2360                                                                 startIndex);
2361                        if (startRect.y < y && startRect.y < cellBounds.y) {
2362                            startRect.y += startRect.height;
2363                            startIndex =
2364                                list.locationToIndex(startRect.getLocation());
2365                            startRect =
2366                                list.getCellBounds(startIndex, startIndex);
2367                        }
2368                        cellBounds = startRect;
2369                        cellBounds.height = visRect.height;
2370                    }
2371                    else {
2372                        // adjust height to fit into visible rectangle
2373                        cellBounds.height = Math.min(cellBounds.height, visRect.height);
2374                    }
2375                }
2376                list.scrollRectToVisible(cellBounds);
2377            }
2378        }
2379
2380        private int getNextColumnIndex(JList<?> list, BasicListUI ui,
2381                                       int amount) {
2382            if (list.getLayoutOrientation() != JList.VERTICAL) {
2383                int index = adjustIndex(list.getLeadSelectionIndex(), list);
2384                int size = list.getModel().getSize();
2385
2386                if (index == -1) {
2387                    return 0;
2388                } else if (size == 1) {
2389                    // there's only one item so we should select it
2390                    return 0;
2391                } else if (ui == null || ui.columnCount <= 1) {
2392                    return -1;
2393                }
2394
2395                int column = ui.convertModelToColumn(index);
2396                int row = ui.convertModelToRow(index);
2397
2398                column += amount;
2399                if (column >= ui.columnCount || column < 0) {
2400                    // No wrapping.
2401                    return -1;
2402                }
2403                int maxRowCount = ui.getRowCount(column);
2404                if (row >= maxRowCount) {
2405                    return -1;
2406                }
2407                return ui.getModelIndex(column, row);
2408            }
2409            // Won't change the selection.
2410            return -1;
2411        }
2412
2413        private int getNextIndex(JList<?> list, BasicListUI ui, int amount) {
2414            int index = adjustIndex(list.getLeadSelectionIndex(), list);
2415            int size = list.getModel().getSize();
2416
2417            if (index == -1) {
2418                if (size > 0) {
2419                    if (amount > 0) {
2420                        index = 0;
2421                    }
2422                    else {
2423                        index = size - 1;
2424                    }
2425                }
2426            } else if (size == 1) {
2427                // there's only one item so we should select it
2428                index = 0;
2429            } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
2430                if (ui != null) {
2431                    index += ui.columnCount * amount;
2432                }
2433            } else {
2434                index += amount;
2435            }
2436
2437            return index;
2438        }
2439    }
2440
2441
2442    private class Handler implements FocusListener, KeyListener,
2443                          ListDataListener, ListSelectionListener,
2444                          MouseInputListener, PropertyChangeListener,
2445                          BeforeDrag {
2446        //
2447        // KeyListener
2448        //
2449        private String prefix = "";
2450        private String typedString = "";
2451        private long lastTime = 0L;
2452
2453        /**
2454         * Invoked when a key has been typed.
2455         *
2456         * Moves the keyboard focus to the first element whose prefix matches the
2457         * sequence of alphanumeric keys pressed by the user with delay less
2458         * than value of <code>timeFactor</code> property (or 1000 milliseconds
2459         * if it is not defined). Subsequent same key presses move the keyboard
2460         * focus to the next object that starts with the same letter until another
2461         * key is pressed, then it is treated as the prefix with appropriate number
2462         * of the same letters followed by first typed another letter.
2463         */
2464        public void keyTyped(KeyEvent e) {
2465            JList<?> src = (JList)e.getSource();
2466            ListModel<?> model = src.getModel();
2467
2468            if (model.getSize() == 0 || e.isAltDown() ||
2469                    BasicGraphicsUtils.isMenuShortcutKeyDown(e) ||
2470                    isNavigationKey(e)) {
2471                // Nothing to select
2472                return;
2473            }
2474            boolean startingFromSelection = true;
2475
2476            char c = e.getKeyChar();
2477
2478            long time = e.getWhen();
2479            int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
2480            if (time - lastTime < timeFactor) {
2481                typedString += c;
2482                if((prefix.length() == 1) && (c == prefix.charAt(0))) {
2483                    // Subsequent same key presses move the keyboard focus to the next
2484                    // object that starts with the same letter.
2485                    startIndex++;
2486                } else {
2487                    prefix = typedString;
2488                }
2489            } else {
2490                startIndex++;
2491                typedString = "" + c;
2492                prefix = typedString;
2493            }
2494            lastTime = time;
2495
2496            if (startIndex < 0 || startIndex >= model.getSize()) {
2497                startingFromSelection = false;
2498                startIndex = 0;
2499            }
2500            int index = src.getNextMatch(prefix, startIndex,
2501                                         Position.Bias.Forward);
2502            if (index >= 0) {
2503                src.setSelectedIndex(index);
2504                src.ensureIndexIsVisible(index);
2505            } else if (startingFromSelection) { // wrap
2506                index = src.getNextMatch(prefix, 0,
2507                                         Position.Bias.Forward);
2508                if (index >= 0) {
2509                    src.setSelectedIndex(index);
2510                    src.ensureIndexIsVisible(index);
2511                }
2512            }
2513        }
2514
2515        /**
2516         * Invoked when a key has been pressed.
2517         *
2518         * Checks to see if the key event is a navigation key to prevent
2519         * dispatching these keys for the first letter navigation.
2520         */
2521        public void keyPressed(KeyEvent e) {
2522            if ( isNavigationKey(e) ) {
2523                prefix = "";
2524                typedString = "";
2525                lastTime = 0L;
2526            }
2527        }
2528
2529        /**
2530         * Invoked when a key has been released.
2531         * See the class description for {@link KeyEvent} for a definition of
2532         * a key released event.
2533         */
2534        public void keyReleased(KeyEvent e) {
2535        }
2536
2537        /**
2538         * Returns whether or not the supplied key event maps to a key that is used for
2539         * navigation.  This is used for optimizing key input by only passing non-
2540         * navigation keys to the first letter navigation mechanism.
2541         */
2542        private boolean isNavigationKey(KeyEvent event) {
2543            InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2544            KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2545
2546            if (inputMap != null && inputMap.get(key) != null) {
2547                return true;
2548            }
2549            return false;
2550        }
2551
2552        //
2553        // PropertyChangeListener
2554        //
2555        public void propertyChange(PropertyChangeEvent e) {
2556            String propertyName = e.getPropertyName();
2557
2558            /* If the JList.model property changes, remove our listener,
2559             * listDataListener from the old model and add it to the new one.
2560             */
2561            if (propertyName == "model") {
2562                @SuppressWarnings("unchecked")
2563                ListModel<?> oldModel = (ListModel)e.getOldValue();
2564                @SuppressWarnings("unchecked")
2565                ListModel<?> newModel = (ListModel)e.getNewValue();
2566                if (oldModel != null) {
2567                    oldModel.removeListDataListener(listDataListener);
2568                }
2569                if (newModel != null) {
2570                    newModel.addListDataListener(listDataListener);
2571                }
2572                updateLayoutStateNeeded |= modelChanged;
2573                redrawList();
2574            }
2575
2576            /* If the JList.selectionModel property changes, remove our listener,
2577             * listSelectionListener from the old selectionModel and add it to the new one.
2578             */
2579            else if (propertyName == "selectionModel") {
2580                ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
2581                ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
2582                if (oldModel != null) {
2583                    oldModel.removeListSelectionListener(listSelectionListener);
2584                }
2585                if (newModel != null) {
2586                    newModel.addListSelectionListener(listSelectionListener);
2587                }
2588                updateLayoutStateNeeded |= modelChanged;
2589                redrawList();
2590            }
2591            else if (propertyName == "cellRenderer") {
2592                updateLayoutStateNeeded |= cellRendererChanged;
2593                redrawList();
2594            }
2595            else if (propertyName == "font") {
2596                updateLayoutStateNeeded |= fontChanged;
2597                redrawList();
2598            }
2599            else if (propertyName == "prototypeCellValue") {
2600                updateLayoutStateNeeded |= prototypeCellValueChanged;
2601                redrawList();
2602            }
2603            else if (propertyName == "fixedCellHeight") {
2604                updateLayoutStateNeeded |= fixedCellHeightChanged;
2605                redrawList();
2606            }
2607            else if (propertyName == "fixedCellWidth") {
2608                updateLayoutStateNeeded |= fixedCellWidthChanged;
2609                redrawList();
2610            }
2611            else if (propertyName == "selectionForeground") {
2612                list.repaint();
2613            }
2614            else if (propertyName == "selectionBackground") {
2615                list.repaint();
2616            }
2617            else if ("layoutOrientation" == propertyName) {
2618                updateLayoutStateNeeded |= layoutOrientationChanged;
2619                layoutOrientation = list.getLayoutOrientation();
2620                redrawList();
2621            }
2622            else if ("visibleRowCount" == propertyName) {
2623                if (layoutOrientation != JList.VERTICAL) {
2624                    updateLayoutStateNeeded |= layoutOrientationChanged;
2625                    redrawList();
2626                }
2627            }
2628            else if ("componentOrientation" == propertyName) {
2629                isLeftToRight = list.getComponentOrientation().isLeftToRight();
2630                updateLayoutStateNeeded |= componentOrientationChanged;
2631                redrawList();
2632
2633                InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
2634                SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
2635                                                 inputMap);
2636            } else if ("List.isFileList" == propertyName) {
2637                updateIsFileList();
2638                redrawList();
2639            } else if ("dropLocation" == propertyName) {
2640                JList.DropLocation oldValue = (JList.DropLocation)e.getOldValue();
2641                repaintDropLocation(oldValue);
2642                repaintDropLocation(list.getDropLocation());
2643            }
2644        }
2645
2646        private void repaintDropLocation(JList.DropLocation loc) {
2647            if (loc == null) {
2648                return;
2649            }
2650
2651            Rectangle r;
2652
2653            if (loc.isInsert()) {
2654                r = getDropLineRect(loc);
2655            } else {
2656                r = getCellBounds(list, loc.getIndex());
2657            }
2658
2659            if (r != null) {
2660                list.repaint(r);
2661            }
2662        }
2663
2664        //
2665        // ListDataListener
2666        //
2667        public void intervalAdded(ListDataEvent e) {
2668            updateLayoutStateNeeded = modelChanged;
2669
2670            int minIndex = Math.min(e.getIndex0(), e.getIndex1());
2671            int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
2672
2673            /* Sync the SelectionModel with the DataModel.
2674             */
2675
2676            ListSelectionModel sm = list.getSelectionModel();
2677            if (sm != null) {
2678                sm.insertIndexInterval(minIndex, maxIndex - minIndex+1, true);
2679            }
2680
2681            /* Repaint the entire list, from the origin of
2682             * the first added cell, to the bottom of the
2683             * component.
2684             */
2685            redrawList();
2686        }
2687
2688
2689        public void intervalRemoved(ListDataEvent e)
2690        {
2691            updateLayoutStateNeeded = modelChanged;
2692
2693            /* Sync the SelectionModel with the DataModel.
2694             */
2695
2696            ListSelectionModel sm = list.getSelectionModel();
2697            if (sm != null) {
2698                sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
2699            }
2700
2701            /* Repaint the entire list, from the origin of
2702             * the first removed cell, to the bottom of the
2703             * component.
2704             */
2705
2706            redrawList();
2707        }
2708
2709
2710        public void contentsChanged(ListDataEvent e) {
2711            updateLayoutStateNeeded = modelChanged;
2712            redrawList();
2713        }
2714
2715
2716        //
2717        // ListSelectionListener
2718        //
2719        public void valueChanged(ListSelectionEvent e) {
2720            maybeUpdateLayoutState();
2721
2722            int size = list.getModel().getSize();
2723            int firstIndex = Math.min(size - 1, Math.max(e.getFirstIndex(), 0));
2724            int lastIndex = Math.min(size - 1, Math.max(e.getLastIndex(), 0));
2725
2726            Rectangle bounds = getCellBounds(list, firstIndex, lastIndex);
2727
2728            if (bounds != null) {
2729                list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2730            }
2731        }
2732
2733        //
2734        // MouseListener
2735        //
2736        public void mouseClicked(MouseEvent e) {
2737        }
2738
2739        public void mouseEntered(MouseEvent e) {
2740        }
2741
2742        public void mouseExited(MouseEvent e) {
2743        }
2744
2745        // Whether or not the mouse press (which is being considered as part
2746        // of a drag sequence) also caused the selection change to be fully
2747        // processed.
2748        private boolean dragPressDidSelection;
2749
2750        public void mousePressed(MouseEvent e) {
2751            if (SwingUtilities2.shouldIgnore(e, list)) {
2752                return;
2753            }
2754
2755            boolean dragEnabled = list.getDragEnabled();
2756            boolean grabFocus = true;
2757
2758            // different behavior if drag is enabled
2759            if (dragEnabled) {
2760                int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
2761                // if we have a valid row and this is a drag initiating event
2762                if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
2763                    dragPressDidSelection = false;
2764
2765                    if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
2766                        // do nothing for control - will be handled on release
2767                        // or when drag starts
2768                        return;
2769                    } else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
2770                        // clicking on something that's already selected
2771                        // and need to make it the lead now
2772                        list.addSelectionInterval(row, row);
2773                        return;
2774                    }
2775
2776                    // could be a drag initiating event - don't grab focus
2777                    grabFocus = false;
2778
2779                    dragPressDidSelection = true;
2780                }
2781            } else {
2782                // When drag is enabled mouse drags won't change the selection
2783                // in the list, so we only set the isAdjusting flag when it's
2784                // not enabled
2785                list.setValueIsAdjusting(true);
2786            }
2787
2788            if (grabFocus) {
2789                SwingUtilities2.adjustFocus(list);
2790            }
2791
2792            adjustSelection(e);
2793        }
2794
2795        private void adjustSelection(MouseEvent e) {
2796            int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
2797            if (row < 0) {
2798                // If shift is down in multi-select, we should do nothing.
2799                // For single select or non-shift-click, clear the selection
2800                if (isFileList &&
2801                    e.getID() == MouseEvent.MOUSE_PRESSED &&
2802                    (!e.isShiftDown() ||
2803                     list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
2804                    list.clearSelection();
2805                }
2806            }
2807            else {
2808                int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
2809                boolean anchorSelected;
2810                if (anchorIndex == -1) {
2811                    anchorIndex = 0;
2812                    anchorSelected = false;
2813                } else {
2814                    anchorSelected = list.isSelectedIndex(anchorIndex);
2815                }
2816
2817                if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
2818                    if (e.isShiftDown()) {
2819                        if (anchorSelected) {
2820                            list.addSelectionInterval(anchorIndex, row);
2821                        } else {
2822                            list.removeSelectionInterval(anchorIndex, row);
2823                            if (isFileList) {
2824                                list.addSelectionInterval(row, row);
2825                                list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
2826                            }
2827                        }
2828                    } else if (list.isSelectedIndex(row)) {
2829                        list.removeSelectionInterval(row, row);
2830                    } else {
2831                        list.addSelectionInterval(row, row);
2832                    }
2833                } else if (e.isShiftDown()) {
2834                    list.setSelectionInterval(anchorIndex, row);
2835                } else {
2836                    list.setSelectionInterval(row, row);
2837                }
2838            }
2839        }
2840
2841        public void dragStarting(MouseEvent me) {
2842            if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) {
2843                int row = SwingUtilities2.loc2IndexFileList(list, me.getPoint());
2844                list.addSelectionInterval(row, row);
2845            }
2846        }
2847
2848        public void mouseDragged(MouseEvent e) {
2849            if (SwingUtilities2.shouldIgnore(e, list)) {
2850                return;
2851            }
2852
2853            if (list.getDragEnabled()) {
2854                DragRecognitionSupport.mouseDragged(e, this);
2855                return;
2856            }
2857
2858            if (e.isShiftDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
2859                return;
2860            }
2861
2862            int row = locationToIndex(list, e.getPoint());
2863            if (row != -1) {
2864                // 4835633.  Dragging onto a File should not select it.
2865                if (isFileList) {
2866                    return;
2867                }
2868                Rectangle cellBounds = getCellBounds(list, row, row);
2869                if (cellBounds != null) {
2870                    list.scrollRectToVisible(cellBounds);
2871                    list.setSelectionInterval(row, row);
2872                }
2873            }
2874        }
2875
2876        public void mouseMoved(MouseEvent e) {
2877        }
2878
2879        public void mouseReleased(MouseEvent e) {
2880            if (SwingUtilities2.shouldIgnore(e, list)) {
2881                return;
2882            }
2883
2884            if (list.getDragEnabled()) {
2885                MouseEvent me = DragRecognitionSupport.mouseReleased(e);
2886                if (me != null) {
2887                    SwingUtilities2.adjustFocus(list);
2888                    if (!dragPressDidSelection) {
2889                        adjustSelection(me);
2890                    }
2891                }
2892            } else {
2893                list.setValueIsAdjusting(false);
2894            }
2895        }
2896
2897        //
2898        // FocusListener
2899        //
2900        protected void repaintCellFocus()
2901        {
2902            int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
2903            if (leadIndex != -1) {
2904                Rectangle r = getCellBounds(list, leadIndex, leadIndex);
2905                if (r != null) {
2906                    list.repaint(r.x, r.y, r.width, r.height);
2907                }
2908            }
2909        }
2910
2911        /* The focusGained() focusLost() methods run when the JList
2912         * focus changes.
2913         */
2914
2915        public void focusGained(FocusEvent e) {
2916            repaintCellFocus();
2917        }
2918
2919        public void focusLost(FocusEvent e) {
2920            repaintCellFocus();
2921        }
2922    }
2923
2924    private static int adjustIndex(int index, JList<?> list) {
2925        return index < list.getModel().getSize() ? index : -1;
2926    }
2927
2928    private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
2929
2930    @SuppressWarnings("serial") // Superclass is a JDK-implementation class
2931    static class ListTransferHandler extends TransferHandler implements UIResource {
2932
2933        /**
2934         * Create a Transferable to use as the source for a data transfer.
2935         *
2936         * @param c  The component holding the data to be transfered.  This
2937         *  argument is provided to enable sharing of TransferHandlers by
2938         *  multiple components.
2939         * @return  The representation of the data to be transfered.
2940         *
2941         */
2942        @SuppressWarnings("deprecation")
2943        protected Transferable createTransferable(JComponent c) {
2944            if (c instanceof JList) {
2945                JList<?> list = (JList) c;
2946                Object[] values = list.getSelectedValues();
2947
2948                if (values == null || values.length == 0) {
2949                    return null;
2950                }
2951
2952                StringBuilder plainStr = new StringBuilder();
2953                StringBuilder htmlStr = new StringBuilder();
2954
2955                htmlStr.append("<html>\n<body>\n<ul>\n");
2956
2957                for (int i = 0; i < values.length; i++) {
2958                    Object obj = values[i];
2959                    String val = ((obj == null) ? "" : obj.toString());
2960                    plainStr.append(val).append('\n');
2961                    htmlStr.append("  <li>").append(val).append('\n');
2962                }
2963
2964                // remove the last newline
2965                plainStr.deleteCharAt(plainStr.length() - 1);
2966                htmlStr.append("</ul>\n</body>\n</html>");
2967
2968                return new BasicTransferable(plainStr.toString(), htmlStr.toString());
2969            }
2970
2971            return null;
2972        }
2973
2974        public int getSourceActions(JComponent c) {
2975            return COPY;
2976        }
2977
2978    }
2979}
2980