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