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