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