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