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