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