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 <= 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 <= 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™ 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 "protected" 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™ 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™ 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™ 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 @SuppressWarnings("serial") // Superclass is a JDK-implementation class 2838 static class ListTransferHandler extends TransferHandler implements UIResource { 2839 2840 /** 2841 * Create a Transferable to use as the source for a data transfer. 2842 * 2843 * @param c The component holding the data to be transfered. This 2844 * argument is provided to enable sharing of TransferHandlers by 2845 * multiple components. 2846 * @return The representation of the data to be transfered. 2847 * 2848 */ 2849 protected Transferable createTransferable(JComponent c) { 2850 if (c instanceof JList) { 2851 JList list = (JList) c; 2852 Object[] values = list.getSelectedValues(); 2853 2854 if (values == null || values.length == 0) { 2855 return null; 2856 } 2857 2858 StringBuffer plainBuf = new StringBuffer(); 2859 StringBuffer htmlBuf = new StringBuffer(); 2860 2861 htmlBuf.append("<html>\n<body>\n<ul>\n"); 2862 2863 for (int i = 0; i < values.length; i++) { 2864 Object obj = values[i]; 2865 String val = ((obj == null) ? "" : obj.toString()); 2866 plainBuf.append(val + "\n"); 2867 htmlBuf.append(" <li>" + val + "\n"); 2868 } 2869 2870 // remove the last newline 2871 plainBuf.deleteCharAt(plainBuf.length() - 1); 2872 htmlBuf.append("</ul>\n</body>\n</html>"); 2873 2874 return new BasicTransferable(plainBuf.toString(), htmlBuf.toString()); 2875 } 2876 2877 return null; 2878 } 2879 2880 public int getSourceActions(JComponent c) { 2881 return COPY; 2882 } 2883 2884 } 2885 }