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