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