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