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